xref: /expo/tools/src/ProjectVersions.ts (revision a272999e)
1*a272999eSBartosz Kaszubowskiimport JsonFile from '@expo/json-file';
2eeffdb10STomasz Sapetaimport fs from 'fs-extra';
3*a272999eSBartosz Kaszubowskiimport path from 'path';
4eeffdb10STomasz Sapetaimport plist from 'plist';
5eeffdb10STomasz Sapetaimport semver from 'semver';
6eeffdb10STomasz Sapeta
79657025fSTomasz Sapetaimport { EXPO_DIR, ANDROID_DIR, PACKAGES_DIR } from './Constants';
8eeffdb10STomasz Sapeta
9eeffdb10STomasz Sapetaexport type Platform = 'ios' | 'android';
10eeffdb10STomasz Sapeta
11eeffdb10STomasz Sapetaexport type SDKVersionsObject = {
12eeffdb10STomasz Sapeta  sdkVersions: string[];
13eeffdb10STomasz Sapeta};
14eeffdb10STomasz Sapeta
159657025fSTomasz Sapetaconst BUNDLED_NATIVE_MODULES_PATH = path.join(PACKAGES_DIR, 'expo', 'bundledNativeModules.json');
169657025fSTomasz Sapeta
17eeffdb10STomasz Sapetaexport async function sdkVersionAsync(): Promise<string> {
18eeffdb10STomasz Sapeta  const packageJson = await JsonFile.readAsync(path.join(EXPO_DIR, 'packages/expo/package.json'));
19eeffdb10STomasz Sapeta  return packageJson.version as string;
20eeffdb10STomasz Sapeta}
21eeffdb10STomasz Sapeta
22eeffdb10STomasz Sapetaexport async function iosAppVersionAsync(): Promise<string> {
23eeffdb10STomasz Sapeta  const infoPlistPath = path.join(EXPO_DIR, 'ios', 'Exponent', 'Supporting', 'Info.plist');
24eeffdb10STomasz Sapeta  const infoPlist = plist.parse(fs.readFileSync(infoPlistPath, 'utf8'));
25eeffdb10STomasz Sapeta  const bundleVersion = infoPlist.CFBundleShortVersionString;
26eeffdb10STomasz Sapeta
27eeffdb10STomasz Sapeta  if (!bundleVersion) {
28eeffdb10STomasz Sapeta    throw new Error(`"CFBundleShortVersionString" not found in plist: ${infoPlistPath}`);
29eeffdb10STomasz Sapeta  }
30eeffdb10STomasz Sapeta  return bundleVersion;
31eeffdb10STomasz Sapeta}
32eeffdb10STomasz Sapeta
33eeffdb10STomasz Sapetaexport async function androidAppVersionAsync(): Promise<string> {
34eeffdb10STomasz Sapeta  const buildGradlePath = path.join(ANDROID_DIR, 'app', 'build.gradle');
35eeffdb10STomasz Sapeta  const buildGradleContent = await fs.readFile(buildGradlePath, 'utf8');
36eeffdb10STomasz Sapeta  const match = buildGradleContent.match(/versionName ['"]([^'"]+?)['"]/);
37eeffdb10STomasz Sapeta
38eeffdb10STomasz Sapeta  if (!match) {
39eeffdb10STomasz Sapeta    throw new Error("Can't obtain `versionName` from app's build.gradle");
40eeffdb10STomasz Sapeta  }
41eeffdb10STomasz Sapeta  return match[1];
42eeffdb10STomasz Sapeta}
43eeffdb10STomasz Sapeta
44eeffdb10STomasz Sapetaexport async function getHomeSDKVersionAsync(): Promise<string> {
45eeffdb10STomasz Sapeta  const homeAppJsonPath = path.join(EXPO_DIR, 'home', 'app.json');
46eeffdb10STomasz Sapeta  const appJson = (await JsonFile.readAsync(homeAppJsonPath, { json5: true })) as any;
47eeffdb10STomasz Sapeta
48eeffdb10STomasz Sapeta  if (appJson?.expo?.sdkVersion) {
49eeffdb10STomasz Sapeta    return appJson.expo.sdkVersion as string;
50eeffdb10STomasz Sapeta  }
51eeffdb10STomasz Sapeta  throw new Error(`Home's SDK version not found!`);
52eeffdb10STomasz Sapeta}
53eeffdb10STomasz Sapeta
54eeffdb10STomasz Sapetaexport async function getSDKVersionsAsync(platform: Platform): Promise<string[]> {
55eeffdb10STomasz Sapeta  const sdkVersionsPath = path.join(
56eeffdb10STomasz Sapeta    EXPO_DIR,
57eeffdb10STomasz Sapeta    platform === 'ios' ? 'ios/Exponent/Supporting' : 'android',
58eeffdb10STomasz Sapeta    'sdkVersions.json'
59eeffdb10STomasz Sapeta  );
60eeffdb10STomasz Sapeta
61eeffdb10STomasz Sapeta  if (!(await fs.pathExists(sdkVersionsPath))) {
62eeffdb10STomasz Sapeta    throw new Error(`File at path "${sdkVersionsPath}" not found.`);
63eeffdb10STomasz Sapeta  }
64eeffdb10STomasz Sapeta  const { sdkVersions } = (await JsonFile.readAsync(sdkVersionsPath)) as SDKVersionsObject;
65eeffdb10STomasz Sapeta  return sdkVersions;
66eeffdb10STomasz Sapeta}
67eeffdb10STomasz Sapeta
68eeffdb10STomasz Sapetaexport async function getOldestSDKVersionAsync(platform: Platform): Promise<string | undefined> {
69eeffdb10STomasz Sapeta  const sdkVersions = await getSDKVersionsAsync(platform);
70eeffdb10STomasz Sapeta  return sdkVersions.sort(semver.compare)[0];
71eeffdb10STomasz Sapeta}
72eeffdb10STomasz Sapeta
73eeffdb10STomasz Sapetaexport async function getNewestSDKVersionAsync(platform: Platform): Promise<string | undefined> {
74eeffdb10STomasz Sapeta  const sdkVersions = await getSDKVersionsAsync(platform);
75eeffdb10STomasz Sapeta  return sdkVersions.sort(semver.rcompare)[0];
76eeffdb10STomasz Sapeta}
77eeffdb10STomasz Sapeta
78eeffdb10STomasz Sapetaexport async function getNextSDKVersionAsync(platform: Platform): Promise<string | undefined> {
79eeffdb10STomasz Sapeta  const newestVersion = await getNewestSDKVersionAsync(platform);
80eeffdb10STomasz Sapeta
81eeffdb10STomasz Sapeta  if (!newestVersion) {
82eeffdb10STomasz Sapeta    return;
83eeffdb10STomasz Sapeta  }
84eeffdb10STomasz Sapeta  return `${semver.major(semver.inc(newestVersion, 'major')!)}.0.0`;
85eeffdb10STomasz Sapeta}
869657025fSTomasz Sapeta
879657025fSTomasz Sapeta/**
88941f41afSTomasz Sapeta * Resolves given SDK number or tag to appropriate version.
89941f41afSTomasz Sapeta */
90941f41afSTomasz Sapetaexport async function resolveSDKVersionAsync(
91941f41afSTomasz Sapeta  sdkVersion: string,
92941f41afSTomasz Sapeta  platform: Platform
93941f41afSTomasz Sapeta): Promise<string | undefined> {
94941f41afSTomasz Sapeta  if (sdkVersion === 'latest') {
95941f41afSTomasz Sapeta    return await getNewestSDKVersionAsync(platform);
96941f41afSTomasz Sapeta  }
97941f41afSTomasz Sapeta  if (sdkVersion === 'oldest') {
98941f41afSTomasz Sapeta    return await getOldestSDKVersionAsync(platform);
99941f41afSTomasz Sapeta  }
100941f41afSTomasz Sapeta  if (sdkVersion === 'next') {
101941f41afSTomasz Sapeta    return await getNextSDKVersionAsync(platform);
102941f41afSTomasz Sapeta  }
103941f41afSTomasz Sapeta  if (/^\d+$/.test(sdkVersion)) {
104941f41afSTomasz Sapeta    return `${sdkVersion}.0.0`;
105941f41afSTomasz Sapeta  }
106941f41afSTomasz Sapeta  return sdkVersion;
107941f41afSTomasz Sapeta}
108941f41afSTomasz Sapeta
109941f41afSTomasz Sapeta/**
1109657025fSTomasz Sapeta * Returns an object with versions of bundled native modules.
1119657025fSTomasz Sapeta */
1129657025fSTomasz Sapetaexport async function getBundledVersionsAsync(): Promise<Record<string, string>> {
1139657025fSTomasz Sapeta  return require(BUNDLED_NATIVE_MODULES_PATH) as Record<string, string>;
1149657025fSTomasz Sapeta}
1159657025fSTomasz Sapeta
1169657025fSTomasz Sapeta/**
1179657025fSTomasz Sapeta * Updates bundled native modules versions.
1189657025fSTomasz Sapeta */
1199657025fSTomasz Sapetaexport async function updateBundledVersionsAsync(patch: Record<string, string>): Promise<void> {
1209657025fSTomasz Sapeta  await JsonFile.mergeAsync(BUNDLED_NATIVE_MODULES_PATH, patch);
1219657025fSTomasz Sapeta}
122