xref: /expo/tools/src/dynamic-macros/macros.ts (revision 47f2bcdc)
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