1import JsonFile from '@expo/json-file';
2import chalk from 'chalk';
3import resolveFrom from 'resolve-from';
4
5import { getNativeModuleVersionsAsync } from '../../../api/getNativeModuleVersions';
6import * as Log from '../../../log';
7import { env } from '../../../utils/env';
8import { CommandError } from '../../../utils/errors';
9
10const debug = require('debug')(
11  'expo:doctor:dependencies:bundledNativeModules'
12) as typeof console.log;
13
14export type BundledNativeModules = Record<string, string>;
15
16/**
17 * Gets the bundledNativeModules.json for a given SDK version:
18 * - Tries to fetch the data from the /sdks/:sdkVersion/native-modules API endpoint.
19 * - If the data is missing on the server (it can happen for SDKs that are yet fully released)
20 *    or there's a downtime, reads the local .json file from the "expo" package.
21 * - For UNVERSIONED, returns the local .json file contents.
22 */
23export async function getVersionedNativeModulesAsync(
24  projectRoot: string,
25  sdkVersion: string
26): Promise<BundledNativeModules> {
27  if (sdkVersion !== 'UNVERSIONED' && !env.EXPO_OFFLINE) {
28    try {
29      debug('Fetching bundled native modules from the server...');
30      return await getNativeModuleVersionsAsync(sdkVersion);
31    } catch (error: any) {
32      if (error instanceof CommandError && (error.code === 'OFFLINE' || error.code === 'API')) {
33        Log.warn(
34          chalk`Unable to reach well-known versions endpoint. Using local dependency map {bold expo/bundledNativeModules.json} for version validation`
35        );
36      } else {
37        throw error;
38      }
39    }
40  }
41
42  debug('Fetching bundled native modules from the local JSON file...');
43  return await getBundledNativeModulesAsync(projectRoot);
44}
45
46/**
47 * Get the legacy static `bundledNativeModules.json` file
48 * that's shipped with the version of `expo` that the project has installed.
49 */
50async function getBundledNativeModulesAsync(projectRoot: string): Promise<BundledNativeModules> {
51  // TODO: Revisit now that this code is in the `expo` package.
52  const bundledNativeModulesPath = resolveFrom.silent(
53    projectRoot,
54    'expo/bundledNativeModules.json'
55  );
56  if (!bundledNativeModulesPath) {
57    Log.log();
58    throw new CommandError(
59      chalk`The dependency map {bold expo/bundledNativeModules.json} cannot be found, please ensure you have the package "{bold expo}" installed in your project.`
60    );
61  }
62  return await JsonFile.readAsync<BundledNativeModules>(bundledNativeModulesPath);
63}
64