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