xref: /expo/tools/src/ProjectVersions.ts (revision a272999e)
1import JsonFile from '@expo/json-file';
2import fs from 'fs-extra';
3import path from 'path';
4import plist from 'plist';
5import semver from 'semver';
6
7import { EXPO_DIR, ANDROID_DIR, PACKAGES_DIR } from './Constants';
8
9export type Platform = 'ios' | 'android';
10
11export type SDKVersionsObject = {
12  sdkVersions: string[];
13};
14
15const BUNDLED_NATIVE_MODULES_PATH = path.join(PACKAGES_DIR, 'expo', 'bundledNativeModules.json');
16
17export async function sdkVersionAsync(): Promise<string> {
18  const packageJson = await JsonFile.readAsync(path.join(EXPO_DIR, 'packages/expo/package.json'));
19  return packageJson.version as string;
20}
21
22export async function iosAppVersionAsync(): Promise<string> {
23  const infoPlistPath = path.join(EXPO_DIR, 'ios', 'Exponent', 'Supporting', 'Info.plist');
24  const infoPlist = plist.parse(fs.readFileSync(infoPlistPath, 'utf8'));
25  const bundleVersion = infoPlist.CFBundleShortVersionString;
26
27  if (!bundleVersion) {
28    throw new Error(`"CFBundleShortVersionString" not found in plist: ${infoPlistPath}`);
29  }
30  return bundleVersion;
31}
32
33export async function androidAppVersionAsync(): Promise<string> {
34  const buildGradlePath = path.join(ANDROID_DIR, 'app', 'build.gradle');
35  const buildGradleContent = await fs.readFile(buildGradlePath, 'utf8');
36  const match = buildGradleContent.match(/versionName ['"]([^'"]+?)['"]/);
37
38  if (!match) {
39    throw new Error("Can't obtain `versionName` from app's build.gradle");
40  }
41  return match[1];
42}
43
44export async function getHomeSDKVersionAsync(): Promise<string> {
45  const homeAppJsonPath = path.join(EXPO_DIR, 'home', 'app.json');
46  const appJson = (await JsonFile.readAsync(homeAppJsonPath, { json5: true })) as any;
47
48  if (appJson?.expo?.sdkVersion) {
49    return appJson.expo.sdkVersion as string;
50  }
51  throw new Error(`Home's SDK version not found!`);
52}
53
54export async function getSDKVersionsAsync(platform: Platform): Promise<string[]> {
55  const sdkVersionsPath = path.join(
56    EXPO_DIR,
57    platform === 'ios' ? 'ios/Exponent/Supporting' : 'android',
58    'sdkVersions.json'
59  );
60
61  if (!(await fs.pathExists(sdkVersionsPath))) {
62    throw new Error(`File at path "${sdkVersionsPath}" not found.`);
63  }
64  const { sdkVersions } = (await JsonFile.readAsync(sdkVersionsPath)) as SDKVersionsObject;
65  return sdkVersions;
66}
67
68export async function getOldestSDKVersionAsync(platform: Platform): Promise<string | undefined> {
69  const sdkVersions = await getSDKVersionsAsync(platform);
70  return sdkVersions.sort(semver.compare)[0];
71}
72
73export async function getNewestSDKVersionAsync(platform: Platform): Promise<string | undefined> {
74  const sdkVersions = await getSDKVersionsAsync(platform);
75  return sdkVersions.sort(semver.rcompare)[0];
76}
77
78export async function getNextSDKVersionAsync(platform: Platform): Promise<string | undefined> {
79  const newestVersion = await getNewestSDKVersionAsync(platform);
80
81  if (!newestVersion) {
82    return;
83  }
84  return `${semver.major(semver.inc(newestVersion, 'major')!)}.0.0`;
85}
86
87/**
88 * Resolves given SDK number or tag to appropriate version.
89 */
90export async function resolveSDKVersionAsync(
91  sdkVersion: string,
92  platform: Platform
93): Promise<string | undefined> {
94  if (sdkVersion === 'latest') {
95    return await getNewestSDKVersionAsync(platform);
96  }
97  if (sdkVersion === 'oldest') {
98    return await getOldestSDKVersionAsync(platform);
99  }
100  if (sdkVersion === 'next') {
101    return await getNextSDKVersionAsync(platform);
102  }
103  if (/^\d+$/.test(sdkVersion)) {
104    return `${sdkVersion}.0.0`;
105  }
106  return sdkVersion;
107}
108
109/**
110 * Returns an object with versions of bundled native modules.
111 */
112export async function getBundledVersionsAsync(): Promise<Record<string, string>> {
113  return require(BUNDLED_NATIVE_MODULES_PATH) as Record<string, string>;
114}
115
116/**
117 * Updates bundled native modules versions.
118 */
119export async function updateBundledVersionsAsync(patch: Record<string, string>): Promise<void> {
120  await JsonFile.mergeAsync(BUNDLED_NATIVE_MODULES_PATH, patch);
121}
122