@ceraph/react-native-mcp 0.2.2 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +116 -15
- package/README.md +79 -77
- package/assets/default.png +0 -0
- package/dist/app-lifecycle.d.ts +50 -0
- package/dist/app-lifecycle.js +487 -0
- package/dist/camera-image-writer.d.ts +43 -0
- package/dist/camera-image-writer.js +280 -0
- package/dist/camera-registry-sync.d.ts +18 -0
- package/dist/camera-registry-sync.js +117 -0
- package/dist/cli.d.ts +0 -7
- package/dist/cli.js +41 -9
- package/dist/device-autonomy.d.ts +30 -0
- package/dist/device-autonomy.js +117 -0
- package/dist/error-parser.d.ts +6 -26
- package/dist/error-parser.js +4 -74
- package/dist/expo-manager.d.ts +2 -74
- package/dist/expo-manager.js +11 -125
- package/dist/index.d.ts +0 -7
- package/dist/index.js +1266 -56
- package/dist/init/ast-camera.d.ts +29 -0
- package/dist/init/ast-camera.js +267 -0
- package/dist/init/ast-layout.d.ts +15 -0
- package/dist/init/ast-layout.js +167 -0
- package/dist/init/claude-hook-constants.d.ts +9 -0
- package/dist/init/claude-hook-constants.js +91 -0
- package/dist/init/lan-ip.d.ts +11 -0
- package/dist/init/lan-ip.js +51 -0
- package/dist/init/monorepo.d.ts +13 -0
- package/dist/init/monorepo.js +185 -0
- package/dist/init/oauth.d.ts +52 -0
- package/dist/init/oauth.js +220 -0
- package/dist/init/package-manager.d.ts +11 -0
- package/dist/init/package-manager.js +60 -0
- package/dist/init/prompt.d.ts +12 -0
- package/dist/init/prompt.js +68 -0
- package/dist/init/shell-profile.d.ts +22 -0
- package/dist/init/shell-profile.js +85 -0
- package/dist/init/steps.d.ts +135 -0
- package/dist/init/steps.js +399 -0
- package/dist/init/url-scheme.d.ts +42 -0
- package/dist/init/url-scheme.js +187 -0
- package/dist/init/walkthrough.d.ts +76 -0
- package/dist/init/walkthrough.js +340 -0
- package/dist/init.d.ts +7 -7
- package/dist/init.js +280 -120
- package/dist/iproxy-manager.d.ts +32 -0
- package/dist/iproxy-manager.js +216 -0
- package/dist/mac-caffeinate.d.ts +10 -0
- package/dist/mac-caffeinate.js +56 -0
- package/dist/permission-interceptor.d.ts +29 -0
- package/dist/permission-interceptor.js +185 -0
- package/dist/prebuild-detector.d.ts +0 -30
- package/dist/prebuild-detector.js +1 -42
- package/dist/preflight.d.ts +34 -0
- package/dist/preflight.js +847 -0
- package/dist/screen.d.ts +132 -43
- package/dist/screen.js +668 -94
- package/dist/shim/boot.d.ts +41 -0
- package/dist/shim/boot.js +141 -0
- package/dist/shim/camera.d.ts +22 -0
- package/dist/shim/camera.js +62 -0
- package/dist/shim/config.d.ts +6 -0
- package/dist/shim/config.js +56 -0
- package/dist/shim/deep-link.d.ts +1 -0
- package/dist/shim/deep-link.js +25 -0
- package/dist/shim/dev-guard.d.ts +1 -0
- package/dist/shim/dev-guard.js +3 -0
- package/dist/shim/error-handler.d.ts +20 -0
- package/dist/shim/error-handler.js +66 -0
- package/dist/shim/fetch-interceptor.d.ts +13 -0
- package/dist/shim/fetch-interceptor.js +93 -0
- package/dist/shim/index.d.ts +6 -0
- package/dist/shim/index.js +6 -0
- package/dist/shim/keep-awake.d.ts +13 -0
- package/dist/shim/keep-awake.js +118 -0
- package/dist/shim/reload.d.ts +23 -0
- package/dist/shim/reload.js +76 -0
- package/dist/shim/signal-capture.d.ts +11 -0
- package/dist/shim/signal-capture.js +15 -0
- package/dist/shim/signal-transport.d.ts +17 -0
- package/dist/shim/signal-transport.js +43 -0
- package/dist/signal-listener.d.ts +27 -0
- package/dist/signal-listener.js +135 -0
- package/dist/simulator-boot.d.ts +52 -0
- package/dist/simulator-boot.js +227 -0
- package/dist/target.d.ts +48 -0
- package/dist/target.js +267 -0
- package/dist/uninstall/cli-runner.d.ts +32 -0
- package/dist/uninstall/cli-runner.js +223 -0
- package/dist/uninstall/footprint.d.ts +40 -0
- package/dist/uninstall/footprint.js +288 -0
- package/dist/uninstall/mcp-tools.d.ts +14 -0
- package/dist/uninstall/mcp-tools.js +175 -0
- package/dist/uninstall/revert-auth.d.ts +22 -0
- package/dist/uninstall/revert-auth.js +31 -0
- package/dist/uninstall/revert-boot.d.ts +24 -0
- package/dist/uninstall/revert-boot.js +242 -0
- package/dist/uninstall/revert-camera.d.ts +12 -0
- package/dist/uninstall/revert-camera.js +199 -0
- package/dist/uninstall/revert-ceraph-dir.d.ts +27 -0
- package/dist/uninstall/revert-ceraph-dir.js +38 -0
- package/dist/uninstall/revert-claude-hooks.d.ts +19 -0
- package/dist/uninstall/revert-claude-hooks.js +191 -0
- package/dist/uninstall/revert-gitignore.d.ts +17 -0
- package/dist/uninstall/revert-gitignore.js +43 -0
- package/dist/uninstall/revert-mcp-clients.d.ts +57 -0
- package/dist/uninstall/revert-mcp-clients.js +194 -0
- package/dist/uninstall/revert-package.d.ts +34 -0
- package/dist/uninstall/revert-package.js +98 -0
- package/dist/uninstall/revert-scheme.d.ts +36 -0
- package/dist/uninstall/revert-scheme.js +139 -0
- package/dist/uninstall/revert-signal-host-env.d.ts +31 -0
- package/dist/uninstall/revert-signal-host-env.js +61 -0
- package/dist/uninstall/walkthrough.d.ts +80 -0
- package/dist/uninstall/walkthrough.js +1244 -0
- package/dist/utils/atomic-write.d.ts +1 -0
- package/dist/utils/atomic-write.js +30 -0
- package/dist/wait-for-device.d.ts +68 -0
- package/dist/wait-for-device.js +368 -0
- package/dist/wda-manager.d.ts +38 -0
- package/dist/wda-manager.js +186 -0
- package/dist/wda-simulator.d.ts +28 -0
- package/dist/wda-simulator.js +257 -0
- package/package.json +59 -5
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
export async function runXcrun(cmd, args, timeoutMs = 30_000) {
|
|
3
|
+
return new Promise((resolve) => {
|
|
4
|
+
let stdout = "";
|
|
5
|
+
let stderr = "";
|
|
6
|
+
let child;
|
|
7
|
+
try {
|
|
8
|
+
child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
9
|
+
}
|
|
10
|
+
catch (err) {
|
|
11
|
+
resolve({
|
|
12
|
+
code: 127,
|
|
13
|
+
stdout: "",
|
|
14
|
+
stderr: err instanceof Error ? err.message : String(err),
|
|
15
|
+
});
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const timer = setTimeout(() => {
|
|
19
|
+
try {
|
|
20
|
+
child.kill("SIGKILL");
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
}
|
|
24
|
+
}, timeoutMs);
|
|
25
|
+
child.stdout?.on("data", (d) => {
|
|
26
|
+
stdout += d.toString();
|
|
27
|
+
});
|
|
28
|
+
child.stderr?.on("data", (d) => {
|
|
29
|
+
stderr += d.toString();
|
|
30
|
+
});
|
|
31
|
+
let settled = false;
|
|
32
|
+
child.on("error", (err) => {
|
|
33
|
+
if (settled)
|
|
34
|
+
return;
|
|
35
|
+
settled = true;
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
resolve({
|
|
38
|
+
code: 127,
|
|
39
|
+
stdout,
|
|
40
|
+
stderr: stderr + (err.message ?? String(err)),
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
child.on("exit", (code) => {
|
|
44
|
+
if (settled)
|
|
45
|
+
return;
|
|
46
|
+
settled = true;
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
resolve({ code: code ?? 1, stdout, stderr });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export const defaultBootSimulatorDeps = {
|
|
53
|
+
runner: runXcrun,
|
|
54
|
+
};
|
|
55
|
+
export function parseIosRuntimeVersion(runtimeKey) {
|
|
56
|
+
const lower = runtimeKey.toLowerCase();
|
|
57
|
+
if (!lower.includes("ios") && !lower.includes("iphone"))
|
|
58
|
+
return null;
|
|
59
|
+
const re = /ios[-\s]?(\d+)(?:[-.](\d+))?/i;
|
|
60
|
+
const m = re.exec(runtimeKey);
|
|
61
|
+
if (!m)
|
|
62
|
+
return null;
|
|
63
|
+
const major = Number(m[1]);
|
|
64
|
+
const minor = m[2] ? Number(m[2]) : 0;
|
|
65
|
+
if (!Number.isFinite(major))
|
|
66
|
+
return null;
|
|
67
|
+
return { major, minor, label: `iOS ${major}${m[2] ? "." + minor : ""}` };
|
|
68
|
+
}
|
|
69
|
+
const VARIANT_MODIFIERS = ["pro", "max", "plus", "mini", "se", "air"];
|
|
70
|
+
function parseModelNumber(name) {
|
|
71
|
+
const m = /\b(\d+)\b/.exec(name);
|
|
72
|
+
if (!m)
|
|
73
|
+
return 0;
|
|
74
|
+
const n = Number(m[1]);
|
|
75
|
+
return Number.isFinite(n) ? n : 0;
|
|
76
|
+
}
|
|
77
|
+
function countModifiers(name) {
|
|
78
|
+
const tokens = name.toLowerCase().split(/\s+/);
|
|
79
|
+
let count = 0;
|
|
80
|
+
for (const t of tokens) {
|
|
81
|
+
if (VARIANT_MODIFIERS.includes(t))
|
|
82
|
+
count++;
|
|
83
|
+
}
|
|
84
|
+
return count;
|
|
85
|
+
}
|
|
86
|
+
export function pickBestSimulator(list, deviceType) {
|
|
87
|
+
const wanted = (deviceType ?? "").trim().toLowerCase();
|
|
88
|
+
const candidates = [];
|
|
89
|
+
for (const [runtimeKey, devices] of Object.entries(list.devices ?? {})) {
|
|
90
|
+
const runtime = parseIosRuntimeVersion(runtimeKey);
|
|
91
|
+
if (!runtime)
|
|
92
|
+
continue;
|
|
93
|
+
if (!Array.isArray(devices))
|
|
94
|
+
continue;
|
|
95
|
+
for (const dev of devices) {
|
|
96
|
+
if (!dev.udid || !dev.name)
|
|
97
|
+
continue;
|
|
98
|
+
if (dev.isAvailable === false)
|
|
99
|
+
continue;
|
|
100
|
+
const lowerName = dev.name.toLowerCase();
|
|
101
|
+
if (wanted && !lowerName.includes(wanted))
|
|
102
|
+
continue;
|
|
103
|
+
candidates.push({
|
|
104
|
+
udid: dev.udid,
|
|
105
|
+
name: dev.name,
|
|
106
|
+
runtimeLabel: runtime.label,
|
|
107
|
+
state: dev.state ?? "Unknown",
|
|
108
|
+
major: runtime.major,
|
|
109
|
+
minor: runtime.minor,
|
|
110
|
+
modifiers: countModifiers(dev.name),
|
|
111
|
+
modelNumber: parseModelNumber(dev.name),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (candidates.length === 0)
|
|
116
|
+
return null;
|
|
117
|
+
candidates.sort((a, b) => {
|
|
118
|
+
if (b.major !== a.major)
|
|
119
|
+
return b.major - a.major;
|
|
120
|
+
if (b.minor !== a.minor)
|
|
121
|
+
return b.minor - a.minor;
|
|
122
|
+
if (a.modifiers !== b.modifiers)
|
|
123
|
+
return a.modifiers - b.modifiers;
|
|
124
|
+
if (b.modelNumber !== a.modelNumber)
|
|
125
|
+
return b.modelNumber - a.modelNumber;
|
|
126
|
+
return a.name.localeCompare(b.name);
|
|
127
|
+
});
|
|
128
|
+
const top = candidates[0];
|
|
129
|
+
return {
|
|
130
|
+
udid: top.udid,
|
|
131
|
+
name: top.name,
|
|
132
|
+
runtimeLabel: top.runtimeLabel,
|
|
133
|
+
state: top.state,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
export function findSimulatorByUdid(list, udid) {
|
|
137
|
+
for (const [runtimeKey, devices] of Object.entries(list.devices ?? {})) {
|
|
138
|
+
const runtime = parseIosRuntimeVersion(runtimeKey);
|
|
139
|
+
if (!runtime)
|
|
140
|
+
continue;
|
|
141
|
+
if (!Array.isArray(devices))
|
|
142
|
+
continue;
|
|
143
|
+
for (const dev of devices) {
|
|
144
|
+
if (dev.udid !== udid)
|
|
145
|
+
continue;
|
|
146
|
+
return {
|
|
147
|
+
udid: dev.udid,
|
|
148
|
+
name: dev.name ?? udid,
|
|
149
|
+
runtimeLabel: runtime.label,
|
|
150
|
+
state: dev.state ?? "Unknown",
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
export function isAlreadyBootedError(stderr) {
|
|
157
|
+
const s = stderr.toLowerCase();
|
|
158
|
+
return (s.includes("unable to boot device in current state") &&
|
|
159
|
+
s.includes("booted"));
|
|
160
|
+
}
|
|
161
|
+
export async function bootSimulator(opts = {}, deps = defaultBootSimulatorDeps) {
|
|
162
|
+
const bootTimeoutMs = opts.bootTimeoutMs ?? 120_000;
|
|
163
|
+
const runner = deps.runner;
|
|
164
|
+
let chosen;
|
|
165
|
+
const listResult = await runner("xcrun", ["simctl", "list", "devices", "--json"], 15_000);
|
|
166
|
+
if (listResult.code === 127) {
|
|
167
|
+
throw new Error("`xcrun` not found on PATH. Install Xcode Command Line Tools " +
|
|
168
|
+
"(`xcode-select --install`) and try again.");
|
|
169
|
+
}
|
|
170
|
+
if (listResult.code !== 0) {
|
|
171
|
+
throw new Error(`xcrun simctl list devices failed (exit ${listResult.code}): ${listResult.stderr || listResult.stdout || "no output"}`);
|
|
172
|
+
}
|
|
173
|
+
let parsed;
|
|
174
|
+
try {
|
|
175
|
+
parsed = JSON.parse(listResult.stdout);
|
|
176
|
+
}
|
|
177
|
+
catch (err) {
|
|
178
|
+
throw new Error(`Failed to parse simctl device list as JSON: ${err instanceof Error ? err.message : String(err)}`);
|
|
179
|
+
}
|
|
180
|
+
if (opts.udid) {
|
|
181
|
+
const found = findSimulatorByUdid(parsed, opts.udid);
|
|
182
|
+
if (!found) {
|
|
183
|
+
throw new Error(`No iOS simulator with udid ${opts.udid} found in the simctl ` +
|
|
184
|
+
"listing. Run `xcrun simctl list devices` to see available udids.");
|
|
185
|
+
}
|
|
186
|
+
chosen = found;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
const picked = pickBestSimulator(parsed, opts.deviceType);
|
|
190
|
+
if (!picked) {
|
|
191
|
+
throw new Error(opts.deviceType
|
|
192
|
+
? `No iOS simulators matching "${opts.deviceType}" are available. ` +
|
|
193
|
+
"Open Xcode → Settings → Platforms → install an iOS simulator " +
|
|
194
|
+
"runtime, or pick a different deviceType."
|
|
195
|
+
: "No iOS simulators are available on this Mac. Open Xcode → " +
|
|
196
|
+
"Settings → Platforms → install an iOS simulator runtime.");
|
|
197
|
+
}
|
|
198
|
+
chosen = picked;
|
|
199
|
+
}
|
|
200
|
+
if (chosen.state.toLowerCase() === "booted") {
|
|
201
|
+
return {
|
|
202
|
+
udid: chosen.udid,
|
|
203
|
+
deviceType: chosen.name,
|
|
204
|
+
alreadyBooted: true,
|
|
205
|
+
booted: true,
|
|
206
|
+
runtime: chosen.runtimeLabel,
|
|
207
|
+
durationMs: 0,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const start = Date.now();
|
|
211
|
+
const bootResult = await runner("xcrun", ["simctl", "boot", chosen.udid], 30_000);
|
|
212
|
+
if (bootResult.code !== 0 && !isAlreadyBootedError(bootResult.stderr)) {
|
|
213
|
+
throw new Error(`Failed to boot simulator ${chosen.name} (${chosen.udid}): ${bootResult.stderr || bootResult.stdout || "no output"}`);
|
|
214
|
+
}
|
|
215
|
+
const bootstatus = await runner("xcrun", ["simctl", "bootstatus", chosen.udid, "-b"], bootTimeoutMs + 5_000);
|
|
216
|
+
if (bootstatus.code !== 0) {
|
|
217
|
+
throw new Error(`simctl bootstatus failed for ${chosen.name} (${chosen.udid}): ${bootstatus.stderr || bootstatus.stdout || "no output"}`);
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
udid: chosen.udid,
|
|
221
|
+
deviceType: chosen.name,
|
|
222
|
+
alreadyBooted: false,
|
|
223
|
+
booted: true,
|
|
224
|
+
runtime: chosen.runtimeLabel,
|
|
225
|
+
durationMs: Date.now() - start,
|
|
226
|
+
};
|
|
227
|
+
}
|
package/dist/target.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export type Target = "device" | "simulator";
|
|
2
|
+
export type TargetPreference = Target | "auto";
|
|
3
|
+
export interface TargetInfo {
|
|
4
|
+
target: Target;
|
|
5
|
+
udid: string | null;
|
|
6
|
+
baseUrl: string;
|
|
7
|
+
wdaReady: boolean;
|
|
8
|
+
reason: "env-override-device" | "env-override-simulator" | "auto-prefer-device" | "auto-only-simulator" | "auto-no-target-found";
|
|
9
|
+
}
|
|
10
|
+
export interface BootedSimulator {
|
|
11
|
+
udid: string;
|
|
12
|
+
name: string;
|
|
13
|
+
runtime?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface AppLifecycleLike {
|
|
16
|
+
getUdid(): Promise<string | null>;
|
|
17
|
+
}
|
|
18
|
+
export declare function preferenceFromEnv(raw: string | undefined): TargetPreference;
|
|
19
|
+
export declare function listBootedSimulators(): Promise<BootedSimulator[]>;
|
|
20
|
+
export declare function parseWdaPort(stdout: string): number | null;
|
|
21
|
+
export declare const DEFAULT_DEVICE_WDA_BASE_URL = "http://localhost:8100";
|
|
22
|
+
export declare function simulatorWdaBaseUrl(port: number): string;
|
|
23
|
+
export declare class TargetResolver {
|
|
24
|
+
private apps;
|
|
25
|
+
private readPreference;
|
|
26
|
+
private cached;
|
|
27
|
+
private cachedAt;
|
|
28
|
+
private readonly cacheTtlMs;
|
|
29
|
+
private inflight;
|
|
30
|
+
private stateVersion;
|
|
31
|
+
private simulatorWdaPort;
|
|
32
|
+
private simulatorWdaUdid;
|
|
33
|
+
constructor(opts?: {
|
|
34
|
+
apps?: AppLifecycleLike;
|
|
35
|
+
readPreference?: () => TargetPreference;
|
|
36
|
+
cacheTtlMs?: number;
|
|
37
|
+
});
|
|
38
|
+
setAppLifecycle(apps: AppLifecycleLike): void;
|
|
39
|
+
setSimulatorWdaPort(port: number, udid: string): void;
|
|
40
|
+
clearSimulatorWdaPort(): void;
|
|
41
|
+
getSimulatorWdaPort(): number | null;
|
|
42
|
+
getSimulatorWdaUdid(): string | null;
|
|
43
|
+
invalidate(): void;
|
|
44
|
+
getBaseUrl(): Promise<string>;
|
|
45
|
+
getTarget(): Promise<Target>;
|
|
46
|
+
resolve(): Promise<TargetInfo>;
|
|
47
|
+
reset(): void;
|
|
48
|
+
}
|
package/dist/target.js
ADDED
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
function runCommand(cmd, args, opts = {}) {
|
|
3
|
+
return new Promise((resolve) => {
|
|
4
|
+
let stdout = "";
|
|
5
|
+
let stderr = "";
|
|
6
|
+
let child;
|
|
7
|
+
try {
|
|
8
|
+
child = spawn(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
|
|
9
|
+
}
|
|
10
|
+
catch (err) {
|
|
11
|
+
resolve({
|
|
12
|
+
code: 127,
|
|
13
|
+
stdout: "",
|
|
14
|
+
stderr: err instanceof Error ? err.message : String(err),
|
|
15
|
+
});
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const timer = setTimeout(() => {
|
|
19
|
+
try {
|
|
20
|
+
child.kill("SIGKILL");
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
}
|
|
24
|
+
}, opts.timeoutMs ?? 8000);
|
|
25
|
+
child.stdout?.on("data", (d) => {
|
|
26
|
+
stdout += d.toString();
|
|
27
|
+
});
|
|
28
|
+
child.stderr?.on("data", (d) => {
|
|
29
|
+
stderr += d.toString();
|
|
30
|
+
});
|
|
31
|
+
let settled = false;
|
|
32
|
+
child.on("error", (err) => {
|
|
33
|
+
if (settled)
|
|
34
|
+
return;
|
|
35
|
+
settled = true;
|
|
36
|
+
clearTimeout(timer);
|
|
37
|
+
resolve({
|
|
38
|
+
code: 127,
|
|
39
|
+
stdout,
|
|
40
|
+
stderr: stderr + (err.message ?? String(err)),
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
child.on("exit", (code) => {
|
|
44
|
+
if (settled)
|
|
45
|
+
return;
|
|
46
|
+
settled = true;
|
|
47
|
+
clearTimeout(timer);
|
|
48
|
+
resolve({ code: code ?? 1, stdout, stderr });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
export function preferenceFromEnv(raw) {
|
|
53
|
+
const v = (raw ?? "").trim().toLowerCase();
|
|
54
|
+
if (v === "device")
|
|
55
|
+
return "device";
|
|
56
|
+
if (v === "simulator" || v === "sim")
|
|
57
|
+
return "simulator";
|
|
58
|
+
return "auto";
|
|
59
|
+
}
|
|
60
|
+
export async function listBootedSimulators() {
|
|
61
|
+
const res = await runCommand("xcrun", [
|
|
62
|
+
"simctl",
|
|
63
|
+
"list",
|
|
64
|
+
"devices",
|
|
65
|
+
"booted",
|
|
66
|
+
"-j",
|
|
67
|
+
]);
|
|
68
|
+
if (res.code !== 0)
|
|
69
|
+
return [];
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(res.stdout);
|
|
72
|
+
const out = [];
|
|
73
|
+
const devices = parsed.devices ?? {};
|
|
74
|
+
for (const [runtimeKey, list] of Object.entries(devices)) {
|
|
75
|
+
const isIos = runtimeKey.toLowerCase().includes("ios") ||
|
|
76
|
+
runtimeKey.toLowerCase().includes("iphone");
|
|
77
|
+
if (!isIos)
|
|
78
|
+
continue;
|
|
79
|
+
if (!Array.isArray(list))
|
|
80
|
+
continue;
|
|
81
|
+
for (const dev of list) {
|
|
82
|
+
if (!dev.udid)
|
|
83
|
+
continue;
|
|
84
|
+
if ((dev.state ?? "").toLowerCase() !== "booted")
|
|
85
|
+
continue;
|
|
86
|
+
out.push({
|
|
87
|
+
udid: dev.udid,
|
|
88
|
+
name: dev.name ?? dev.udid,
|
|
89
|
+
runtime: dev.runtime ?? runtimeKey,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return [];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export function parseWdaPort(stdout) {
|
|
100
|
+
const re = /ServerURLHere->https?:\/\/[^:/\s]+:(\d{2,5})<-ServerURLHere/;
|
|
101
|
+
const m = re.exec(stdout);
|
|
102
|
+
if (!m)
|
|
103
|
+
return null;
|
|
104
|
+
const port = Number(m[1]);
|
|
105
|
+
if (!Number.isFinite(port) || port < 1 || port > 65535)
|
|
106
|
+
return null;
|
|
107
|
+
return port;
|
|
108
|
+
}
|
|
109
|
+
export const DEFAULT_DEVICE_WDA_BASE_URL = "http://localhost:8100";
|
|
110
|
+
export function simulatorWdaBaseUrl(port) {
|
|
111
|
+
return `http://localhost:${port}`;
|
|
112
|
+
}
|
|
113
|
+
export class TargetResolver {
|
|
114
|
+
apps;
|
|
115
|
+
readPreference;
|
|
116
|
+
cached = null;
|
|
117
|
+
cachedAt = 0;
|
|
118
|
+
cacheTtlMs;
|
|
119
|
+
inflight = null;
|
|
120
|
+
stateVersion = 0;
|
|
121
|
+
simulatorWdaPort = null;
|
|
122
|
+
simulatorWdaUdid = null;
|
|
123
|
+
constructor(opts = {}) {
|
|
124
|
+
this.apps = opts.apps ?? null;
|
|
125
|
+
this.readPreference = opts.readPreference ?? (() => preferenceFromEnv(process.env.CERAPH_TARGET));
|
|
126
|
+
this.cacheTtlMs = opts.cacheTtlMs ?? 5_000;
|
|
127
|
+
}
|
|
128
|
+
setAppLifecycle(apps) {
|
|
129
|
+
this.apps = apps;
|
|
130
|
+
this.cached = null;
|
|
131
|
+
this.stateVersion++;
|
|
132
|
+
}
|
|
133
|
+
setSimulatorWdaPort(port, udid) {
|
|
134
|
+
this.simulatorWdaPort = port;
|
|
135
|
+
this.simulatorWdaUdid = udid;
|
|
136
|
+
this.cached = null;
|
|
137
|
+
this.stateVersion++;
|
|
138
|
+
}
|
|
139
|
+
clearSimulatorWdaPort() {
|
|
140
|
+
this.simulatorWdaPort = null;
|
|
141
|
+
this.simulatorWdaUdid = null;
|
|
142
|
+
this.cached = null;
|
|
143
|
+
this.stateVersion++;
|
|
144
|
+
}
|
|
145
|
+
getSimulatorWdaPort() {
|
|
146
|
+
return this.simulatorWdaPort;
|
|
147
|
+
}
|
|
148
|
+
getSimulatorWdaUdid() {
|
|
149
|
+
return this.simulatorWdaUdid;
|
|
150
|
+
}
|
|
151
|
+
invalidate() {
|
|
152
|
+
this.cached = null;
|
|
153
|
+
this.stateVersion++;
|
|
154
|
+
}
|
|
155
|
+
async getBaseUrl() {
|
|
156
|
+
const info = await this.resolve();
|
|
157
|
+
return info.baseUrl;
|
|
158
|
+
}
|
|
159
|
+
async getTarget() {
|
|
160
|
+
const info = await this.resolve();
|
|
161
|
+
return info.target;
|
|
162
|
+
}
|
|
163
|
+
async resolve() {
|
|
164
|
+
if (this.cached &&
|
|
165
|
+
this.cacheTtlMs > 0 &&
|
|
166
|
+
Date.now() - this.cachedAt < this.cacheTtlMs) {
|
|
167
|
+
return this.cached;
|
|
168
|
+
}
|
|
169
|
+
if (this.inflight)
|
|
170
|
+
return this.inflight;
|
|
171
|
+
const versionAtStart = this.stateVersion;
|
|
172
|
+
const work = (async () => {
|
|
173
|
+
const pref = this.readPreference();
|
|
174
|
+
if (pref === "device") {
|
|
175
|
+
const udid = (await this.apps?.getUdid()) ?? null;
|
|
176
|
+
return {
|
|
177
|
+
target: "device",
|
|
178
|
+
udid,
|
|
179
|
+
baseUrl: DEFAULT_DEVICE_WDA_BASE_URL,
|
|
180
|
+
wdaReady: true,
|
|
181
|
+
reason: "env-override-device",
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
if (pref === "simulator") {
|
|
185
|
+
const sims = await listBootedSimulators();
|
|
186
|
+
const picked = this.simulatorWdaUdid
|
|
187
|
+
? sims.find((s) => s.udid === this.simulatorWdaUdid) ?? sims[0]
|
|
188
|
+
: sims[0];
|
|
189
|
+
const pickedUdid = picked?.udid ?? this.simulatorWdaUdid ?? null;
|
|
190
|
+
const wdaHostStillBooted = this.simulatorWdaUdid !== null &&
|
|
191
|
+
sims.some((s) => s.udid === this.simulatorWdaUdid);
|
|
192
|
+
const portMatchesSim = this.simulatorWdaPort !== null &&
|
|
193
|
+
this.simulatorWdaUdid !== null &&
|
|
194
|
+
wdaHostStillBooted &&
|
|
195
|
+
pickedUdid === this.simulatorWdaUdid;
|
|
196
|
+
return {
|
|
197
|
+
target: "simulator",
|
|
198
|
+
udid: pickedUdid,
|
|
199
|
+
baseUrl: portMatchesSim
|
|
200
|
+
? simulatorWdaBaseUrl(this.simulatorWdaPort)
|
|
201
|
+
: DEFAULT_DEVICE_WDA_BASE_URL,
|
|
202
|
+
wdaReady: portMatchesSim,
|
|
203
|
+
reason: "env-override-simulator",
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
const [deviceUdid, sims] = await Promise.all([
|
|
207
|
+
this.apps?.getUdid() ?? Promise.resolve(null),
|
|
208
|
+
listBootedSimulators(),
|
|
209
|
+
]);
|
|
210
|
+
if (deviceUdid) {
|
|
211
|
+
return {
|
|
212
|
+
target: "device",
|
|
213
|
+
udid: deviceUdid,
|
|
214
|
+
baseUrl: DEFAULT_DEVICE_WDA_BASE_URL,
|
|
215
|
+
wdaReady: true,
|
|
216
|
+
reason: "auto-prefer-device",
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
if (sims.length > 0) {
|
|
220
|
+
const picked = this.simulatorWdaUdid
|
|
221
|
+
? sims.find((s) => s.udid === this.simulatorWdaUdid) ?? sims[0]
|
|
222
|
+
: sims[0];
|
|
223
|
+
const wdaHostStillBooted = this.simulatorWdaUdid !== null &&
|
|
224
|
+
sims.some((s) => s.udid === this.simulatorWdaUdid);
|
|
225
|
+
const portMatchesSim = this.simulatorWdaPort !== null &&
|
|
226
|
+
this.simulatorWdaUdid !== null &&
|
|
227
|
+
wdaHostStillBooted &&
|
|
228
|
+
picked.udid === this.simulatorWdaUdid;
|
|
229
|
+
return {
|
|
230
|
+
target: "simulator",
|
|
231
|
+
udid: picked.udid,
|
|
232
|
+
baseUrl: portMatchesSim
|
|
233
|
+
? simulatorWdaBaseUrl(this.simulatorWdaPort)
|
|
234
|
+
: DEFAULT_DEVICE_WDA_BASE_URL,
|
|
235
|
+
wdaReady: portMatchesSim,
|
|
236
|
+
reason: "auto-only-simulator",
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
return {
|
|
240
|
+
target: "device",
|
|
241
|
+
udid: null,
|
|
242
|
+
baseUrl: DEFAULT_DEVICE_WDA_BASE_URL,
|
|
243
|
+
wdaReady: true,
|
|
244
|
+
reason: "auto-no-target-found",
|
|
245
|
+
};
|
|
246
|
+
})();
|
|
247
|
+
this.inflight = work;
|
|
248
|
+
try {
|
|
249
|
+
const info = await work;
|
|
250
|
+
if (this.stateVersion === versionAtStart) {
|
|
251
|
+
this.cached = info;
|
|
252
|
+
this.cachedAt = Date.now();
|
|
253
|
+
}
|
|
254
|
+
return info;
|
|
255
|
+
}
|
|
256
|
+
finally {
|
|
257
|
+
this.inflight = null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
reset() {
|
|
261
|
+
this.cached = null;
|
|
262
|
+
this.inflight = null;
|
|
263
|
+
this.simulatorWdaPort = null;
|
|
264
|
+
this.simulatorWdaUdid = null;
|
|
265
|
+
this.stateVersion++;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type PromptDeps } from "../init/prompt.js";
|
|
2
|
+
import { type UninstallStep, type UninstallWalkthroughResult } from "./walkthrough.js";
|
|
3
|
+
export interface CliRunnerOpts extends PromptDeps {
|
|
4
|
+
argv: string[];
|
|
5
|
+
cwd?: string;
|
|
6
|
+
spawnRemove?: (input: {
|
|
7
|
+
bin: string;
|
|
8
|
+
args: string[];
|
|
9
|
+
cwd: string;
|
|
10
|
+
}) => Promise<{
|
|
11
|
+
exitCode: number;
|
|
12
|
+
}>;
|
|
13
|
+
}
|
|
14
|
+
export interface CliRunnerResult {
|
|
15
|
+
exitCode: 0 | 1;
|
|
16
|
+
walkthrough: UninstallWalkthroughResult | null;
|
|
17
|
+
parsedFlags: ParsedFlags;
|
|
18
|
+
}
|
|
19
|
+
interface ParsedFlags {
|
|
20
|
+
yes: boolean;
|
|
21
|
+
purgeImages: boolean;
|
|
22
|
+
global: boolean;
|
|
23
|
+
dryRun: boolean;
|
|
24
|
+
projectDir: string | null;
|
|
25
|
+
help: boolean;
|
|
26
|
+
unknown: string[];
|
|
27
|
+
}
|
|
28
|
+
export declare function runUninstallCli(opts: CliRunnerOpts): Promise<CliRunnerResult>;
|
|
29
|
+
export declare function formatStepLine(step: UninstallStep): string;
|
|
30
|
+
export declare function formatSummary(result: UninstallWalkthroughResult): string;
|
|
31
|
+
export declare function parseArgs(argv: string[]): ParsedFlags;
|
|
32
|
+
export {};
|