18d307f52SEvan Baconimport { ExpoConfig } from '@expo/config';
28d307f52SEvan Baconimport { ModPlatform } from '@expo/config-plugins';
38d307f52SEvan Bacon
48a424bebSJames Ideimport { clearNativeFolder, promptToClearMalformedNativeProjectsAsync } from './clearNativeFolder';
58a424bebSJames Ideimport { configureProjectAsync } from './configureProjectAsync';
68a424bebSJames Ideimport { ensureConfigAsync } from './ensureConfigAsync';
78a424bebSJames Ideimport { assertPlatforms, ensureValidPlatforms, resolveTemplateOption } from './resolveOptions';
88a424bebSJames Ideimport { updateFromTemplateAsync } from './updateFromTemplate';
96caf5755SEvan Baconimport { installAsync } from '../install/installAsync';
106caf5755SEvan Baconimport { env } from '../utils/env';
112dd43328SEvan Baconimport { setNodeEnv } from '../utils/nodeEnv';
126caf5755SEvan Baconimport { clearNodeModulesAsync } from '../utils/nodeModules';
138d307f52SEvan Baconimport { logNewSection } from '../utils/ora';
148d307f52SEvan Baconimport { profile } from '../utils/profile';
158d307f52SEvan Bacon
16474a7a4bSEvan Baconconst debug = require('debug')('expo:prebuild') as typeof console.log;
17474a7a4bSEvan Bacon
188d307f52SEvan Baconexport type PrebuildResults = {
198d307f52SEvan Bacon  /** Expo config. */
208d307f52SEvan Bacon  exp: ExpoConfig;
218d307f52SEvan Bacon  /** Indicates if the process created new files. */
228d307f52SEvan Bacon  hasNewProjectFiles: boolean;
238d307f52SEvan Bacon  /** The platforms that were prebuilt. */
248d307f52SEvan Bacon  platforms: ModPlatform[];
258d307f52SEvan Bacon  /** Indicates if pod install was run. */
268d307f52SEvan Bacon  podInstall: boolean;
278d307f52SEvan Bacon  /** Indicates if node modules were installed. */
288d307f52SEvan Bacon  nodeInstall: boolean;
298d307f52SEvan Bacon};
308d307f52SEvan Bacon
318d307f52SEvan Bacon/**
328d307f52SEvan Bacon * Entry point into the prebuild process, delegates to other helpers to perform various steps.
338d307f52SEvan Bacon *
348d307f52SEvan Bacon * 0. Attempt to clean the project folders.
358d307f52SEvan Bacon * 1. Create native projects (ios, android).
368d307f52SEvan Bacon * 2. Install node modules.
378d307f52SEvan Bacon * 3. Apply config to native projects.
388d307f52SEvan Bacon * 4. Install CocoaPods.
398d307f52SEvan Bacon */
408d307f52SEvan Baconexport async function prebuildAsync(
418d307f52SEvan Bacon  projectRoot: string,
428d307f52SEvan Bacon  options: {
438d307f52SEvan Bacon    /** Should install node modules and cocoapods. */
443d6e487dSEvan Bacon    install?: boolean;
458d307f52SEvan Bacon    /** List of platforms to prebuild. */
468d307f52SEvan Bacon    platforms: ModPlatform[];
478d307f52SEvan Bacon    /** Should delete the native folders before attempting to prebuild. */
488d307f52SEvan Bacon    clean?: boolean;
498d307f52SEvan Bacon    /** URL or file path to the prebuild template. */
508d307f52SEvan Bacon    template?: string;
518d307f52SEvan Bacon    /** Name of the node package manager to install with. */
526caf5755SEvan Bacon    packageManager?: {
536caf5755SEvan Bacon      npm?: boolean;
546caf5755SEvan Bacon      yarn?: boolean;
556caf5755SEvan Bacon      pnpm?: boolean;
569b1b5ec6SEvan Bacon      bun?: boolean;
576caf5755SEvan Bacon    };
588d307f52SEvan Bacon    /** List of node modules to skip updating. */
598d307f52SEvan Bacon    skipDependencyUpdate?: string[];
608d307f52SEvan Bacon  }
6129975bfdSEvan Bacon): Promise<PrebuildResults | null> {
622dd43328SEvan Bacon  setNodeEnv('development');
636a750d06SEvan Bacon  require('@expo/env').load(projectRoot);
642dd43328SEvan Bacon
658d307f52SEvan Bacon  if (options.clean) {
66*1a3a1db5SEvan Bacon    const { maybeBailOnGitStatusAsync } = await import('../utils/git.js');
678d307f52SEvan Bacon    // Clean the project folders...
688d307f52SEvan Bacon    if (await maybeBailOnGitStatusAsync()) {
6929975bfdSEvan Bacon      return null;
708d307f52SEvan Bacon    }
718d307f52SEvan Bacon    // Clear the native folders before syncing
728d307f52SEvan Bacon    await clearNativeFolder(projectRoot, options.platforms);
738d307f52SEvan Bacon  } else {
748d307f52SEvan Bacon    // Check if the existing project folders are malformed.
758d307f52SEvan Bacon    await promptToClearMalformedNativeProjectsAsync(projectRoot, options.platforms);
768d307f52SEvan Bacon  }
778d307f52SEvan Bacon
788d307f52SEvan Bacon  // Warn if the project is attempting to prebuild an unsupported platform (iOS on Windows).
798d307f52SEvan Bacon  options.platforms = ensureValidPlatforms(options.platforms);
808d307f52SEvan Bacon  // Assert if no platforms are left over after filtering.
818d307f52SEvan Bacon  assertPlatforms(options.platforms);
828d307f52SEvan Bacon
838d307f52SEvan Bacon  // Get the Expo config, create it if missing.
848d307f52SEvan Bacon  const { exp, pkg } = await ensureConfigAsync(projectRoot, { platforms: options.platforms });
858d307f52SEvan Bacon
868d307f52SEvan Bacon  // Create native projects from template.
878d307f52SEvan Bacon  const { hasNewProjectFiles, needsPodInstall, hasNewDependencies } = await updateFromTemplateAsync(
888d307f52SEvan Bacon    projectRoot,
898d307f52SEvan Bacon    {
908d307f52SEvan Bacon      exp,
918d307f52SEvan Bacon      pkg,
928d307f52SEvan Bacon      template: options.template != null ? resolveTemplateOption(options.template) : undefined,
938d307f52SEvan Bacon      platforms: options.platforms,
948d307f52SEvan Bacon      skipDependencyUpdate: options.skipDependencyUpdate,
958d307f52SEvan Bacon    }
968d307f52SEvan Bacon  );
978d307f52SEvan Bacon
988d307f52SEvan Bacon  // Install node modules
998d307f52SEvan Bacon  if (options.install) {
1006caf5755SEvan Bacon    if (hasNewDependencies && options.packageManager?.npm) {
1016caf5755SEvan Bacon      await clearNodeModulesAsync(projectRoot);
1026caf5755SEvan Bacon    }
1036caf5755SEvan Bacon
1046caf5755SEvan Bacon    await installAsync([], {
105f479be69SEvan Bacon      npm: !!options.packageManager?.npm,
106f479be69SEvan Bacon      yarn: !!options.packageManager?.yarn,
107f479be69SEvan Bacon      pnpm: !!options.packageManager?.pnpm,
1089b1b5ec6SEvan Bacon      bun: !!options.packageManager?.bun,
1095417038cSCedric van Putten      silent: !(env.EXPO_DEBUG || env.CI),
1108d307f52SEvan Bacon    });
1118d307f52SEvan Bacon  }
1128d307f52SEvan Bacon
1138d307f52SEvan Bacon  // Apply Expo config to native projects
1148d307f52SEvan Bacon  const configSyncingStep = logNewSection('Config syncing');
1158d307f52SEvan Bacon  try {
1168d307f52SEvan Bacon    await profile(configureProjectAsync)(projectRoot, {
1178d307f52SEvan Bacon      platforms: options.platforms,
1188d307f52SEvan Bacon    });
1198d307f52SEvan Bacon    configSyncingStep.succeed('Config synced');
1208d307f52SEvan Bacon  } catch (error) {
1218d307f52SEvan Bacon    configSyncingStep.fail('Config sync failed');
1228d307f52SEvan Bacon    throw error;
1238d307f52SEvan Bacon  }
1248d307f52SEvan Bacon
1258d307f52SEvan Bacon  // Install CocoaPods
1268d307f52SEvan Bacon  let podsInstalled: boolean = false;
1278d307f52SEvan Bacon  // err towards running pod install less because it's slow and users can easily run npx pod-install afterwards.
1288d307f52SEvan Bacon  if (options.platforms.includes('ios') && options.install && needsPodInstall) {
129*1a3a1db5SEvan Bacon    const { installCocoaPodsAsync } = await import('../utils/cocoapods.js');
1308d307f52SEvan Bacon
1318d307f52SEvan Bacon    podsInstalled = await installCocoaPodsAsync(projectRoot);
1328d307f52SEvan Bacon  } else {
133474a7a4bSEvan Bacon    debug('Skipped pod install');
1348d307f52SEvan Bacon  }
1358d307f52SEvan Bacon
1368d307f52SEvan Bacon  return {
1373d6e487dSEvan Bacon    nodeInstall: !!options.install,
1388d307f52SEvan Bacon    podInstall: !podsInstalled,
1398d307f52SEvan Bacon    platforms: options.platforms,
1408d307f52SEvan Bacon    hasNewProjectFiles,
1418d307f52SEvan Bacon    exp,
1428d307f52SEvan Bacon  };
1438d307f52SEvan Bacon}
144