import { createRequire } from 'node:module' import path from 'node:path' import { isValidRuntimeVersion } from '@contrast/runtime-delivery' import { createJiti } from 'jiti' import { rnxPublicBrand } from '../src/public-brand' import { resolveAppProject, type ResolvedAppProject } from './app-project' import type { RNXConfig } from '../src/config' const require = createRequire(import.meta.url) function isOptionalRecord(value: unknown): boolean { return ( value === undefined || (value !== null && typeof value === 'object' && !Array.isArray(value)) ) } function isRNXConfig(value: unknown): value is RNXConfig { if (!value || typeof value !== 'object' || Array.isArray(value)) return false const runtimeVersion = Reflect.get(value, 'runtimeVersion') const network = Reflect.get(value, 'network') return ( (runtimeVersion === undefined || (typeof runtimeVersion === 'string' && runtimeVersion === runtimeVersion.trim() && isValidRuntimeVersion(runtimeVersion))) && isOptionalRecord(Reflect.get(value, 'modules')) && isOptionalRecord(Reflect.get(value, 'turboModules')) && isOptionalRecord(Reflect.get(value, 'nativeModules')) && isOptionalRecord(Reflect.get(value, 'env')) && isOptionalRecord(network) && isOptionalRecord(Reflect.get(value, 'settings')) && isOptionalRecord(Reflect.get(value, 'initialState')) ) } function resolveConfigReplacementFiles(config: RNXConfig, configPath: string): RNXConfig { if (!config.modules) return config const configDir = path.dirname(configPath) return { ...config, modules: Object.fromEntries( Object.entries(config.modules).map(([name, resolution]) => [ name, resolution && typeof resolution === 'object' && 'file' in resolution ? { ...resolution, file: path.resolve(configDir, resolution.file) } : resolution, ]), ), } } export async function loadRNXAppConfig( project: ResolvedAppProject | null, ): Promise { if (!project?.configPath) return undefined try { const jiti = createJiti(import.meta.url, { interopDefault: true, alias: { [`${rnxPublicBrand.packageName}/config`]: require.resolve( `${rnxPublicBrand.packageName}/config`, ), }, }) const loaded: unknown = await jiti.import(project.configPath, { default: true }) if (!isRNXConfig(loaded)) { throw new Error('the default export must be an rnx config object') } return resolveConfigReplacementFiles(loaded, project.configPath) } catch (error) { const message = error instanceof Error ? error.message : String(error) throw new Error(`could not load ${project.configPath}: ${message}`) } } export async function resolveRNXAppConfig( startDir: string = process.cwd(), ): Promise<{ project: ResolvedAppProject | null; config: RNXConfig | undefined }> { const project = resolveAppProject(startDir) return { project, config: await loadRNXAppConfig(project) } }