xref: /expo/packages/@expo/cli/bin/cli.ts (revision ea0d7378)
1#!/usr/bin/env node
2import arg from 'arg';
3import chalk from 'chalk';
4import Debug from 'debug';
5import { boolish } from 'getenv';
6
7// Setup before requiring `debug`.
8if (boolish('EXPO_DEBUG', false)) {
9  Debug.enable('expo:*');
10} else if (Debug.enabled('expo:')) {
11  process.env.EXPO_DEBUG = '1';
12}
13
14const defaultCmd = 'start';
15
16export type Command = (argv?: string[]) => void;
17
18const commands: { [command: string]: () => Promise<Command> } = {
19  // Add a new command here
20  'run:ios': () => import('../src/run/ios').then((i) => i.expoRunIos),
21  'run:android': () => import('../src/run/android').then((i) => i.expoRunAndroid),
22  start: () => import('../src/start').then((i) => i.expoStart),
23  prebuild: () => import('../src/prebuild').then((i) => i.expoPrebuild),
24  config: () => import('../src/config').then((i) => i.expoConfig),
25  export: () => import('../src/export').then((i) => i.expoExport),
26  'export:web': () => import('../src/export/web').then((i) => i.expoExportWeb),
27
28  // Auxiliary commands
29  install: () => import('../src/install').then((i) => i.expoInstall),
30  customize: () => import('../src/customize').then((i) => i.expoCustomize),
31
32  // Auth
33  login: () => import('../src/login').then((i) => i.expoLogin),
34  logout: () => import('../src/logout').then((i) => i.expoLogout),
35  register: () => import('../src/register').then((i) => i.expoRegister),
36  whoami: () => import('../src/whoami').then((i) => i.expoWhoami),
37};
38
39const args = arg(
40  {
41    // Types
42    '--version': Boolean,
43    '--help': Boolean,
44
45    // Aliases
46    '-v': '--version',
47    '-h': '--help',
48  },
49  {
50    permissive: true,
51  }
52);
53
54if (args['--version']) {
55  // Version is added in the build script.
56  console.log(process.env.__EXPO_VERSION);
57  process.exit(0);
58}
59
60// Check if we are running `npx expo <subcommand>` or `npx expo`
61const isSubcommand = Boolean(commands[args._[0]]);
62
63// Handle `--help` flag
64if (!isSubcommand && args['--help']) {
65  const {
66    login,
67    logout,
68    whoami,
69    register,
70    start,
71    install,
72    export: _export,
73    config,
74    customize,
75    prebuild,
76    'run:ios': runIos,
77    'run:android': runAndroid,
78    ...others
79  } = commands;
80
81  console.log(chalk`
82  {bold Usage}
83    {dim $} npx expo <command>
84
85  {bold Commands}
86    ${Object.keys({ start, export: _export, ...others }).join(', ')}
87    ${Object.keys({ 'run:ios': runIos, 'run:android': runAndroid, prebuild }).join(', ')}
88    ${Object.keys({ install, customize, config }).join(', ')}
89    {dim ${Object.keys({ login, logout, whoami, register }).join(', ')}}
90
91  {bold Options}
92    --version, -v   Version number
93    --help, -h      Usage info
94
95  For more info run a command with the {bold --help} flag
96    {dim $} npx expo start --help
97`);
98  process.exit(0);
99}
100
101// NOTE(EvanBacon): Squat some directory names to help with migration,
102// users can still use folders named "send" or "eject" by using the fully qualified `npx expo start ./send`.
103if (!isSubcommand) {
104  const migrationMap: Record<string, string> = {
105    init: 'npx create-expo-app',
106    eject: 'npx expo prebuild',
107    'start:web': 'npx expo start --web',
108    'build:ios': 'eas build -p ios',
109    'build:android': 'eas build -p android',
110    'client:install:ios': 'npx expo start --ios',
111    'client:install:android': 'npx expo start --android',
112    doctor: 'expo doctor',
113    upgrade: 'expo upgrade',
114    'customize:web': 'npx expo customize',
115
116    publish: 'eas update',
117    'publish:set': 'eas update',
118    'publish:rollback': 'eas update',
119    'publish:history': 'eas update',
120    'publish:details': 'eas update',
121
122    'build:web': 'npx expo export',
123
124    'credentials:manager': `eas credentials`,
125    'fetch:ios:certs': `eas credentials`,
126    'fetch:android:keystore': `eas credentials`,
127    'fetch:android:hashes': `eas credentials`,
128    'fetch:android:upload-cert': `eas credentials`,
129    'push:android:upload': `eas credentials`,
130    'push:android:show': `eas credentials`,
131    'push:android:clear': `eas credentials`,
132    url: `eas build:list`,
133    'url:ipa': `eas build:list`,
134    'url:apk': `eas build:list`,
135    webhooks: `eas webhook`,
136    'webhooks:add': `eas webhook:create`,
137    'webhooks:remove': `eas webhook:delete`,
138    'webhooks:update': `eas webhook:update`,
139
140    'build:status': `eas build:list`,
141    'upload:android': `eas submit -p android`,
142    'upload:ios': `eas submit -p ios`,
143  };
144
145  const subcommand = args._[0];
146  if (subcommand in migrationMap) {
147    const replacement = migrationMap[subcommand];
148    console.log();
149    console.log(
150      chalk.yellow`  {gray $} {bold expo ${subcommand}} is not supported in the local CLI, please use {bold ${replacement}} instead`
151    );
152    console.log();
153    process.exit(1);
154  }
155  const deprecated = ['send', 'client:ios'];
156  if (deprecated.includes(subcommand)) {
157    console.log();
158    console.log(chalk.yellow`  {gray $} {bold expo ${subcommand}} is deprecated`);
159    console.log();
160    process.exit(1);
161  }
162}
163
164const command = isSubcommand ? args._[0] : defaultCmd;
165const commandArgs = isSubcommand ? args._.slice(1) : args._;
166
167// Push the help flag to the subcommand args.
168if (args['--help']) {
169  commandArgs.push('--help');
170}
171
172// Install exit hooks
173process.on('SIGINT', () => process.exit(0));
174process.on('SIGTERM', () => process.exit(0));
175
176commands[command]().then((exec) => exec(commandArgs));
177