1import { CommandError } from '../utils/errors';
2import { createCachedFetch } from './rest/client';
3
4interface NativeModule {
5  npmPackage: string;
6  versionRange: string;
7}
8type BundledNativeModuleList = NativeModule[];
9
10export type BundledNativeModules = Record<string, string>;
11
12/**
13 * The endpoint returns the list of bundled native modules for a given SDK version.
14 * The data is populated by the `et sync-bundled-native-modules` script from expo/expo repo.
15 * See the code for more details:
16 * https://github.com/expo/expo/blob/main/tools/src/commands/SyncBundledNativeModules.ts
17 *
18 * Example result:
19 * [
20 *   {
21 *     id: "79285187-e5c4-47f7-b6a9-664f5d16f0db",
22 *     sdkVersion: "41.0.0",
23 *     npmPackage: "expo-camera",
24 *     versionRange: "~10.1.0",
25 *     createdAt: "2021-04-29T09:34:32.825Z",
26 *     updatedAt: "2021-04-29T09:34:32.825Z"
27 *   },
28 *   ...
29 * ]
30 */
31export async function getNativeModuleVersionsAsync(
32  sdkVersion: string
33): Promise<BundledNativeModules> {
34  const fetchAsync = createCachedFetch({
35    cacheDirectory: 'native-modules-cache',
36    // 1 minute cache
37    ttl: 1000 * 60 * 1,
38  });
39  const results = await fetchAsync(`sdks/${sdkVersion}/native-modules`);
40  if (!results.ok) {
41    throw new CommandError(
42      'API',
43      `Unexpected response when fetching version info from Expo servers: ${results.statusText}.`
44    );
45  }
46  const { data } = await results.json();
47  if (!data.length) {
48    throw new CommandError('VERSIONS', 'The bundled native module list from the Expo API is empty');
49  }
50  return fromBundledNativeModuleList(data);
51}
52
53function fromBundledNativeModuleList(list: BundledNativeModuleList): BundledNativeModules {
54  return list.reduce((acc, i) => {
55    acc[i.npmPackage] = i.versionRange;
56    return acc;
57  }, {} as BundledNativeModules);
58}
59