#!/usr/bin/env node const { execSync, spawn } = require("node:child_process") const net = require("node:net") const readline = require("node:readline") function getAvailableDevices() { const raw = execSync("xcrun simctl list devices available -j", { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], }) const parsed = JSON.parse(raw) const devicesByRuntime = parsed.devices || {} const all = [] for (const [runtime, devices] of Object.entries(devicesByRuntime)) { for (const device of devices) { if (!device.isAvailable) continue if (device.name.includes("iPad") || device.name.includes("iPhone")) { all.push({ name: device.name, udid: device.udid, state: device.state, runtime, }) } } } all.sort((a, b) => a.name.localeCompare(b.name) || a.runtime.localeCompare(b.runtime)) return all } function promptChoice(max) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }) return new Promise((resolve, reject) => { rl.question("请输入要使用的机型编号: ", (answer) => { rl.close() const idx = Number(answer.trim()) if (!Number.isInteger(idx) || idx < 1 || idx > max) { reject(new Error(`无效编号: ${answer}`)) return } resolve(idx - 1) }) }) } function isPortListening(port, host = "127.0.0.1") { return new Promise((resolve) => { const socket = new net.Socket() let settled = false const finalize = (result) => { if (settled) return settled = true socket.destroy() resolve(result) } socket.setTimeout(500) socket.once("connect", () => finalize(true)) socket.once("timeout", () => finalize(false)) socket.once("error", () => finalize(false)) socket.connect(port, host) }) } async function hasListeningDevServer(port) { const checks = await Promise.all([ isPortListening(port, "127.0.0.1"), isPortListening(port, "::1"), isPortListening(port, "localhost"), ]) return checks.some(Boolean) } async function main() { const devices = getAvailableDevices() if (devices.length === 0) { throw new Error("未找到可用 iOS 模拟器,请先在 Xcode 安装模拟器运行时。") } console.log("可用 iOS 模拟器:") devices.forEach((d, i) => { const runtime = d.runtime.replace("com.apple.CoreSimulator.SimRuntime.", "") console.log(`${i + 1}. ${d.name} | ${runtime} | ${d.state} | ${d.udid}`) }) const pickedIndex = await promptChoice(devices.length) const picked = devices[pickedIndex] console.log(`\n已选择: ${picked.name} (${picked.runtime})`) const args = ["tauri", "ios", "dev", picked.name] const tauriDevPort = Number(process.env.TAURI_DEV_PORT || 1420) const hasExistingDevServer = await hasListeningDevServer(tauriDevPort) if (hasExistingDevServer) { console.log(`检测到 ${tauriDevPort} 端口已有 dev server,复用现有服务并跳过 beforeDevCommand。`) args.push( "--config", JSON.stringify({ build: { beforeDevCommand: `echo using-existing-dev-server-on-${tauriDevPort}`, }, }) ) } const child = spawn("pnpm", args, { stdio: "inherit", shell: process.platform === "win32", }) child.on("exit", (code) => { process.exit(code ?? 1) }) } main().catch((err) => { console.error(`执行失败: ${err.message}`) process.exit(1) })