18d307f52SEvan Bacon#!/usr/bin/env node 28d307f52SEvan Baconimport arg from 'arg'; 38d307f52SEvan Baconimport chalk from 'chalk'; 42ff06c55SEvan Baconimport Debug from 'debug'; 52ff06c55SEvan Baconimport { boolish } from 'getenv'; 62ff06c55SEvan Bacon 72ff06c55SEvan Bacon// Setup before requiring `debug`. 82ff06c55SEvan Baconif (boolish('EXPO_DEBUG', false)) { 92ff06c55SEvan Bacon Debug.enable('expo:*'); 102ff06c55SEvan Bacon} else if (Debug.enabled('expo:')) { 112ff06c55SEvan Bacon process.env.EXPO_DEBUG = '1'; 122ff06c55SEvan Bacon} 138d307f52SEvan Bacon 148d307f52SEvan Baconconst defaultCmd = 'start'; 158d307f52SEvan Bacon 168d307f52SEvan Baconexport type Command = (argv?: string[]) => void; 178d307f52SEvan Bacon 188d307f52SEvan Baconconst commands: { [command: string]: () => Promise<Command> } = { 198d307f52SEvan Bacon // Add a new command here 202dd43328SEvan Bacon // NOTE(EvanBacon): Ensure every bundler-related command sets `NODE_ENV` as expected for the command. 21*1a3a1db5SEvan Bacon 'run:ios': () => import('../src/run/ios/index.js').then((i) => i.expoRunIos), 22*1a3a1db5SEvan Bacon 'run:android': () => import('../src/run/android/index.js').then((i) => i.expoRunAndroid), 23*1a3a1db5SEvan Bacon start: () => import('../src/start/index.js').then((i) => i.expoStart), 24*1a3a1db5SEvan Bacon prebuild: () => import('../src/prebuild/index.js').then((i) => i.expoPrebuild), 25*1a3a1db5SEvan Bacon config: () => import('../src/config/index.js').then((i) => i.expoConfig), 26*1a3a1db5SEvan Bacon export: () => import('../src/export/index.js').then((i) => i.expoExport), 27*1a3a1db5SEvan Bacon 'export:web': () => import('../src/export/web/index.js').then((i) => i.expoExportWeb), 28*1a3a1db5SEvan Bacon 'export:embed': () => import('../src/export/embed/index.js').then((i) => i.expoExportEmbed), 2909bb6093SEvan Bacon 3009bb6093SEvan Bacon // Auxiliary commands 31*1a3a1db5SEvan Bacon install: () => import('../src/install/index.js').then((i) => i.expoInstall), 32*1a3a1db5SEvan Bacon add: () => import('../src/install/index.js').then((i) => i.expoInstall), 33*1a3a1db5SEvan Bacon customize: () => import('../src/customize/index.js').then((i) => i.expoCustomize), 3409bb6093SEvan Bacon 358d307f52SEvan Bacon // Auth 36*1a3a1db5SEvan Bacon login: () => import('../src/login/index.js').then((i) => i.expoLogin), 37*1a3a1db5SEvan Bacon logout: () => import('../src/logout/index.js').then((i) => i.expoLogout), 38*1a3a1db5SEvan Bacon register: () => import('../src/register/index.js').then((i) => i.expoRegister), 39*1a3a1db5SEvan Bacon whoami: () => import('../src/whoami/index.js').then((i) => i.expoWhoami), 408d307f52SEvan Bacon}; 418d307f52SEvan Bacon 428d307f52SEvan Baconconst args = arg( 438d307f52SEvan Bacon { 448d307f52SEvan Bacon // Types 458d307f52SEvan Bacon '--version': Boolean, 468d307f52SEvan Bacon '--help': Boolean, 47120fe602SEvan Bacon // NOTE(EvanBacon): This is here to silence warnings from processes that 48120fe602SEvan Bacon // expect the global expo-cli. 49120fe602SEvan Bacon '--non-interactive': Boolean, 508d307f52SEvan Bacon 518d307f52SEvan Bacon // Aliases 528d307f52SEvan Bacon '-v': '--version', 538d307f52SEvan Bacon '-h': '--help', 548d307f52SEvan Bacon }, 558d307f52SEvan Bacon { 568d307f52SEvan Bacon permissive: true, 578d307f52SEvan Bacon } 588d307f52SEvan Bacon); 598d307f52SEvan Bacon 608d307f52SEvan Baconif (args['--version']) { 618d307f52SEvan Bacon // Version is added in the build script. 628d307f52SEvan Bacon console.log(process.env.__EXPO_VERSION); 638d307f52SEvan Bacon process.exit(0); 648d307f52SEvan Bacon} 658d307f52SEvan Bacon 66120fe602SEvan Baconif (args['--non-interactive']) { 67120fe602SEvan Bacon console.warn(chalk.yellow` {bold --non-interactive} is not supported, use {bold $CI=1} instead`); 68120fe602SEvan Bacon} 69120fe602SEvan Bacon 708d307f52SEvan Bacon// Check if we are running `npx expo <subcommand>` or `npx expo` 718d307f52SEvan Baconconst isSubcommand = Boolean(commands[args._[0]]); 728d307f52SEvan Bacon 738d307f52SEvan Bacon// Handle `--help` flag 748d307f52SEvan Baconif (!isSubcommand && args['--help']) { 7583d464dcSEvan Bacon const { 7683d464dcSEvan Bacon login, 7783d464dcSEvan Bacon logout, 7883d464dcSEvan Bacon whoami, 7983d464dcSEvan Bacon register, 8083d464dcSEvan Bacon start, 8183d464dcSEvan Bacon install, 821971907aSEvan Bacon add, 8383d464dcSEvan Bacon export: _export, 8483d464dcSEvan Bacon config, 85dfe12d45SEvan Bacon customize, 863d6e487dSEvan Bacon prebuild, 87c4ef02aeSEvan Bacon 'run:ios': runIos, 883d6e487dSEvan Bacon 'run:android': runAndroid, 89b6b91c50SEvan Bacon // NOTE(EvanBacon): Don't document this command as it's a temporary 90b6b91c50SEvan Bacon // workaround until we can use `expo export` for all production bundling. 91b6b91c50SEvan Bacon // https://github.com/expo/expo/pull/21396/files#r1121025873 92b6b91c50SEvan Bacon 'export:embed': exportEmbed_unused, 9383d464dcSEvan Bacon ...others 9483d464dcSEvan Bacon } = commands; 9583d464dcSEvan Bacon 968d307f52SEvan Bacon console.log(chalk` 978d307f52SEvan Bacon {bold Usage} 9883d464dcSEvan Bacon {dim $} npx expo <command> 998d307f52SEvan Bacon 10083d464dcSEvan Bacon {bold Commands} 101dfe12d45SEvan Bacon ${Object.keys({ start, export: _export, ...others }).join(', ')} 102c4ef02aeSEvan Bacon ${Object.keys({ 'run:ios': runIos, 'run:android': runAndroid, prebuild }).join(', ')} 103dfe12d45SEvan Bacon ${Object.keys({ install, customize, config }).join(', ')} 10483d464dcSEvan Bacon {dim ${Object.keys({ login, logout, whoami, register }).join(', ')}} 1058d307f52SEvan Bacon 1068d307f52SEvan Bacon {bold Options} 1078d307f52SEvan Bacon --version, -v Version number 10883d464dcSEvan Bacon --help, -h Usage info 1098d307f52SEvan Bacon 11083d464dcSEvan Bacon For more info run a command with the {bold --help} flag 11183d464dcSEvan Bacon {dim $} npx expo start --help 1128d307f52SEvan Bacon`); 113ea99eec9SEvan Bacon 1148d307f52SEvan Bacon process.exit(0); 1158d307f52SEvan Bacon} 1168d307f52SEvan Bacon 1174b86b89eSEvan Bacon// NOTE(EvanBacon): Squat some directory names to help with migration, 1184b86b89eSEvan Bacon// users can still use folders named "send" or "eject" by using the fully qualified `npx expo start ./send`. 1194b86b89eSEvan Baconif (!isSubcommand) { 1204b86b89eSEvan Bacon const migrationMap: Record<string, string> = { 1214b86b89eSEvan Bacon init: 'npx create-expo-app', 1224b86b89eSEvan Bacon eject: 'npx expo prebuild', 123120fe602SEvan Bacon web: 'npx expo start --web', 1244b86b89eSEvan Bacon 'start:web': 'npx expo start --web', 1254b86b89eSEvan Bacon 'build:ios': 'eas build -p ios', 1264b86b89eSEvan Bacon 'build:android': 'eas build -p android', 1274b86b89eSEvan Bacon 'client:install:ios': 'npx expo start --ios', 1284b86b89eSEvan Bacon 'client:install:android': 'npx expo start --android', 129c3d7da5bSKeith Kurak doctor: 'npx expo-doctor', 13032ffbf34SEvan Bacon upgrade: 'expo-cli upgrade', 1314b86b89eSEvan Bacon 'customize:web': 'npx expo customize', 1324b86b89eSEvan Bacon 1334b86b89eSEvan Bacon publish: 'eas update', 1344b86b89eSEvan Bacon 'publish:set': 'eas update', 1354b86b89eSEvan Bacon 'publish:rollback': 'eas update', 1364b86b89eSEvan Bacon 'publish:history': 'eas update', 1374b86b89eSEvan Bacon 'publish:details': 'eas update', 1384b86b89eSEvan Bacon 13932ffbf34SEvan Bacon 'build:web': 'npx expo export:web', 1404b86b89eSEvan Bacon 1414b86b89eSEvan Bacon 'credentials:manager': `eas credentials`, 1424b86b89eSEvan Bacon 'fetch:ios:certs': `eas credentials`, 1434b86b89eSEvan Bacon 'fetch:android:keystore': `eas credentials`, 1444b86b89eSEvan Bacon 'fetch:android:hashes': `eas credentials`, 1454b86b89eSEvan Bacon 'fetch:android:upload-cert': `eas credentials`, 1464b86b89eSEvan Bacon 'push:android:upload': `eas credentials`, 1474b86b89eSEvan Bacon 'push:android:show': `eas credentials`, 1484b86b89eSEvan Bacon 'push:android:clear': `eas credentials`, 1494b86b89eSEvan Bacon url: `eas build:list`, 1504b86b89eSEvan Bacon 'url:ipa': `eas build:list`, 1514b86b89eSEvan Bacon 'url:apk': `eas build:list`, 1524b86b89eSEvan Bacon webhooks: `eas webhook`, 1534b86b89eSEvan Bacon 'webhooks:add': `eas webhook:create`, 1544b86b89eSEvan Bacon 'webhooks:remove': `eas webhook:delete`, 1554b86b89eSEvan Bacon 'webhooks:update': `eas webhook:update`, 1564b86b89eSEvan Bacon 1574b86b89eSEvan Bacon 'build:status': `eas build:list`, 1584b86b89eSEvan Bacon 'upload:android': `eas submit -p android`, 1594b86b89eSEvan Bacon 'upload:ios': `eas submit -p ios`, 1604b86b89eSEvan Bacon }; 1614b86b89eSEvan Bacon 162ea99eec9SEvan Bacon // TODO: Log telemetry about invalid command used. 1634b86b89eSEvan Bacon const subcommand = args._[0]; 1644b86b89eSEvan Bacon if (subcommand in migrationMap) { 1654b86b89eSEvan Bacon const replacement = migrationMap[subcommand]; 1664b86b89eSEvan Bacon console.log(); 1674b86b89eSEvan Bacon console.log( 1684b86b89eSEvan Bacon chalk.yellow` {gray $} {bold expo ${subcommand}} is not supported in the local CLI, please use {bold ${replacement}} instead` 1694b86b89eSEvan Bacon ); 1704b86b89eSEvan Bacon console.log(); 1714b86b89eSEvan Bacon process.exit(1); 1724b86b89eSEvan Bacon } 1734b86b89eSEvan Bacon const deprecated = ['send', 'client:ios']; 1744b86b89eSEvan Bacon if (deprecated.includes(subcommand)) { 1754b86b89eSEvan Bacon console.log(); 1764b86b89eSEvan Bacon console.log(chalk.yellow` {gray $} {bold expo ${subcommand}} is deprecated`); 1774b86b89eSEvan Bacon console.log(); 1784b86b89eSEvan Bacon process.exit(1); 1794b86b89eSEvan Bacon } 1804b86b89eSEvan Bacon} 1814b86b89eSEvan Bacon 1828d307f52SEvan Baconconst command = isSubcommand ? args._[0] : defaultCmd; 1838d307f52SEvan Baconconst commandArgs = isSubcommand ? args._.slice(1) : args._; 1848d307f52SEvan Bacon 1858d307f52SEvan Bacon// Push the help flag to the subcommand args. 1868d307f52SEvan Baconif (args['--help']) { 1878d307f52SEvan Bacon commandArgs.push('--help'); 1888d307f52SEvan Bacon} 1898d307f52SEvan Bacon 1908d307f52SEvan Bacon// Install exit hooks 1918d307f52SEvan Baconprocess.on('SIGINT', () => process.exit(0)); 1928d307f52SEvan Baconprocess.on('SIGTERM', () => process.exit(0)); 1938d307f52SEvan Bacon 194ea99eec9SEvan Baconcommands[command]().then((exec) => { 195ea99eec9SEvan Bacon exec(commandArgs); 196ea99eec9SEvan Bacon 197ea99eec9SEvan Bacon // NOTE(EvanBacon): Track some basic telemetry events indicating the command 198ea99eec9SEvan Bacon // that was run. This can be disabled with the $EXPO_NO_TELEMETRY environment variable. 199ea99eec9SEvan Bacon // We do this to determine how well deprecations are going before removing a command. 200ea99eec9SEvan Bacon const { logEventAsync } = 201ea99eec9SEvan Bacon require('../src/utils/analytics/rudderstackClient') as typeof import('../src/utils/analytics/rudderstackClient'); 202ea99eec9SEvan Bacon logEventAsync('action', { 203ea99eec9SEvan Bacon action: `expo ${command}`, 204ea99eec9SEvan Bacon source: 'expo/cli', 205ea99eec9SEvan Bacon source_version: process.env.__EXPO_VERSION, 206ea99eec9SEvan Bacon }); 207ea99eec9SEvan Bacon}); 208