xref: /expo/tools/src/commands/AddSDKVersion.ts (revision 0dfbbd2a)
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      return AndroidVersioning.addVersionAsync(sdkVersion);
84    default:
85      throw new Error(`Platform '${options.platform}' is not supported.`);
86  }
87}
88
89export default (program: Command) => {
90  program
91    .command('add-sdk-version')
92    .alias('add-sdk')
93    .description('Versions code for the new SDK version.')
94    .usage(
95      `
96
97To version code for the new SDK on iOS, run:
98${chalk.gray('>')} ${chalk.italic.cyan('et add-sdk-version --platform ios')}
99
100To backport changes made in unversioned code into already versioned SDK, run:
101${chalk.gray('>')} ${chalk.italic.cyan(
102        'et add-sdk-version --platform ios --sdkVersion XX.0.0 --filenames */some/glob/expression/**'
103      )}`
104    )
105    .option(
106      '-p, --platform <string>',
107      `Specifies a platform for which the SDK code should be generated. Supported platforms: ${chalk.cyan(
108        'ios'
109      )}.`
110    )
111    .option(
112      '-s, --sdkVersion [string]',
113      'SDK version to add. Can be a full version name, major number or `next` tag. Defaults to `next` on the CI.'
114    )
115    .option(
116      '-f, --filenames [string]',
117      '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.'
118    )
119    .option(
120      '-v, --vendored <string>',
121      'Name of the vendored module to (re)version. iOS only.',
122      (value, previous) => (previous ?? []).concat(value),
123      []
124    )
125    .option(
126      '-r, --reinstall',
127      'Whether to force reinstalling pods after generating a new version. iOS only.'
128    )
129    .option(
130      '--prevent-reinstall',
131      'Whether to force not reinstalling pods after generating a new version. iOS only.'
132    )
133    .option('-x, --packages <string...>', 'Name of the expo package to version.')
134    .asyncAction(action);
135};
136