import chalk from 'chalk' ; import { ChildProcessWithoutNullStreams , spawn } from 'child_process' ; import path from 'path' ; import wrapAnsi from 'wrap-ansi' ; import { Logger , SimControl } from './internal' ; const forks = { } ; export type SimControlLog = { /**
     * 258753568922927108
     */ traceID : number ; /**
     *
     * "Connection 1: done",
     */ eventMessage : string ; /**
     * "logEvent" | "activityCreateEvent",
     */ eventType : 'logEvent' | 'activityCreateEvent' ; source : null | { /**
         * 'RCTDefaultLogFunction_block_invoke' | '__TCC_CRASHING_DUE_TO_PRIVACY_VIOLATION__'
         */ symbol : string ; line : number ; /**
         * 'TCC' | 'Security' | 'CFNetwork' | 'libnetwork.dylib' | 'myapp'
         *
         * TCC is apple sys, it means "Transparency, Consent, and Control"
         */ image : string ; /**
         * 'RCTLog.mm' | ''
         */ file : string ; } ; /**
     * "Connection %llu: done"
     */ formatString : string ; /**
     * 0
     */ activityIdentifier : number ; subsystem : '' | 'com.apple.network' | 'com.facebook.react.log' | 'com.apple.TCC' | 'com.apple.CoreTelephony' | 'com.apple.WebKit' | 'com.apple.runningboard' | string ; category : '' | 'access' | 'connection' | 'plugin' ; /**
     * "2021-03-15 15:36:28.004331-0700"
     */ timestamp : string ; /**
     * 706567072091713
     */ machTimestamp : number ; /**
     * "Default"
     */ messageType : 'Default' | 'Error' ; /**
     * 15192
     */ processID : number ; } ; function parseMessageJson ( data ) { const stringData = data . toString ( ) ; try { return JSON . parse ( stringData ) as SimControlLog ; } catch ( e ) { Logger . global . debug ( 'Failed to parse simctl JSON message:\n' + stringData ) ; } return null ; } function isNetworkLog ( simLog ) { return ( simLog . subsystem === 'com.apple.network' || simLog . category === 'connection' || simLog . source ?. image === 'CFNetwork' ) ; } function isReactLog ( simLog ) { return simLog . subsystem === 'com.facebook.react.log' && simLog . source ?. file === 'RCTLog.mm' ; } function isCoreTelephonyLog ( simLog ) { return simLog . subsystem === 'com.apple.CoreTelephony' ; } function isWebKitLog ( simLog ) { return simLog . subsystem === 'com.apple.WebKit' ; } function isRunningBoardServicesLog ( simLog ) { return simLog . subsystem === 'com.apple.runningboard' ; } function formatMessage ( simLog ) { const category = chalk . gray ( `[${ simLog . source ?. image ?? simLog . subsystem }]` ) ; const message = simLog . eventMessage ; return wrapAnsi ( category + ' ' + message , process . stdout . columns || 80 ) ; } export function onMessage ( simLog ) { let hasLogged = false ; if ( simLog . messageType === 'Error' ) { if ( ! isNetworkLog ( simLog ) && ! isReactLog ( simLog ) && ! isCoreTelephonyLog ( simLog ) && ! isWebKitLog ( simLog ) && ! isRunningBoardServicesLog ( simLog ) ) { hasLogged = true ; Logger . global . error ( formatMessage ( simLog ) ) ; } } else if ( simLog . eventMessage ) { if ( simLog . source ?. file || simLog . eventMessage . includes ( 'Terminating app due to uncaught exception' ) ) { hasLogged = true ; Logger . global . info ( formatMessage ( simLog ) ) ; } } if ( ! hasLogged ) { Logger . global . debug ( formatMessage ( simLog ) ) ; } else { } } export function isStreamingLogs ( udid ) { return ! ! forks [ udid ] ; } export function streamLogs ( { pid , udid } ) { if ( forks [ udid ] ) { return ; } const childProcess = spawn ( 'xcrun' , [ 'simctl' , 'spawn' , udid , 'log' , 'stream' , '--process' , pid , '--style' , 'ndjson' , '--source' , '--type' , 'log' , '--no-backtrace' , ] ) ; childProcess . stdout . on ( 'data' , ( data ) => { const simLog = parseMessageJson ( data ) ; if ( ! simLog ) { return ; } onMessage ( simLog ) ; } ) ; childProcess . on ( 'error' , ( { message } ) => { Logger . global . debug ( '[simctl error]:' , message ) ; } ) ; forks [ udid ] = childProcess ; ensureExitHooksInstalled ( ) ; } export async function detachStream ( udid ) { if ( forks [ udid ] ) { await killProcess ( forks [ udid ] ) ; delete forks [ udid ] ; } } let hasInstalledExitHooks = false ; function ensureExitHooksInstalled ( ) { if ( hasInstalledExitHooks ) return ; hasInstalledExitHooks = true ; const killSignals = [ 'SIGINT' , 'SIGTERM' ] ; for ( const signal of killSignals ) { process . on ( signal , async ( ) => { await Promise . all ( Object . keys ( forks ) . map ( udid => detachStream ( udid ) ) ) ; } ) ; } } async function killProcess ( childProcess ) { if ( childProcess ) { return new Promise < void > ( resolve => { childProcess . on ( 'close' , resolve ) ; childProcess . kill ( ) ; } ) ; } } param udid param bundleIdentifier returns export async function getImageNameFromBundleIdentifierAsync ( udid , bundleIdentifier ) { const containerPath = await SimControl . getContainerPathAsync ( { udid , bundleIdentifier } ) ; if ( containerPath ) { return getImageNameFromContainerPath ( containerPath ) ; } return null ; } export function getImageNameFromContainerPath ( binaryPath ) { return path . basename ( binaryPath ) . split ( '.' ) [ 0 ] ; }