xref: /expo/packages/create-expo/src/cli.ts (revision b7d15820)
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