xref: /expo/tools/src/commands/AddSDKVersion.ts (revision 1833af43)
1import { Command } from '@expo/commander';
2import chalk from 'chalk';
3import inquirer from 'inquirer';
4import semver from 'semver';
5
6import { getListOfPackagesAsync } from '../Packages';
7import { Platform, getNextSDKVersionAsync, resolveSDKVersionAsync } from '../ProjectVersions';
8import * as AndroidVersioning from '../versioning/android';
9import * as IosVersioning from '../versioning/ios';
10
11type ActionOptions = {
12  platform: Platform;
13  sdkVersion?: string;
14  filenames?: string;
15  vendored: string[];
16  reinstall?: boolean;
17  preventReinstall?: boolean;
18  packages?: string[];
19};
20
21async function getNextOrAskForSDKVersionAsync(platform: Platform): Promise<string | undefined> {
22  const defaultSdkVersion = await getNextSDKVersionAsync(platform);
23
24  if (defaultSdkVersion && process.env.CI) {
25    console.log(
26      `${chalk.red('`--sdkVersion`')} not provided - defaulting to ${chalk.cyan(defaultSdkVersion)}`
27    );
28    return defaultSdkVersion;
29  }
30
31  const { sdkVersion } = await inquirer.prompt<{ sdkVersion: string }>([
32    {
33      type: 'input',
34      name: 'sdkVersion',
35      message: 'What is the SDK version that you want to add?',
36      default: defaultSdkVersion,
37      validate(value) {
38        if (!semver.valid(value)) {
39          return `Invalid version: ${chalk.cyan(value)}`;
40        }
41        return true;
42      },
43    },
44  ]);
45  return sdkVersion;
46}
47
48async function action(options: ActionOptions) {
49  if (!options.platform) {
50    throw new Error('Run with `--platform <ios | android>`.');
51  }
52
53  const sdkVersion =
54    (options.sdkVersion && (await resolveSDKVersionAsync(options.sdkVersion, options.platform))) ||
55    (await getNextOrAskForSDKVersionAsync(options.platform));
56
57  if (!sdkVersion) {
58    throw new Error('Next SDK version not found. Try to run with `--sdkVersion <SDK version>`.');
59  }
60  const sdkNumber = semver.major(sdkVersion);
61  const packages = (await getListOfPackagesAsync()).filter((pkg) =>
62    pkg.isVersionableOnPlatform(options.platform)
63  );
64
65  switch (options.platform) {
66    case 'ios':
67      if (options.vendored.length > 0) {
68        await IosVersioning.versionVendoredModulesAsync(sdkNumber, options.vendored);
69      } else if (options.filenames) {
70        await IosVersioning.versionReactNativeIOSFilesAsync(options.filenames, sdkVersion);
71      } else if (options.packages) {
72        await IosVersioning.versionExpoModulesAsync(
73          sdkNumber,
74          packages.filter((pkg) => options.packages?.includes(pkg.packageName))
75        );
76      } else {
77        await IosVersioning.versionVendoredModulesAsync(sdkNumber, null);
78        await IosVersioning.addVersionAsync(sdkVersion, packages);
79      }
80      await IosVersioning.reinstallPodsAsync(options.reinstall, options.preventReinstall);
81      return;
82    case 'android':
83      await AndroidVersioning.addVersionAsync(sdkVersion);
84      await AndroidVersioning.versionVendoredModulesAsync(sdkNumber, null);
85      return;
86    default:
87      throw new Error(`Platform '${options.platform}' is not supported.`);
88  }
89}
90
91export default (program: Command) => {
92  program
93    .command('add-sdk-version')
94    .alias('add-sdk')
95    .description('Versions code for the new SDK version.')
96    .usage(
97      `
98
99To version code for the new SDK on iOS, run:
100${chalk.gray('>')} ${chalk.italic.cyan('et add-sdk-version --platform ios')}
101
102To backport changes made in unversioned code into already versioned SDK, run:
103${chalk.gray('>')} ${chalk.italic.cyan(
104        'et add-sdk-version --platform ios --sdkVersion XX.0.0 --filenames */some/glob/expression/**'
105      )}`
106    )
107    .option(
108      '-p, --platform <string>',
109      `Specifies a platform for which the SDK code should be generated. Supported platforms: ${chalk.cyan(
110        'ios'
111      )}.`
112    )
113    .option(
114      '-s, --sdkVersion [string]',
115      'SDK version to add. Can be a full version name, major number or `next` tag. Defaults to `next` on the CI.'
116    )
117    .option(
118      '-f, --filenames [string]',
119      'Glob pattern of file paths to version. Useful when you want to backport unversioned code into already versioned SDK. Optional. When provided, option `--sdkVersion` is required.'
120    )
121    .option(
122      '-v, --vendored <string>',
123      'Name of the vendored module to (re)version. iOS only.',
124      (value, previous) => (previous ?? []).concat(value),
125      []
126    )
127    .option(
128      '-r, --reinstall',
129      'Whether to force reinstalling pods after generating a new version. iOS only.'
130    )
131    .option(
132      '--prevent-reinstall',
133      'Whether to force not reinstalling pods after generating a new version. iOS only.'
134    )
135    .option('-x, --packages <string...>', 'Name of the expo package to version.')
136    .asyncAction(action);
137};
138