1import { JSONObject } from '@expo/json-file'; 2import fs from 'fs'; 3import schemaDerefSync from 'json-schema-deref-sync'; 4import path from 'path'; 5 6import { createCachedFetch } from './rest/client'; 7import { env } from '../utils/env'; 8import { CommandError } from '../utils/errors'; 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 (env.EXPO_UNIVERSE_DIR) { 52 return JSON.parse( 53 fs 54 .readFileSync( 55 path.join( 56 env.EXPO_UNIVERSE_DIR, 57 'server', 58 'www', 59 'xdl-schemas', 60 'UNVERSIONED-schema.json' 61 ) 62 ) 63 .toString() 64 ); 65 } 66 67 if (!schemaJson[sdkVersion]) { 68 try { 69 schemaJson[sdkVersion] = await getConfigurationSchemaAsync(sdkVersion); 70 } catch (e: any) { 71 if (e.code === 'INVALID_JSON') { 72 throw new CommandError('INVALID_JSON', `Couldn't read schema from server`); 73 } 74 75 throw e; 76 } 77 } 78 79 return schemaJson[sdkVersion]; 80} 81 82async function getConfigurationSchemaAsync(sdkVersion: string): Promise<JSONObject> { 83 // Reconstruct the cached fetch since caching could be disabled. 84 const fetchAsync = createCachedFetch({ 85 cacheDirectory: 'schema-cache', 86 // We'll use a 1 week cache for versions so older versions get flushed out eventually. 87 ttl: 1000 * 60 * 60 * 24 * 7, 88 }); 89 const response = await fetchAsync(`project/configuration/schema/${sdkVersion}`); 90 const { data } = await response.json(); 91 return data; 92} 93