xref: /expo/packages/@expo/cli/src/api/getExpoSchema.ts (revision bec11b8a)
1import { JSONObject } from '@expo/json-file';
2import fs from 'fs';
3import schemaDerefSync from 'json-schema-deref-sync';
4import path from 'path';
5
6import { EXPO_UNIVERSE_DIR } from '../utils/env';
7import { CommandError } from '../utils/errors';
8import { createCachedFetch } from './rest/client';
9
10export type Schema = any;
11
12export type AssetSchema = {
13  fieldPath: string;
14};
15
16const schemaJson: { [sdkVersion: string]: Schema } = {};
17
18// TODO: Maybe move json-schema-deref-sync out of api (1.58MB -- lodash)
19// https://packagephobia.com/result?p=json-schema-deref-sync
20async function getSchemaAsync(sdkVersion: string): Promise<Schema> {
21  const json = await getSchemaJSONAsync(sdkVersion);
22  return schemaDerefSync(json.schema);
23}
24
25/**
26 * Array of schema nodes that refer to assets along with their field path (eg. 'notification.icon')
27 *
28 * @param sdkVersion
29 */
30export async function getAssetSchemasAsync(sdkVersion: string = 'UNVERSIONED'): Promise<string[]> {
31  // If no SDK version is available then fall back to unversioned
32  const schema = await getSchemaAsync(sdkVersion);
33  const assetSchemas: string[] = [];
34  const visit = (node: Schema, fieldPath: string) => {
35    if (node.meta && node.meta.asset) {
36      assetSchemas.push(fieldPath);
37    }
38    const properties = node.properties;
39    if (properties) {
40      Object.keys(properties).forEach((property) =>
41        visit(properties[property], `${fieldPath}${fieldPath.length > 0 ? '.' : ''}${property}`)
42      );
43    }
44  };
45  visit(schema, '');
46
47  return assetSchemas;
48}
49
50async function getSchemaJSONAsync(sdkVersion: string): Promise<{ schema: Schema }> {
51  if (EXPO_UNIVERSE_DIR) {
52    return JSON.parse(
53      fs
54        .readFileSync(
55          path.join(EXPO_UNIVERSE_DIR, 'server', 'www', 'xdl-schemas', 'UNVERSIONED-schema.json')
56        )
57        .toString()
58    );
59  }
60
61  if (!schemaJson[sdkVersion]) {
62    try {
63      schemaJson[sdkVersion] = await getConfigurationSchemaAsync(sdkVersion);
64    } catch (e: any) {
65      if (e.code === 'INVALID_JSON') {
66        throw new CommandError('INVALID_JSON', `Couldn't read schema from server`);
67      }
68
69      throw e;
70    }
71  }
72
73  return schemaJson[sdkVersion];
74}
75
76async function getConfigurationSchemaAsync(sdkVersion: string): Promise<JSONObject> {
77  // Reconstruct the cached fetch since caching could be disabled.
78  const fetchAsync = createCachedFetch({
79    cacheDirectory: 'schema-cache',
80    // We'll use a 1 week cache for versions so older versions get flushed out eventually.
81    ttl: 1000 * 60 * 60 * 24 * 7,
82  });
83  const response = await fetchAsync(`project/configuration/schema/${sdkVersion}`);
84  const { data } = await response.json();
85  return data;
86}
87