1eeffdb10STomasz Sapetaimport spawnAsync from '@expo/spawn-async';
2eeffdb10STomasz Sapetaimport chalk from 'chalk';
3eeffdb10STomasz Sapetaimport fs from 'fs-extra';
4eeffdb10STomasz Sapetaimport inquirer from 'inquirer';
5eeffdb10STomasz Sapetaimport path from 'path';
6eeffdb10STomasz Sapetaimport readline from 'readline';
7eeffdb10STomasz Sapeta
8eeffdb10STomasz Sapetaimport * as Directories from '../Directories';
9eeffdb10STomasz Sapetaimport * as Packages from '../Packages';
10eeffdb10STomasz Sapetaimport * as ProjectVersions from '../ProjectVersions';
11eeffdb10STomasz Sapeta
12eeffdb10STomasz Sapetatype ActionOptions = {
13eeffdb10STomasz Sapeta  sdkVersion: string;
14eeffdb10STomasz Sapeta  packages?: string;
15eeffdb10STomasz Sapeta};
16eeffdb10STomasz Sapeta
17eeffdb10STomasz Sapetatype Package = {
18eeffdb10STomasz Sapeta  name: string;
19eeffdb10STomasz Sapeta  sourceDir: string;
20eeffdb10STomasz Sapeta  buildDirRelative: string;
21eeffdb10STomasz Sapeta};
22eeffdb10STomasz Sapeta
23eeffdb10STomasz Sapeta// There are a few packages that we want to exclude from shell app builds; they don't follow any
24eeffdb10STomasz Sapeta// easy pattern so we just keep track of them manually here.
25eeffdb10STomasz Sapetaexport const EXCLUDED_PACKAGE_SLUGS = [
2607f89462SEric Samelson  'expo-dev-client',
2707f89462SEric Samelson  'expo-dev-launcher',
28eeffdb10STomasz Sapeta  'expo-dev-menu',
29eeffdb10STomasz Sapeta  'expo-dev-menu-interface',
30eeffdb10STomasz Sapeta  'expo-module-template',
31e229380eSŁukasz Kosmaty  'expo-modules-test-core',
320b794d50SŁukasz Kosmaty  'unimodules-core',
330b794d50SŁukasz Kosmaty  'unimodules-react-native-adapter',
34eeffdb10STomasz Sapeta];
35eeffdb10STomasz Sapeta
36eeffdb10STomasz Sapetaconst EXPO_ROOT_DIR = Directories.getExpoRepositoryRootDir();
37eeffdb10STomasz Sapetaconst ANDROID_DIR = Directories.getAndroidDir();
38eeffdb10STomasz Sapeta
39eeffdb10STomasz Sapetaconst REACT_ANDROID_PKG = {
40eeffdb10STomasz Sapeta  name: 'ReactAndroid',
41eeffdb10STomasz Sapeta  sourceDir: path.join(ANDROID_DIR, 'ReactAndroid'),
42eeffdb10STomasz Sapeta  buildDirRelative: path.join('com', 'facebook', 'react'),
43eeffdb10STomasz Sapeta};
44eeffdb10STomasz Sapetaconst EXPOVIEW_PKG = {
45eeffdb10STomasz Sapeta  name: 'expoview',
46eeffdb10STomasz Sapeta  sourceDir: path.join(ANDROID_DIR, 'expoview'),
47eeffdb10STomasz Sapeta  buildDirRelative: path.join('host', 'exp', 'exponent', 'expoview'),
48eeffdb10STomasz Sapeta};
49eeffdb10STomasz Sapeta
50eeffdb10STomasz Sapetaasync function _findUnimodules(pkgDir: string): Promise<Package[]> {
51eeffdb10STomasz Sapeta  const unimodules: Package[] = [];
52eeffdb10STomasz Sapeta
53eeffdb10STomasz Sapeta  const packages = await Packages.getListOfPackagesAsync();
54eeffdb10STomasz Sapeta  for (const pkg of packages) {
55eeffdb10STomasz Sapeta    if (!pkg.isSupportedOnPlatform('android') || !pkg.androidPackageName) continue;
56eeffdb10STomasz Sapeta    unimodules.push({
57eeffdb10STomasz Sapeta      name: pkg.packageSlug,
58eeffdb10STomasz Sapeta      sourceDir: path.join(pkg.path, pkg.androidSubdirectory),
59eeffdb10STomasz Sapeta      buildDirRelative: `${pkg.androidPackageName.replace(/\./g, '/')}/${pkg.packageSlug}`,
60eeffdb10STomasz Sapeta    });
61eeffdb10STomasz Sapeta  }
62eeffdb10STomasz Sapeta
63eeffdb10STomasz Sapeta  return unimodules;
64eeffdb10STomasz Sapeta}
65eeffdb10STomasz Sapeta
66eeffdb10STomasz Sapetaasync function _isPackageUpToDate(sourceDir: string, buildDir: string): Promise<boolean> {
67eeffdb10STomasz Sapeta  try {
68eeffdb10STomasz Sapeta    const sourceCommits = await _gitLogAsync(sourceDir);
69eeffdb10STomasz Sapeta    const buildCommits = await _gitLogAsync(buildDir);
70eeffdb10STomasz Sapeta
71eeffdb10STomasz Sapeta    const latestSourceCommitSha = sourceCommits.lines[0].split(' ')[0];
72eeffdb10STomasz Sapeta    const latestBuildCommitSha = buildCommits.lines[0].split(' ')[0];
73eeffdb10STomasz Sapeta
74eeffdb10STomasz Sapeta    // throws if source commit is not an ancestor of build commit
75eeffdb10STomasz Sapeta    await spawnAsync(
76eeffdb10STomasz Sapeta      'git',
77eeffdb10STomasz Sapeta      ['merge-base', '--is-ancestor', latestSourceCommitSha, latestBuildCommitSha],
78eeffdb10STomasz Sapeta      {
79eeffdb10STomasz Sapeta        cwd: EXPO_ROOT_DIR,
80eeffdb10STomasz Sapeta      }
81eeffdb10STomasz Sapeta    );
82eeffdb10STomasz Sapeta    return true;
83*a272999eSBartosz Kaszubowski  } catch {
84eeffdb10STomasz Sapeta    return false;
85eeffdb10STomasz Sapeta  }
86eeffdb10STomasz Sapeta}
87eeffdb10STomasz Sapeta
88eeffdb10STomasz Sapetaasync function _gitLogAsync(path: string): Promise<{ lines: string[] }> {
89eeffdb10STomasz Sapeta  const child = await spawnAsync('git', ['log', `--pretty=oneline`, '--', path], {
90eeffdb10STomasz Sapeta    stdio: 'pipe',
91eeffdb10STomasz Sapeta    cwd: EXPO_ROOT_DIR,
92eeffdb10STomasz Sapeta  });
93eeffdb10STomasz Sapeta
94eeffdb10STomasz Sapeta  return {
95eeffdb10STomasz Sapeta    lines: child.stdout
96eeffdb10STomasz Sapeta      .trim()
97eeffdb10STomasz Sapeta      .split(/\r?\n/g)
98eeffdb10STomasz Sapeta      .filter((a) => a),
99eeffdb10STomasz Sapeta  };
100eeffdb10STomasz Sapeta}
101eeffdb10STomasz Sapeta
102eeffdb10STomasz Sapetaasync function _getSuggestedPackagesToBuild(packages: Package[]): Promise<string[]> {
103*a272999eSBartosz Kaszubowski  const packagesToBuild: string[] = [];
104eeffdb10STomasz Sapeta  for (const pkg of packages) {
105eeffdb10STomasz Sapeta    const isUpToDate = await _isPackageUpToDate(
106eeffdb10STomasz Sapeta      pkg.sourceDir,
107eeffdb10STomasz Sapeta      path.join(EXPO_ROOT_DIR, 'android', 'maven', pkg.buildDirRelative)
108eeffdb10STomasz Sapeta    );
109eeffdb10STomasz Sapeta    if (!isUpToDate) {
110eeffdb10STomasz Sapeta      packagesToBuild.push(pkg.name);
111eeffdb10STomasz Sapeta    }
112eeffdb10STomasz Sapeta  }
113eeffdb10STomasz Sapeta  return packagesToBuild;
114eeffdb10STomasz Sapeta}
115eeffdb10STomasz Sapeta
116eeffdb10STomasz Sapetaasync function _regexFileAsync(
117eeffdb10STomasz Sapeta  filename: string,
118eeffdb10STomasz Sapeta  regex: RegExp | string,
119eeffdb10STomasz Sapeta  replace: string
120eeffdb10STomasz Sapeta): Promise<void> {
121*a272999eSBartosz Kaszubowski  const file = await fs.readFile(filename);
122*a272999eSBartosz Kaszubowski  const fileString = file.toString();
123eeffdb10STomasz Sapeta  await fs.writeFile(filename, fileString.replace(regex, replace));
124eeffdb10STomasz Sapeta}
125eeffdb10STomasz Sapeta
126*a272999eSBartosz Kaszubowskiconst savedFiles = {};
127eeffdb10STomasz Sapetaasync function _stashFilesAsync(filenames: string[]): Promise<void> {
128eeffdb10STomasz Sapeta  for (const filename of filenames) {
129*a272999eSBartosz Kaszubowski    const file = await fs.readFile(filename);
130eeffdb10STomasz Sapeta    savedFiles[filename] = file.toString();
131eeffdb10STomasz Sapeta  }
132eeffdb10STomasz Sapeta}
133eeffdb10STomasz Sapeta
134eeffdb10STomasz Sapetaasync function _restoreFilesAsync(): Promise<void> {
135eeffdb10STomasz Sapeta  for (const filename in savedFiles) {
136eeffdb10STomasz Sapeta    await fs.writeFile(filename, savedFiles[filename]);
137eeffdb10STomasz Sapeta    delete savedFiles[filename];
138eeffdb10STomasz Sapeta  }
139eeffdb10STomasz Sapeta}
140eeffdb10STomasz Sapeta
141eeffdb10STomasz Sapetaasync function _commentWhenDistributing(filenames: string[]): Promise<void> {
142eeffdb10STomasz Sapeta  for (const filename of filenames) {
143eeffdb10STomasz Sapeta    await _regexFileAsync(
144eeffdb10STomasz Sapeta      filename,
145eeffdb10STomasz Sapeta      /\/\/ WHEN_DISTRIBUTING_REMOVE_FROM_HERE/g,
146eeffdb10STomasz Sapeta      '/* WHEN_DISTRIBUTING_REMOVE_FROM_HERE'
147eeffdb10STomasz Sapeta    );
148eeffdb10STomasz Sapeta    await _regexFileAsync(
149eeffdb10STomasz Sapeta      filename,
150eeffdb10STomasz Sapeta      /\/\ WHEN_DISTRIBUTING_REMOVE_TO_HERE/g,
151eeffdb10STomasz Sapeta      'WHEN_DISTRIBUTING_REMOVE_TO_HERE */'
152eeffdb10STomasz Sapeta    );
153eeffdb10STomasz Sapeta  }
154eeffdb10STomasz Sapeta}
155eeffdb10STomasz Sapeta
156eeffdb10STomasz Sapetaasync function _uncommentWhenDistributing(filenames: string[]): Promise<void> {
157eeffdb10STomasz Sapeta  for (const filename of filenames) {
158eeffdb10STomasz Sapeta    await _regexFileAsync(filename, '/* UNCOMMENT WHEN DISTRIBUTING', '');
159eeffdb10STomasz Sapeta    await _regexFileAsync(filename, 'END UNCOMMENT WHEN DISTRIBUTING */', '');
160eeffdb10STomasz Sapeta  }
161eeffdb10STomasz Sapeta}
162eeffdb10STomasz Sapeta
163eeffdb10STomasz Sapetaasync function _updateExpoViewAsync(packages: Package[], sdkVersion: string): Promise<number> {
164*a272999eSBartosz Kaszubowski  const appBuildGradle = path.join(ANDROID_DIR, 'app', 'build.gradle');
165*a272999eSBartosz Kaszubowski  const rootBuildGradle = path.join(ANDROID_DIR, 'build.gradle');
166*a272999eSBartosz Kaszubowski  const expoViewBuildGradle = path.join(ANDROID_DIR, 'expoview', 'build.gradle');
167eeffdb10STomasz Sapeta  const settingsGradle = path.join(ANDROID_DIR, 'settings.gradle');
168eeffdb10STomasz Sapeta  const constantsJava = path.join(
169eeffdb10STomasz Sapeta    ANDROID_DIR,
170eeffdb10STomasz Sapeta    'expoview/src/main/java/host/exp/exponent/Constants.java'
171eeffdb10STomasz Sapeta  );
172eeffdb10STomasz Sapeta  const multipleVersionReactNativeActivity = path.join(
173eeffdb10STomasz Sapeta    ANDROID_DIR,
174a2ffa225SBartłomiej Klocek    'expoview/src/versioned/java/host/exp/exponent/experience/MultipleVersionReactNativeActivity.java'
175eeffdb10STomasz Sapeta  );
176eeffdb10STomasz Sapeta
177eeffdb10STomasz Sapeta  // Modify permanently
178eeffdb10STomasz Sapeta  await _regexFileAsync(expoViewBuildGradle, /version = '[\d.]+'/, `version = '${sdkVersion}'`);
179eeffdb10STomasz Sapeta  await _regexFileAsync(
180eeffdb10STomasz Sapeta    expoViewBuildGradle,
181eeffdb10STomasz Sapeta    /api 'com.facebook.react:react-native:[\d.]+'/,
182eeffdb10STomasz Sapeta    `api 'com.facebook.react:react-native:${sdkVersion}'`
183eeffdb10STomasz Sapeta  );
184eeffdb10STomasz Sapeta  await _regexFileAsync(
1852ed31047SKudo Chien    path.join(ANDROID_DIR, 'ReactAndroid', 'build.gradle'),
186eeffdb10STomasz Sapeta    /version = '[\d.]+'/,
187eeffdb10STomasz Sapeta    `version = '${sdkVersion}'`
188eeffdb10STomasz Sapeta  );
189eeffdb10STomasz Sapeta  await _regexFileAsync(
190eeffdb10STomasz Sapeta    path.join(ANDROID_DIR, 'app', 'build.gradle'),
191eeffdb10STomasz Sapeta    /host.exp.exponent:expoview:[\d.]+/,
192eeffdb10STomasz Sapeta    `host.exp.exponent:expoview:${sdkVersion}`
193eeffdb10STomasz Sapeta  );
194eeffdb10STomasz Sapeta
195eeffdb10STomasz Sapeta  await _stashFilesAsync([
196eeffdb10STomasz Sapeta    appBuildGradle,
197eeffdb10STomasz Sapeta    rootBuildGradle,
198eeffdb10STomasz Sapeta    expoViewBuildGradle,
199eeffdb10STomasz Sapeta    multipleVersionReactNativeActivity,
200eeffdb10STomasz Sapeta    constantsJava,
201eeffdb10STomasz Sapeta    settingsGradle,
202eeffdb10STomasz Sapeta  ]);
203eeffdb10STomasz Sapeta
204eeffdb10STomasz Sapeta  // Modify temporarily
205eeffdb10STomasz Sapeta  await _regexFileAsync(
206eeffdb10STomasz Sapeta    constantsJava,
207eeffdb10STomasz Sapeta    /TEMPORARY_ABI_VERSION\s*=\s*null/,
208eeffdb10STomasz Sapeta    `TEMPORARY_ABI_VERSION = "${sdkVersion}"`
209eeffdb10STomasz Sapeta  );
210eeffdb10STomasz Sapeta  await _uncommentWhenDistributing([appBuildGradle, expoViewBuildGradle]);
211eeffdb10STomasz Sapeta  await _commentWhenDistributing([
212eeffdb10STomasz Sapeta    constantsJava,
213eeffdb10STomasz Sapeta    rootBuildGradle,
214eeffdb10STomasz Sapeta    expoViewBuildGradle,
215eeffdb10STomasz Sapeta    multipleVersionReactNativeActivity,
216eeffdb10STomasz Sapeta  ]);
217eeffdb10STomasz Sapeta
218eeffdb10STomasz Sapeta  // Clear maven local so that we don't end up with multiple versions
219eeffdb10STomasz Sapeta  console.log(' ❌  Clearing old package versions...');
220eeffdb10STomasz Sapeta
221eeffdb10STomasz Sapeta  for (const pkg of packages) {
222eeffdb10STomasz Sapeta    await fs.remove(path.join(process.env.HOME!, '.m2', 'repository', pkg.buildDirRelative));
223eeffdb10STomasz Sapeta    await fs.remove(path.join(ANDROID_DIR, 'maven', pkg.buildDirRelative));
224eeffdb10STomasz Sapeta    await fs.remove(path.join(pkg.sourceDir, 'build'));
225eeffdb10STomasz Sapeta  }
226eeffdb10STomasz Sapeta
227eeffdb10STomasz Sapeta  // hacky workaround for weird issue where some packages need to be built twice after cleaning
228eeffdb10STomasz Sapeta  // in order to have .so libs included in the aar
229b06291d9Sjkh  const reactAndroidIndex = packages.findIndex((pkg) => pkg.name === REACT_ANDROID_PKG.name);
230eeffdb10STomasz Sapeta  if (reactAndroidIndex > -1) {
231eeffdb10STomasz Sapeta    packages.splice(reactAndroidIndex, 0, REACT_ANDROID_PKG);
232eeffdb10STomasz Sapeta  }
233b06291d9Sjkh  const expoviewIndex = packages.findIndex((pkg) => pkg.name === EXPOVIEW_PKG.name);
234eeffdb10STomasz Sapeta  if (expoviewIndex > -1) {
235eeffdb10STomasz Sapeta    packages.splice(expoviewIndex, 0, EXPOVIEW_PKG);
236eeffdb10STomasz Sapeta  }
237eeffdb10STomasz Sapeta
238*a272999eSBartosz Kaszubowski  const failedPackages: string[] = [];
239eeffdb10STomasz Sapeta  for (const pkg of packages) {
240eeffdb10STomasz Sapeta    process.stdout.write(` ��   Building ${pkg.name}...`);
241eeffdb10STomasz Sapeta    try {
2422ed31047SKudo Chien      await spawnAsync('./gradlew', [`:${pkg.name}:publish`], {
243eeffdb10STomasz Sapeta        cwd: ANDROID_DIR,
244eeffdb10STomasz Sapeta      });
245eeffdb10STomasz Sapeta      readline.clearLine(process.stdout, 0);
246eeffdb10STomasz Sapeta      readline.cursorTo(process.stdout, 0);
247eeffdb10STomasz Sapeta      process.stdout.write(` ✅  Finished building ${pkg.name}\n`);
248eeffdb10STomasz Sapeta    } catch (e) {
249eeffdb10STomasz Sapeta      if (
250eeffdb10STomasz Sapeta        e.status === 130 ||
251eeffdb10STomasz Sapeta        e.signal === 'SIGINT' ||
252eeffdb10STomasz Sapeta        e.status === 137 ||
253eeffdb10STomasz Sapeta        e.signal === 'SIGKILL' ||
254eeffdb10STomasz Sapeta        e.status === 143 ||
255eeffdb10STomasz Sapeta        e.signal === 'SIGTERM'
256eeffdb10STomasz Sapeta      ) {
257eeffdb10STomasz Sapeta        throw e;
258eeffdb10STomasz Sapeta      } else {
259eeffdb10STomasz Sapeta        failedPackages.push(pkg.name);
260eeffdb10STomasz Sapeta        readline.clearLine(process.stdout, 0);
261eeffdb10STomasz Sapeta        readline.cursorTo(process.stdout, 0);
262eeffdb10STomasz Sapeta        process.stdout.write(` ❌  Failed to build ${pkg.name}:\n`);
263eeffdb10STomasz Sapeta        console.error(chalk.red(e.message));
264eeffdb10STomasz Sapeta        console.error(chalk.red(e.stderr));
265eeffdb10STomasz Sapeta      }
266eeffdb10STomasz Sapeta    }
267eeffdb10STomasz Sapeta  }
268eeffdb10STomasz Sapeta
269eeffdb10STomasz Sapeta  await _restoreFilesAsync();
270eeffdb10STomasz Sapeta
271eeffdb10STomasz Sapeta  console.log(' ��  Copying newly built packages...');
272eeffdb10STomasz Sapeta
273eeffdb10STomasz Sapeta  await fs.mkdirs(path.join(ANDROID_DIR, 'maven/com/facebook'));
274eeffdb10STomasz Sapeta  await fs.mkdirs(path.join(ANDROID_DIR, 'maven/host/exp/exponent'));
275eeffdb10STomasz Sapeta  await fs.mkdirs(path.join(ANDROID_DIR, 'maven/org/unimodules'));
276eeffdb10STomasz Sapeta
277eeffdb10STomasz Sapeta  for (const pkg of packages) {
278eeffdb10STomasz Sapeta    if (failedPackages.includes(pkg.name)) {
279eeffdb10STomasz Sapeta      continue;
280eeffdb10STomasz Sapeta    }
281eeffdb10STomasz Sapeta    await fs.copy(
282eeffdb10STomasz Sapeta      path.join(process.env.HOME!, '.m2', 'repository', pkg.buildDirRelative),
283eeffdb10STomasz Sapeta      path.join(ANDROID_DIR, 'maven', pkg.buildDirRelative)
284eeffdb10STomasz Sapeta    );
285eeffdb10STomasz Sapeta  }
286eeffdb10STomasz Sapeta
287eeffdb10STomasz Sapeta  if (failedPackages.length) {
288eeffdb10STomasz Sapeta    console.log(' ❌  The following packages failed to build:');
289eeffdb10STomasz Sapeta    console.log(failedPackages);
290eeffdb10STomasz Sapeta    console.log(
291eeffdb10STomasz Sapeta      `You will need to fix the compilation errors show in the logs above and then run \`et abp -s ${sdkVersion} -p ${failedPackages.join(
292eeffdb10STomasz Sapeta        ','
293eeffdb10STomasz Sapeta      )}\``
294eeffdb10STomasz Sapeta    );
295eeffdb10STomasz Sapeta  }
296eeffdb10STomasz Sapeta
297eeffdb10STomasz Sapeta  return failedPackages.length;
298eeffdb10STomasz Sapeta}
299eeffdb10STomasz Sapeta
300eeffdb10STomasz Sapetaasync function action(options: ActionOptions) {
301eeffdb10STomasz Sapeta  process.on('SIGINT', _exitHandler);
302eeffdb10STomasz Sapeta  process.on('SIGTERM', _exitHandler);
303eeffdb10STomasz Sapeta
304eeffdb10STomasz Sapeta  const detachableUniversalModules = (
305eeffdb10STomasz Sapeta    await _findUnimodules(path.join(EXPO_ROOT_DIR, 'packages'))
306eeffdb10STomasz Sapeta  ).filter((unimodule) => !EXCLUDED_PACKAGE_SLUGS.includes(unimodule.name));
307eeffdb10STomasz Sapeta
308eeffdb10STomasz Sapeta  // packages must stay in this order --
309eeffdb10STomasz Sapeta  // ReactAndroid MUST be first and expoview MUST be last
310eeffdb10STomasz Sapeta  const packages: Package[] = [REACT_ANDROID_PKG, ...detachableUniversalModules, EXPOVIEW_PKG];
311eeffdb10STomasz Sapeta  let packagesToBuild: string[] = [];
312eeffdb10STomasz Sapeta
313eeffdb10STomasz Sapeta  const expoviewBuildGradle = await fs.readFile(path.join(ANDROID_DIR, 'expoview', 'build.gradle'));
314eeffdb10STomasz Sapeta  const match = expoviewBuildGradle
315eeffdb10STomasz Sapeta    .toString()
316eeffdb10STomasz Sapeta    .match(/api 'com.facebook.react:react-native:([\d.]+)'/);
317eeffdb10STomasz Sapeta  if (!match || !match[1]) {
318eeffdb10STomasz Sapeta    throw new Error(
319eeffdb10STomasz Sapeta      'Could not find SDK version in android/expoview/build.gradle: unexpected format'
320eeffdb10STomasz Sapeta    );
321eeffdb10STomasz Sapeta  }
322eeffdb10STomasz Sapeta
323eeffdb10STomasz Sapeta  if (match[1] !== options.sdkVersion) {
324eeffdb10STomasz Sapeta    console.log(
325eeffdb10STomasz Sapeta      " ��  It looks like you're adding a new SDK version. Ignoring the `--packages` option and rebuilding all packages..."
326eeffdb10STomasz Sapeta    );
327eeffdb10STomasz Sapeta    packagesToBuild = packages.map((pkg) => pkg.name);
328eeffdb10STomasz Sapeta  } else if (options.packages) {
329eeffdb10STomasz Sapeta    if (options.packages === 'all') {
330eeffdb10STomasz Sapeta      packagesToBuild = packages.map((pkg) => pkg.name);
331eeffdb10STomasz Sapeta    } else if (options.packages === 'suggested') {
332eeffdb10STomasz Sapeta      console.log(' ��  Gathering data about packages...');
333eeffdb10STomasz Sapeta      packagesToBuild = await _getSuggestedPackagesToBuild(packages);
334eeffdb10STomasz Sapeta    } else {
335eeffdb10STomasz Sapeta      const packageNames = options.packages.split(',');
336eeffdb10STomasz Sapeta      packagesToBuild = packages
337eeffdb10STomasz Sapeta        .map((pkg) => pkg.name)
338eeffdb10STomasz Sapeta        .filter((pkgName) => packageNames.includes(pkgName));
339eeffdb10STomasz Sapeta    }
340eeffdb10STomasz Sapeta    console.log(' ��   Rebuilding the following packages:');
341eeffdb10STomasz Sapeta    console.log(packagesToBuild);
342eeffdb10STomasz Sapeta  } else {
343eeffdb10STomasz Sapeta    // gather suggested package data and then show prompts
344eeffdb10STomasz Sapeta    console.log(' ��  Gathering data...');
345eeffdb10STomasz Sapeta
346eeffdb10STomasz Sapeta    packagesToBuild = await _getSuggestedPackagesToBuild(packages);
347eeffdb10STomasz Sapeta
348eeffdb10STomasz Sapeta    console.log(' ��️   It appears that the following packages need to be rebuilt:');
349eeffdb10STomasz Sapeta    console.log(packagesToBuild);
350eeffdb10STomasz Sapeta
351eeffdb10STomasz Sapeta    const { option } = await inquirer.prompt<{ option: string }>([
352eeffdb10STomasz Sapeta      {
353eeffdb10STomasz Sapeta        type: 'list',
354eeffdb10STomasz Sapeta        name: 'option',
355eeffdb10STomasz Sapeta        message: 'What would you like to do?',
356eeffdb10STomasz Sapeta        choices: [
357eeffdb10STomasz Sapeta          { value: 'suggested', name: 'Build the suggested packages only' },
358eeffdb10STomasz Sapeta          { value: 'all', name: 'Build all packages' },
359eeffdb10STomasz Sapeta          { value: 'choose', name: 'Choose packages manually' },
360eeffdb10STomasz Sapeta        ],
361eeffdb10STomasz Sapeta      },
362eeffdb10STomasz Sapeta    ]);
363eeffdb10STomasz Sapeta
364eeffdb10STomasz Sapeta    if (option === 'all') {
365eeffdb10STomasz Sapeta      packagesToBuild = packages.map((pkg) => pkg.name);
366eeffdb10STomasz Sapeta    } else if (option === 'choose') {
367eeffdb10STomasz Sapeta      const result = await inquirer.prompt<{ packagesToBuild: string[] }>([
368eeffdb10STomasz Sapeta        {
369eeffdb10STomasz Sapeta          type: 'checkbox',
370eeffdb10STomasz Sapeta          name: 'packagesToBuild',
371b06291d9Sjkh          message: 'Choose which packages to build\n  ● selected ○ unselected\n',
372eeffdb10STomasz Sapeta          choices: packages.map((pkg) => pkg.name),
373eeffdb10STomasz Sapeta          default: packagesToBuild,
374eeffdb10STomasz Sapeta          pageSize: Math.min(packages.length, (process.stdout.rows || 100) - 2),
375eeffdb10STomasz Sapeta        },
376eeffdb10STomasz Sapeta      ]);
377eeffdb10STomasz Sapeta      packagesToBuild = result.packagesToBuild;
378eeffdb10STomasz Sapeta    }
379eeffdb10STomasz Sapeta  }
380eeffdb10STomasz Sapeta
381eeffdb10STomasz Sapeta  try {
382eeffdb10STomasz Sapeta    const failedPackagesCount = await _updateExpoViewAsync(
383eeffdb10STomasz Sapeta      packages.filter((pkg) => packagesToBuild.includes(pkg.name)),
384eeffdb10STomasz Sapeta      options.sdkVersion
385eeffdb10STomasz Sapeta    );
386eeffdb10STomasz Sapeta    if (failedPackagesCount > 0) {
387eeffdb10STomasz Sapeta      process.exitCode = 1;
388eeffdb10STomasz Sapeta    }
389eeffdb10STomasz Sapeta  } catch (e) {
390eeffdb10STomasz Sapeta    await _exitHandler();
391eeffdb10STomasz Sapeta    throw e;
392eeffdb10STomasz Sapeta  }
393eeffdb10STomasz Sapeta}
394eeffdb10STomasz Sapeta
395eeffdb10STomasz Sapetaasync function _exitHandler(): Promise<void> {
396eeffdb10STomasz Sapeta  if (Object.keys(savedFiles).length) {
397eeffdb10STomasz Sapeta    console.log('Exited early, cleaning up...');
398eeffdb10STomasz Sapeta    await _restoreFilesAsync();
399eeffdb10STomasz Sapeta  }
400eeffdb10STomasz Sapeta}
401eeffdb10STomasz Sapeta
402eeffdb10STomasz Sapetaexport default (program: any) => {
403eeffdb10STomasz Sapeta  program
404eeffdb10STomasz Sapeta    .command('android-build-packages')
405eeffdb10STomasz Sapeta    .alias('abp')
406eeffdb10STomasz Sapeta    .description('Builds all Android AAR packages for Turtle')
407eeffdb10STomasz Sapeta    .option('-s, --sdkVersion [string]', '[optional] SDK version')
408eeffdb10STomasz Sapeta    .option(
409eeffdb10STomasz Sapeta      '-p, --packages [string]',
410eeffdb10STomasz Sapeta      '[optional] packages to build. May be `all`, `suggested`, or a comma-separate list of package names.'
411eeffdb10STomasz Sapeta    )
412eeffdb10STomasz Sapeta    .asyncAction(async (options: Partial<ActionOptions>) => {
413eeffdb10STomasz Sapeta      const sdkVersion =
414eeffdb10STomasz Sapeta        options.sdkVersion ?? (await ProjectVersions.getNewestSDKVersionAsync('android'));
415eeffdb10STomasz Sapeta
416eeffdb10STomasz Sapeta      if (!sdkVersion) {
417eeffdb10STomasz Sapeta        throw new Error('Could not infer SDK version, please run with `--sdkVersion SDK_VERSION`');
418eeffdb10STomasz Sapeta      }
419eeffdb10STomasz Sapeta
420eeffdb10STomasz Sapeta      await action({ ...options, sdkVersion });
421eeffdb10STomasz Sapeta    });
422eeffdb10STomasz Sapeta};
423