1import { ExpoConfig, getConfig, PackageJSONConfig } from '@expo/config';
2import { ModPlatform } from '@expo/config-plugins';
3import JsonFile, { JSONObject } from '@expo/json-file';
4import path from 'path';
5
6import * as Log from '../log';
7import { CommandError } from '../utils/errors';
8import {
9  getOrPromptForBundleIdentifier,
10  getOrPromptForPackage,
11} from '../utils/getOrPromptApplicationId';
12
13/**
14 * If an Expo config file does not exist, write a new one using the in-memory config.
15 *
16 * @param projectRoot
17 */
18export async function ensureConfigExistsAsync(projectRoot: string) {
19  try {
20    const config = getConfig(projectRoot, { skipSDKVersionRequirement: false });
21    // If no config exists in the file system then we should generate one so the process doesn't fail.
22    if (!config.dynamicConfigPath && !config.staticConfigPath) {
23      // Remove the internal object before writing.
24      delete config.exp._internal;
25
26      // Write the generated config.
27      await JsonFile.writeAsync(
28        path.join(projectRoot, 'app.json'),
29        { expo: config.exp as unknown as JSONObject },
30        { json5: false }
31      );
32    }
33  } catch (error: any) {
34    // TODO(Bacon): Currently this is already handled in the command
35    Log.log();
36    throw new CommandError(`${error.message}\n`);
37  }
38}
39
40/** Ensure config is written, prompts for application identifiers, and removes entryPoint value. */
41export async function ensureConfigAsync(
42  projectRoot: string,
43  {
44    platforms,
45  }: {
46    platforms: ModPlatform[];
47  }
48): Promise<{ exp: ExpoConfig; pkg: PackageJSONConfig }> {
49  await ensureConfigExistsAsync(projectRoot);
50
51  // Prompt for the Android package first because it's more strict than the bundle identifier
52  // this means you'll have a better chance at matching the bundle identifier with the package name.
53  if (platforms.includes('android')) {
54    await getOrPromptForPackage(projectRoot);
55  }
56
57  if (platforms.includes('ios')) {
58    await getOrPromptForBundleIdentifier(projectRoot);
59  }
60
61  // We need the SDK version to proceed
62  const { exp, pkg } = getConfig(projectRoot);
63
64  // TODO(EvanBacon): Remove the requirement for this once we have a
65  // custom bundle script that respects Expo entry point resolution.
66  if (exp.entryPoint) {
67    delete exp.entryPoint;
68    Log.log(`\u203A expo.entryPoint is not needed and has been removed.`);
69  }
70
71  // Read config again because prompting for bundle id or package name may have mutated the results.
72  return { exp, pkg };
73}
74