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