Skip to main content
Glama
simulator.ts9.78 kB
import { exec, spawn } from "child_process"; import { promisify } from "util"; import { logger } from "./logger"; import { ExecResult } from "../models"; const execAsync = async (command: string): Promise<ExecResult> => { const result = await promisify(exec)(command); // Add the required string methods const enhancedResult: ExecResult = { stdout: result.stdout, stderr: result.stderr, toString() { return this.stdout; }, trim() { return this.stdout.trim(); }, includes(searchString: string) { return this.stdout.includes(searchString); } }; return enhancedResult; }; export interface SimulatorInfo { name: string; udid: string; state: string; isAvailable: boolean; deviceType: string; runtime: string; } export class SimulatorUtils { private execAsync: (command: string) => Promise<ExecResult>; private spawnFn: typeof spawn; /** * Create a SimulatorUtils instance * @param execAsyncFn - promisified exec function (for testing) * @param spawnFn - spawn function (for testing) */ constructor( execAsyncFn: ((command: string) => Promise<ExecResult>) | null = null, spawnFn: typeof spawn | null = null ) { this.execAsync = execAsyncFn || execAsync; this.spawnFn = spawnFn || spawn; } /** * List all available iOS simulators * @returns Promise with array of simulator names */ async listSimulators(): Promise<string[]> { try { logger.info("Listing available iOS simulators"); const result = await this.execAsync("xcrun simctl list devices --json"); const data = JSON.parse(result.stdout); const simulators: string[] = []; // Parse the simulators from the JSON structure Object.keys(data.devices).forEach(runtime => { if (Array.isArray(data.devices[runtime])) { data.devices[runtime].forEach((device: any) => { if (device.isAvailable !== false) { simulators.push(device.name); } }); } }); return [...new Set(simulators)]; // Remove duplicates } catch (error) { logger.error("Failed to list iOS simulators:", error); return []; } } /** * Get detailed information about all simulators * @returns Promise with array of simulator info */ async getSimulatorInfo(): Promise<SimulatorInfo[]> { try { const result = await this.execAsync("xcrun simctl list devices --json"); const data = JSON.parse(result.stdout); const simulators: SimulatorInfo[] = []; Object.keys(data.devices).forEach(runtime => { if (Array.isArray(data.devices[runtime])) { data.devices[runtime].forEach((device: any) => { simulators.push({ name: device.name, udid: device.udid, state: device.state, isAvailable: device.isAvailable !== false, deviceType: device.name, runtime: runtime }); }); } }); return simulators; } catch (error) { logger.error("Failed to get iOS simulator info:", error); return []; } } /** * Get list of running iOS simulators * @returns Promise with array of running simulator info */ async getRunningSimulators(): Promise<SimulatorInfo[]> { try { const allSimulators = await this.getSimulatorInfo(); return allSimulators.filter(sim => sim.state === "Booted"); } catch (error) { logger.error("Failed to get running iOS simulators:", error); return []; } } /** * Start an iOS simulator by name * @param simulatorName - Name of the simulator to start * @param timeoutMs - Optional timeout in milliseconds * @returns Promise with result */ async startSimulator(simulatorName: string, timeoutMs: number = 120000): Promise<{ success: boolean; simulatorName: string; udid?: string; error?: string; }> { try { logger.info(`Starting iOS simulator: ${simulatorName}`); // Find the simulator by name const simulators = await this.getSimulatorInfo(); const simulator = simulators.find(sim => sim.name === simulatorName && sim.isAvailable ); if (!simulator) { return { success: false, simulatorName, error: `Simulator '${simulatorName}' not found or not available` }; } // Check if already running if (simulator.state === "Booted") { return { success: true, simulatorName, udid: simulator.udid, error: "Simulator is already running" }; } // Start the simulator await this.execAsync(`xcrun simctl boot ${simulator.udid}`); // Wait for simulator to boot const bootResult = await this.waitForSimulatorBoot(simulator.udid, timeoutMs); if (bootResult) { return { success: true, simulatorName, udid: simulator.udid }; } else { return { success: false, simulatorName, error: "Simulator failed to boot within timeout period" }; } } catch (error) { logger.error(`Failed to start iOS simulator ${simulatorName}:`, error); return { success: false, simulatorName, error: error instanceof Error ? error.message : String(error) }; } } /** * Shut down an iOS simulator * @param simulatorName - Name of the simulator to shut down * @returns Promise with result */ async shutdownSimulator(simulatorName: string): Promise<{ success: boolean; simulatorName: string; error?: string; }> { try { logger.info(`Shutting down iOS simulator: ${simulatorName}`); // Find the simulator by name const simulators = await this.getSimulatorInfo(); const simulator = simulators.find(sim => sim.name === simulatorName); if (!simulator) { return { success: false, simulatorName, error: `Simulator '${simulatorName}' not found` }; } // Shut down the simulator await this.execAsync(`xcrun simctl shutdown ${simulator.udid}`); return { success: true, simulatorName }; } catch (error) { logger.error(`Failed to shutdown iOS simulator ${simulatorName}:`, error); return { success: false, simulatorName, error: error instanceof Error ? error.message : String(error) }; } } /** * Wait for simulator to boot * @param udid - Simulator UDID * @param timeoutMs - Timeout in milliseconds * @returns Promise with boolean indicating success */ private async waitForSimulatorBoot(udid: string, timeoutMs: number): Promise<boolean> { const startTime = Date.now(); while (Date.now() - startTime < timeoutMs) { try { const result = await this.execAsync(`xcrun simctl list devices --json`); const data = JSON.parse(result.stdout); // Find the simulator and check its state for (const runtime of Object.keys(data.devices)) { const device = data.devices[runtime].find((d: any) => d.udid === udid); if (device && device.state === "Booted") { logger.info(`iOS simulator ${udid} is now booted`); return true; } } // Wait before checking again await new Promise(resolve => setTimeout(resolve, 2000)); } catch (error) { logger.warn(`Error checking simulator boot status: ${error}`); } } logger.warn(`iOS simulator ${udid} failed to boot within ${timeoutMs}ms`); return false; } /** * Check if a specific simulator is running * @param simulatorName - Name of the simulator to check * @returns Promise with boolean indicating if running */ async isSimulatorRunning(simulatorName: string): Promise<boolean> { try { const runningSimulators = await this.getRunningSimulators(); return runningSimulators.some(sim => sim.name === simulatorName); } catch (error) { logger.error(`Failed to check if simulator ${simulatorName} is running:`, error); return false; } } /** * List installed apps on a simulator * @param udid - Simulator UDID * @returns Promise with array of installed app identifiers */ async listInstalledApps(udid: string): Promise<string[]> { try { logger.info(`Listing installed apps for simulator ${udid}`); const result = await this.execAsync(`xcrun simctl listapps ${udid}`); // Parse the output to extract app identifiers // The output format is typically JSON with app bundle identifiers try { const apps = JSON.parse(result.stdout); return Object.keys(apps); } catch { // If JSON parsing fails, try to extract app identifiers from text output const lines = result.stdout.split("\n"); return lines .filter(line => line.trim().length > 0) .map(line => line.trim()); } } catch (error) { logger.error("Failed to list installed apps:", error); return []; } } /** * Launch an app on a simulator * @param udid - Simulator UDID * @param appBundleId - App bundle identifier * @returns Promise that resolves when app launch is initiated */ async launchApp(udid: string, appBundleId: string): Promise<void> { try { logger.info(`Launching app ${appBundleId} on simulator ${udid}`); await this.execAsync(`xcrun simctl launch ${udid} ${appBundleId}`); } catch (error) { logger.error(`Failed to launch app ${appBundleId}:`, error); throw error; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zillow/auto-mobile'

If you have feedback or need assistance with the MCP directory API, please join our Discord server