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