// rnx.config.ts — per-app module resolution, turbo modules, env, state, and settings. // // usage: // import { defineConfig } from 'rnxsim/config' // export default defineConfig({ modules: { ... }, env: { ... } }) import type { RevenueCatTestStoreConfig } from '@sootsim/compat/revenuecat-test-store' export type ModuleResolution = // an empty module under `modules`; under `nativeModules` / `turboModules` a // present-but-inert native module whose every method call is a no-op. | 'noop' | false // disable builtin stub, use original | { use: string } // redirect to existing compat stub | { file: string } // resolve to a file relative to the config | { inline: Record } // inline stub object export type NativeModuleResolution = ModuleResolution | Record // direct native module object for non-url configs export interface RNXConfig = Record> { // host-only engine selection. omitted means the current stable runtime. runtimeVersion?: string modules?: Record turboModules?: Record nativeModules?: Record env?: Record // generic per-sim network rewrites for the guest app's runtime fetches. // `remap` keys/values are `host:port` (or bare `host`) — a tenant fetch whose // url matches a key has its host:port swapped for the value before the // request is issued. exists for third-party e2e suites that hardcode a port // (e.g. bluesky's flows type `http://localhost:3000`) which collides with a // host service; map it to the real bound port without editing the app. network?: { remap?: Record } settings?: Partial initialState?: { authenticated?: boolean locale?: string colorScheme?: 'light' | 'dark' | 'auto' featureFlags?: Record // opt-in RevenueCat Test Store catalog consumed by the RNPurchases seam. // omitting it preserves the empty-project result with purchases unavailable. revenueCat?: RevenueCatTestStoreConfig [key: string]: any } } export const RNX_CONFIG_QUERY_PARAM = 'rnxConfig' export function defineConfig = Record>( config: RNXConfig, ): RNXConfig { return config } function hasOwnKeys(value: Record | undefined): boolean { return !!value && Object.keys(value).length > 0 } export function hasRNXConfig = Record>( config?: RNXConfig | null, ): config is RNXConfig { if (!config) return false return ( (typeof config.runtimeVersion === 'string' && config.runtimeVersion.length > 0) || hasOwnKeys(config.modules as Record | undefined) || hasOwnKeys(config.turboModules as Record | undefined) || hasOwnKeys(config.nativeModules as Record | undefined) || hasOwnKeys(config.env as Record | undefined) || hasOwnKeys(config.network?.remap as Record | undefined) || hasOwnKeys(config.settings as Record | undefined) || hasOwnKeys(config.initialState as Record | undefined) ) } export function mergeRNXConfig< Settings extends Record = Record, >( base?: RNXConfig | null, override?: RNXConfig | null, ): RNXConfig | undefined { if (!hasRNXConfig(base) && !hasRNXConfig(override)) return undefined if (!hasRNXConfig(base)) return override ? { ...override } : undefined if (!hasRNXConfig(override)) return base ? { ...base } : undefined return { runtimeVersion: override.runtimeVersion ?? base.runtimeVersion, modules: { ...(base.modules || {}), ...(override.modules || {}) }, turboModules: { ...(base.turboModules || {}), ...(override.turboModules || {}) }, nativeModules: { ...(base.nativeModules || {}), ...(override.nativeModules || {}), }, env: { ...(base.env || {}), ...(override.env || {}) }, network: { remap: { ...(base.network?.remap || {}), ...(override.network?.remap || {}) }, }, settings: { ...(base.settings || {}), ...(override.settings || {}), } as Partial, initialState: { ...(base.initialState || {}), ...(override.initialState || {}) }, } } export function readRNXConfigFromSearchParams< Settings extends Record = Record, >(searchParams: URLSearchParams): RNXConfig | undefined { const raw = searchParams.get(RNX_CONFIG_QUERY_PARAM) if (!raw) return undefined try { const parsed = JSON.parse(raw) as RNXConfig return hasRNXConfig(parsed) ? parsed : undefined } catch (error) { const message = error instanceof Error ? error.message : String(error) console.warn(`[rnx] invalid ${RNX_CONFIG_QUERY_PARAM}: ${message}`) return undefined } } export function applyRNXConfigToUrl< Settings extends Record = Record, >(url: string, config?: RNXConfig | null): string { const parsed = new URL(url) if (hasRNXConfig(config)) { parsed.searchParams.set(RNX_CONFIG_QUERY_PARAM, JSON.stringify(config)) } else { parsed.searchParams.delete(RNX_CONFIG_QUERY_PARAM) } return parsed.toString() }