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