1#!/usr/bin/env node 2import { Spec } from 'arg'; 3import chalk from 'chalk'; 4 5import { CLI_NAME } from './cmd'; 6import { ExitError } from './error'; 7import { Log } from './log'; 8import { assertWithOptionsArgs, printHelp, resolveStringOrBooleanArgsAsync } from './utils/args'; 9 10const debug = require('debug')('expo:init:cli') as typeof console.log; 11 12async function run() { 13 const argv = process.argv.slice(2) ?? []; 14 const rawArgsMap: Spec = { 15 // Types 16 '--yes': Boolean, 17 '--no-install': Boolean, 18 '--help': Boolean, 19 '--version': Boolean, 20 // Aliases 21 '-y': '--yes', 22 '-h': '--help', 23 '-v': '--version', 24 }; 25 const args = assertWithOptionsArgs(rawArgsMap, { 26 argv, 27 permissive: true, 28 }); 29 30 if (args['--version']) { 31 Log.exit(require('../package.json').version, 0); 32 } 33 34 if (args['--help']) { 35 printHelp( 36 `Creates a new Expo project`, 37 chalk`npx ${CLI_NAME} {cyan <path>} [options]`, 38 [ 39 `-y, --yes Use the default options for creating a project`, 40 ` --no-install Skip installing npm packages or CocoaPods`, 41 chalk`-t, --template {gray [pkg]} NPM template to use: blank, tabs, bare-minimum. Default: blank`, 42 chalk`-e, --example {gray [name]} Example name from {underline https://github.com/expo/examples}.`, 43 `-v, --version Version number`, 44 `-h, --help Usage info`, 45 ].join('\n'), 46 chalk` 47 {gray To choose a template pass in the {bold --template} arg:} 48 49 {gray $} npm create expo-app {cyan --template} 50 51 {gray To choose an Expo example pass in the {bold --example} arg:} 52 53 {gray $} npm create expo-app {cyan --example} 54 {gray $} npm create expo-app {cyan --example with-router} 55 56 {gray The package manager used for installing} 57 {gray node modules is based on how you invoke the CLI:} 58 59 {bold npm:} {cyan npm create expo-app} 60 {bold yarn:} {cyan yarn create expo-app} 61 {bold pnpm:} {cyan pnpm create expo-app} 62 {bold bun:} {cyan bunx create-expo-app} 63 ` 64 ); 65 } 66 67 const { AnalyticsEventPhases, AnalyticsEventTypes, flushAsync, track } = await import( 68 './telemetry' 69 ); 70 try { 71 const parsed = await resolveStringOrBooleanArgsAsync(argv, rawArgsMap, { 72 '--template': Boolean, 73 '--example': Boolean, 74 '-t': '--template', 75 '-e': '--example', 76 }); 77 78 debug(`Default args:\n%O`, args); 79 debug(`Parsed:\n%O`, parsed); 80 81 const { createAsync } = await import('./createAsync'); 82 await createAsync(parsed.projectRoot, { 83 yes: !!args['--yes'], 84 template: parsed.args['--template'], 85 example: parsed.args['--example'], 86 install: !args['--no-install'], 87 }); 88 89 // Track successful event. 90 track({ 91 event: AnalyticsEventTypes.CREATE_EXPO_APP, 92 properties: { phase: AnalyticsEventPhases.SUCCESS }, 93 }); 94 // Flush all events. 95 await flushAsync(); 96 } catch (error: any) { 97 // ExitError has already been logged, all others should be logged before exiting. 98 if (!(error instanceof ExitError)) { 99 Log.exception(error); 100 } 101 102 // Track the failure. 103 track({ 104 event: AnalyticsEventTypes.CREATE_EXPO_APP, 105 properties: { phase: AnalyticsEventPhases.FAIL, message: error.cause }, 106 }); 107 108 // Flush all telemetry events. 109 await flushAsync().finally(() => { 110 // Exit with the error code or non-zero. 111 // Ensure we exit even if the telemetry fails. 112 process.exit(error.code || 1); 113 }); 114 } finally { 115 const shouldUpdate = await (await import('./utils/update-check')).default; 116 await shouldUpdate(); 117 } 118} 119 120run(); 121