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