1import JsonFile from '@expo/json-file'; 2import spawnAsync from '@expo/spawn-async'; 3import { ExponentTools, Project, UrlUtils } from '@expo/xdl'; 4import chalk from 'chalk'; 5import crypto from 'crypto'; 6import ip from 'ip'; 7import fetch from 'node-fetch'; 8import os from 'os'; 9import path from 'path'; 10 11import { getExpoRepositoryRootDir } from '../Directories'; 12import { getHomeSDKVersionAsync } from '../ProjectVersions'; 13 14interface Manifest { 15 id: string; 16 name: string; 17 extra?: { 18 expoClient?: { 19 name: string; 20 }; 21 }; 22} 23 24// some files are absent on turtle builders and we don't want log errors there 25const isTurtle = !!process.env.TURTLE_WORKING_DIR_PATH; 26 27const dogfoodingHomeUrl = 'exp://exp.host/@expo-dogfooding/home'; 28 29const EXPO_DIR = getExpoRepositoryRootDir(); 30 31async function getManifestAsync( 32 url: string, 33 platform: string, 34 sdkVersion: string | null 35): Promise<Manifest> { 36 const headers = { 37 'Exponent-Platform': platform, 38 Accept: 'application/expo+json,application/json', 39 }; 40 if (sdkVersion) { 41 headers['Exponent-SDK-Version'] = sdkVersion; 42 } 43 return await ExponentTools.getManifestAsync(url, headers, { 44 logger: { 45 log: () => {}, 46 error: () => {}, 47 info: () => {}, 48 }, 49 }); 50} 51 52async function getSavedDevHomeUrlAsync(): Promise<string> { 53 const devHomeConfig = await new JsonFile(path.join(EXPO_DIR, 'dev-home-config.json')).readAsync(); 54 return devHomeConfig.url as string; 55} 56 57function kernelManifestObjectToJson(manifest) { 58 if (!manifest.id) { 59 // hack for now because unsigned manifest won't have an id 60 manifest.id = '@exponent/home'; 61 } 62 manifest.sdkVersion = 'UNVERSIONED'; 63 return JSON.stringify(manifest); 64} 65 66export default { 67 async TEST_APP_URI() { 68 if (process.env.TEST_SUITE_URI) { 69 return process.env.TEST_SUITE_URI; 70 } else { 71 try { 72 const testSuitePath = path.join(__dirname, '..', '..', '..', 'apps', 'test-suite'); 73 const status = await Project.currentStatus(testSuitePath); 74 if (status === 'running') { 75 return await UrlUtils.constructManifestUrlAsync(testSuitePath); 76 } else { 77 return ''; 78 } 79 } catch { 80 return ''; 81 } 82 } 83 }, 84 85 async TEST_CONFIG() { 86 if (process.env.TEST_CONFIG) { 87 return process.env.TEST_CONFIG; 88 } else { 89 return ''; 90 } 91 }, 92 93 async TEST_SERVER_URL() { 94 let url = 'TODO'; 95 96 try { 97 const lanAddress = ip.address(); 98 const localServerUrl = `http://${lanAddress}:3013`; 99 const response = await fetch(`${localServerUrl}/expo-test-server-status`, { timeout: 500 }); 100 const data = await response.text(); 101 if (data === 'running!') { 102 url = localServerUrl; 103 } 104 } catch {} 105 106 return url; 107 }, 108 109 async TEST_RUN_ID() { 110 return process.env.UNIVERSE_BUILD_ID || crypto.randomUUID(); 111 }, 112 113 async BUILD_MACHINE_LOCAL_HOSTNAME() { 114 if (process.env.SHELL_APP_BUILDER) { 115 return ''; 116 } 117 118 try { 119 const result = await spawnAsync('scutil', ['--get', 'LocalHostName']); 120 return `${result.stdout.trim()}.local`; 121 } catch (e) { 122 if (e.code !== 'ENOENT') { 123 console.error(e.stack); 124 } 125 return os.hostname(); 126 } 127 }, 128 129 async DEV_PUBLISHED_KERNEL_MANIFEST(platform) { 130 let manifest, savedDevHomeUrl; 131 try { 132 savedDevHomeUrl = await getSavedDevHomeUrlAsync(); 133 const sdkVersion = await this.TEMPORARY_SDK_VERSION(); 134 135 manifest = await getManifestAsync(savedDevHomeUrl, platform, sdkVersion); 136 } catch (e) { 137 const msg = `Unable to download manifest from ${savedDevHomeUrl}: ${e.message}`; 138 console[isTurtle ? 'debug' : 'error'](msg); 139 return ''; 140 } 141 142 return kernelManifestObjectToJson(manifest); 143 }, 144 145 async DOGFOODING_PUBLISHED_KERNEL_MANIFEST(platform) { 146 let manifest: Manifest; 147 try { 148 const sdkVersion = await this.TEMPORARY_SDK_VERSION(); 149 manifest = await getManifestAsync(dogfoodingHomeUrl, platform, sdkVersion); 150 } catch (e) { 151 const msg = `Unable to download manifest from ${dogfoodingHomeUrl}: ${e.message}`; 152 console[isTurtle ? 'debug' : 'error'](msg); 153 return ''; 154 } 155 156 return kernelManifestObjectToJson(manifest); 157 }, 158 159 async BUILD_MACHINE_KERNEL_MANIFEST(platform) { 160 if (process.env.SHELL_APP_BUILDER) { 161 return ''; 162 } 163 164 if (process.env.CI) { 165 console.log('Skip fetching local manifest on CI.'); 166 return ''; 167 } 168 169 const pathToHome = 'home'; 170 const url = await UrlUtils.constructManifestUrlAsync(path.join(EXPO_DIR, pathToHome)); 171 172 try { 173 const manifest = await getManifestAsync(url, platform, null); 174 175 if (manifest.name !== 'expo-home') { 176 console.log( 177 `Manifest at ${url} is not expo-home; using published kernel manifest instead...` 178 ); 179 return ''; 180 } 181 return kernelManifestObjectToJson(manifest); 182 } catch { 183 console.error( 184 chalk.red( 185 `Unable to generate manifest from ${chalk.cyan( 186 pathToHome 187 )}: Failed to fetch manifest from ${chalk.cyan(url)}` 188 ) 189 ); 190 return ''; 191 } 192 }, 193 194 async TEMPORARY_SDK_VERSION(): Promise<string> { 195 return await getHomeSDKVersionAsync(); 196 }, 197 198 INITIAL_URL() { 199 return null; 200 }, 201}; 202