xref: /expo/tools/src/ProjectVersions.ts (revision 2d0cd754)
1import path from 'path';
2import fs from 'fs-extra';
3import plist from 'plist';
4import semver from 'semver';
5import JsonFile from '@expo/json-file';
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 * Returns an object with versions of bundled native modules.
89 */
90export async function getBundledVersionsAsync(): Promise<Record<string, string>> {
91  return require(BUNDLED_NATIVE_MODULES_PATH) as Record<string, string>;
92}
93
94/**
95 * Updates bundled native modules versions.
96 */
97export async function updateBundledVersionsAsync(patch: Record<string, string>): Promise<void> {
98  await JsonFile.mergeAsync(BUNDLED_NATIVE_MODULES_PATH, patch);
99}
100