basant307/AI_Governance_Project
048
1import { existsSync } from "node:fs";2import { dirname, join, sep } from "node:path";3import { execFileSync, spawn, ChildProcess } from "node:child_process";4 5export interface MobilecliCrashEntry {6 processName: string;7 timestamp: string;8 id: string;9}10 11export interface MobilecliCrashesListResponse {12 status: "ok";13 data: MobilecliCrashEntry[];14}15 16export interface MobilecliCrashGetResponse {17 status: "ok";18 data: {19 content: string;20 id: string;21 };22}23 24export interface MobilecliAgentStatusResponse {25 status: "ok" | "fail";26 data: {27 message: string;28 };29}30 31export interface MobilecliDevicesOptions {32 includeOffline?: boolean;33 platform?: "ios" | "android";34 type?: "real" | "emulator" | "simulator";35}36 37export interface MobilecliDeviceProvider {38 type: string; // e.g. "mobilefleet" for remote devices39 allocationId?: string;40}41 42export interface MobilecliDevice {43 id: string;44 name: string;45 platform: "android" | "ios";46 type: "real" | "emulator" | "simulator";47 version: string;48 provider?: MobilecliDeviceProvider;49}50 51export interface MobilecliDevicesResponse {52 status: "ok";53 data: {54 devices: MobilecliDevice[];55 };56}57 58const TIMEOUT = 30000;59const MAX_BUFFER_SIZE = 1024 * 1024 * 8;60 61export class Mobilecli {62 private path: string | null = null;63 64 constructor() { }65 66 private getPath(): string {67 if (!this.path) {68 this.path = Mobilecli.getMobilecliPath();69 }70 return this.path;71 }72 73 public executeCommand(args: string[]): string {74 const path = this.getPath();75 return execFileSync(path, args, { encoding: "utf8" }).toString().trim();76 }77 78 public spawnCommand(args: string[]): ChildProcess {79 const binaryPath = this.getPath();80 return spawn(binaryPath, args, {81 stdio: ["ignore", "ignore", "ignore"],82 });83 }84 85 public executeCommandBuffer(args: string[]): Buffer {86 const path = this.getPath();87 return execFileSync(path, args, {88 encoding: "buffer",89 maxBuffer: MAX_BUFFER_SIZE,90 timeout: TIMEOUT,91 }) as Buffer;92 }93 94 private static getMobilecliPath(): string {95 if (process.env.MOBILECLI_PATH) {96 return process.env.MOBILECLI_PATH;97 }98 99 const platform = process.platform;100 const arch = process.arch;101 102 const normalizedPlatform = platform === "win32" ? "windows" : platform;103 const normalizedArch = arch === "arm64" ? "arm64" : "amd64";104 const ext = platform === "win32" ? ".exe" : "";105 const binaryName = `mobilecli-${normalizedPlatform}-${normalizedArch}${ext}`;106 107 // Check if mobile-mcp is installed as a package108 const currentPath = __filename;109 const pathParts = currentPath.split(sep);110 const lastNodeModulesIndex = pathParts.lastIndexOf("node_modules");111 112 if (lastNodeModulesIndex !== -1) {113 // We're inside node_modules, go to the last node_modules in the path114 const nodeModulesParts = pathParts.slice(0, lastNodeModulesIndex + 1);115 const lastNodeModulesPath = nodeModulesParts.join(sep);116 const mobilecliPath = join(lastNodeModulesPath, "mobilecli", "bin", binaryName);117 118 if (existsSync(mobilecliPath)) {119 return mobilecliPath;120 }121 }122 123 // Not in node_modules, look one directory up from current script124 const scriptDir = dirname(__filename);125 const parentDir = dirname(scriptDir);126 const mobilecliPath = join(parentDir, "node_modules", "mobilecli", "bin", binaryName);127 128 if (existsSync(mobilecliPath)) {129 return mobilecliPath;130 }131 132 throw new Error(`Could not find mobilecli binary for platform: ${platform}`);133 }134 135 getVersion(): string {136 try {137 const output = this.executeCommand(["--version"]);138 if (output.startsWith("mobilecli version ")) {139 return output.substring("mobilecli version ".length);140 }141 142 return "failed";143 } catch (error: any) {144 return "failed " + error.message;145 }146 }147 148 remoteListDevices(): string {149 return this.executeCommand(["remote", "list-devices"]);150 }151 152 remoteAllocate(platform: "ios" | "android"): string {153 return this.executeCommand(["remote", "allocate", "--platform", platform]);154 }155 156 remoteRelease(deviceId: string): string {157 return this.executeCommand(["remote", "release", "--device", deviceId]);158 }159 160 crashesList(deviceId: string): MobilecliCrashesListResponse {161 const output = this.executeCommand(["device", "crashes", "list", "--device", deviceId]);162 return JSON.parse(output) as MobilecliCrashesListResponse;163 }164 165 crashesGet(deviceId: string, id: string): MobilecliCrashGetResponse {166 const output = this.executeCommandBuffer(["device", "crashes", "get", id, "--device", deviceId]);167 return JSON.parse(output.toString().trim()) as MobilecliCrashGetResponse;168 }169 170 agentStatus(deviceId: string): MobilecliAgentStatusResponse {171 const output = this.executeCommand(["agent", "status", "--device", deviceId]);172 return JSON.parse(output) as MobilecliAgentStatusResponse;173 }174 175 agentInstall(deviceId: string): void {176 this.executeCommand(["agent", "install", "--device", deviceId]);177 }178 179 getDevices(options?: MobilecliDevicesOptions): MobilecliDevicesResponse {180 const args = ["devices"];181 182 if (options) {183 if (options.includeOffline) {184 args.push("--include-offline");185 }186 187 if (options.platform) {188 if (options.platform !== "ios" && options.platform !== "android") {189 throw new Error(`Invalid platform: ${options.platform}. Must be "ios" or "android"`);190 }191 192 args.push("--platform", options.platform);193 }194 195 if (options.type) {196 if (options.type !== "real" && options.type !== "emulator" && options.type !== "simulator") {197 throw new Error(`Invalid type: ${options.type}. Must be "real", "emulator", or "simulator"`);198 }199 200 args.push("--type", options.type);201 }202 }203 204 const mobilecliOutput = this.executeCommand(args);205 return JSON.parse(mobilecliOutput) as MobilecliDevicesResponse;206 }207}208 