18d307f52SEvan Baconimport { ExpoConfig } from '@expo/config-types';
28d307f52SEvan Baconimport chalk from 'chalk';
38d307f52SEvan Baconimport qrcode from 'qrcode-terminal';
48d307f52SEvan Baconimport wrapAnsi from 'wrap-ansi';
58d307f52SEvan Bacon
68d307f52SEvan Baconimport * as Log from '../../log';
78d307f52SEvan Bacon
88d307f52SEvan Baconexport const BLT = '\u203A';
98d307f52SEvan Bacon
108d307f52SEvan Baconexport type StartOptions = {
118d307f52SEvan Bacon  isWebSocketsEnabled?: boolean;
128d307f52SEvan Bacon  devClient?: boolean;
138d307f52SEvan Bacon  reset?: boolean;
148d307f52SEvan Bacon  nonPersistent?: boolean;
158d307f52SEvan Bacon  maxWorkers?: number;
168d307f52SEvan Bacon  platforms?: ExpoConfig['platforms'];
178d307f52SEvan Bacon};
188d307f52SEvan Bacon
198d307f52SEvan Baconexport const printHelp = (): void => {
208d307f52SEvan Bacon  logCommandsTable([{ key: '?', msg: 'show all commands' }]);
218d307f52SEvan Bacon};
228d307f52SEvan Bacon
238d307f52SEvan Bacon/** Print the world famous 'Expo QR Code'. */
248d307f52SEvan Baconexport function printQRCode(url: string) {
258d307f52SEvan Bacon  qrcode.generate(url, { small: true }, (code) => Log.log(code));
268d307f52SEvan Bacon}
278d307f52SEvan Bacon
288d307f52SEvan Baconexport const getTerminalColumns = () => process.stdout.columns || 80;
298d307f52SEvan Baconexport const printItem = (text: string): string =>
308d307f52SEvan Bacon  `${BLT} ` + wrapAnsi(text, getTerminalColumns()).trimStart();
318d307f52SEvan Bacon
328d307f52SEvan Baconexport function printUsage(
338d307f52SEvan Bacon  options: Pick<StartOptions, 'devClient' | 'isWebSocketsEnabled' | 'platforms'>,
348d307f52SEvan Bacon  { verbose }: { verbose: boolean }
358d307f52SEvan Bacon) {
368d307f52SEvan Bacon  const isMac = process.platform === 'darwin';
378d307f52SEvan Bacon
388d307f52SEvan Bacon  const { platforms = ['ios', 'android', 'web'] } = options;
398d307f52SEvan Bacon
408d307f52SEvan Bacon  const isAndroidDisabled = !platforms.includes('android');
418d307f52SEvan Bacon  const isIosDisabled = !platforms.includes('ios');
428d307f52SEvan Bacon  const isWebDisable = !platforms.includes('web');
438d307f52SEvan Bacon
44a7e47f4dSEvan Bacon  const switchMsg = `switch to ${options.devClient === false ? 'development build' : 'Expo Go'}`;
45a7e47f4dSEvan Bacon  const target = options.devClient === false ? `Expo Go` : 'development build';
46a7e47f4dSEvan Bacon
47a7e47f4dSEvan Bacon  Log.log();
48a7e47f4dSEvan Bacon  Log.log(printItem(chalk`Using {cyan ${target}}`));
49a7e47f4dSEvan Bacon
508d307f52SEvan Bacon  if (verbose) {
518d307f52SEvan Bacon    logCommandsTable([
52a7e47f4dSEvan Bacon      { key: 's', msg: switchMsg },
538d307f52SEvan Bacon      {},
548d307f52SEvan Bacon      { key: 'a', msg: 'open Android', disabled: isAndroidDisabled },
558d307f52SEvan Bacon      { key: 'shift+a', msg: 'select a device or emulator', disabled: isAndroidDisabled },
568d307f52SEvan Bacon      isMac && { key: 'i', msg: 'open iOS simulator', disabled: isIosDisabled },
578d307f52SEvan Bacon      isMac && { key: 'shift+i', msg: 'select a simulator', disabled: isIosDisabled },
588d307f52SEvan Bacon      { key: 'w', msg: 'open web', disabled: isWebDisable },
598d307f52SEvan Bacon      {},
608d307f52SEvan Bacon      { key: 'r', msg: 'reload app' },
6148103a3dSEvan Bacon      !!options.isWebSocketsEnabled && { key: 'j', msg: 'open debugger' },
628d307f52SEvan Bacon      !!options.isWebSocketsEnabled && { key: 'm', msg: 'toggle menu' },
638d307f52SEvan Bacon      !!options.isWebSocketsEnabled && { key: 'shift+m', msg: 'more tools' },
648d307f52SEvan Bacon      { key: 'o', msg: 'open project code in your editor' },
658d307f52SEvan Bacon      { key: 'c', msg: 'show project QR' },
668d307f52SEvan Bacon      {},
678d307f52SEvan Bacon    ]);
688d307f52SEvan Bacon  } else {
698d307f52SEvan Bacon    logCommandsTable([
70a7e47f4dSEvan Bacon      { key: 's', msg: switchMsg },
718d307f52SEvan Bacon      {},
728d307f52SEvan Bacon      { key: 'a', msg: 'open Android', disabled: isAndroidDisabled },
738d307f52SEvan Bacon      isMac && { key: 'i', msg: 'open iOS simulator', disabled: isIosDisabled },
748d307f52SEvan Bacon      { key: 'w', msg: 'open web', disabled: isWebDisable },
758d307f52SEvan Bacon      {},
7648103a3dSEvan Bacon      { key: 'j', msg: 'open debugger' },
778d307f52SEvan Bacon      { key: 'r', msg: 'reload app' },
788d307f52SEvan Bacon      !!options.isWebSocketsEnabled && { key: 'm', msg: 'toggle menu' },
79*20f5f217SEvan Bacon      { key: 'o', msg: 'open project code in your editor' },
808d307f52SEvan Bacon      {},
818d307f52SEvan Bacon    ]);
828d307f52SEvan Bacon  }
838d307f52SEvan Bacon}
848d307f52SEvan Bacon
858d307f52SEvan Baconfunction logCommandsTable(
868d307f52SEvan Bacon  ui: (false | { key?: string; msg?: string; status?: string; disabled?: boolean })[]
878d307f52SEvan Bacon) {
888d307f52SEvan Bacon  Log.log(
898d307f52SEvan Bacon    ui
908d307f52SEvan Bacon      .filter(Boolean)
918d307f52SEvan Bacon      // @ts-ignore: filter doesn't work
928d307f52SEvan Bacon      .map(({ key, msg, status, disabled }) => {
938d307f52SEvan Bacon        if (!key) return '';
948d307f52SEvan Bacon        let view = `${BLT} `;
958d307f52SEvan Bacon        if (key.length === 1) view += 'Press ';
968d307f52SEvan Bacon        view += chalk`{bold ${key}} {dim │} `;
978d307f52SEvan Bacon        view += msg;
988d307f52SEvan Bacon        if (status) {
998d307f52SEvan Bacon          view += ` ${chalk.dim(`(${chalk.italic(status)})`)}`;
1008d307f52SEvan Bacon        }
1018d307f52SEvan Bacon        if (disabled) {
1028d307f52SEvan Bacon          view = chalk.dim(view);
1038d307f52SEvan Bacon        }
1048d307f52SEvan Bacon        return view;
1058d307f52SEvan Bacon      })
1068d307f52SEvan Bacon      .join('\n')
1078d307f52SEvan Bacon  );
1088d307f52SEvan Bacon}
109