stim 0.0.0 → 1.1.0

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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +162 -0
  3. package/dist/android-CC5bUer7.mjs +204 -0
  4. package/dist/android-DnZlT3aU.mjs +2279 -0
  5. package/dist/android-cas-DGlTStWu.mjs +1078 -0
  6. package/dist/android-cas-compiler.d.mts +1 -0
  7. package/dist/android-cas-compiler.mjs +71 -0
  8. package/dist/android-xmyGBNy5.mjs +665 -0
  9. package/dist/app-install-oZIQRbdD.mjs +1141 -0
  10. package/dist/build-lock-vE45ZzJZ.mjs +645 -0
  11. package/dist/build-slots-CgacA9zQ.mjs +152 -0
  12. package/dist/cache-manifest-oeH18ERr.mjs +66 -0
  13. package/dist/cache-manifest.d.mts +27 -0
  14. package/dist/cache-manifest.mjs +2 -0
  15. package/dist/cli.d.mts +1 -0
  16. package/dist/cli.mjs +40 -0
  17. package/dist/collector-run.d.mts +119 -0
  18. package/dist/collector-run.mjs +541 -0
  19. package/dist/command-output-8wHMEde0.mjs +289 -0
  20. package/dist/config-D7uu8Gbq.mjs +381 -0
  21. package/dist/deps-64wVnpZc.mjs +435 -0
  22. package/dist/dev-client-Bvky1hya.mjs +557 -0
  23. package/dist/device-DJKv5Qej.mjs +250 -0
  24. package/dist/device-lease-D-G3SgVe.mjs +335 -0
  25. package/dist/device-pool-BlHXnYYs.mjs +408 -0
  26. package/dist/device-remote-CCgkG82J.mjs +1209 -0
  27. package/dist/doctor-BRF9aAJ3.mjs +530 -0
  28. package/dist/doctor-DlVxTtMq.mjs +1137 -0
  29. package/dist/error-diagnostics-B5IJwv3-.mjs +636 -0
  30. package/dist/errors-B6W7FkxQ.mjs +15 -0
  31. package/dist/exec-bsN9MJXb.mjs +63 -0
  32. package/dist/gc-DhXdgSdt.mjs +1651 -0
  33. package/dist/guide-DrhrDBo5.mjs +4319 -0
  34. package/dist/ios-BZxKPuqP.mjs +382 -0
  35. package/dist/ios-CK1Vrk6Q.mjs +2721 -0
  36. package/dist/ios-device-CYbuRNXR.mjs +81 -0
  37. package/dist/ios-device-CdSm0GDw.mjs +413 -0
  38. package/dist/logs-Dxoy16Qv.mjs +176 -0
  39. package/dist/logs-query-oqn47Ffx.mjs +273 -0
  40. package/dist/metro-B01H3EuD.mjs +184 -0
  41. package/dist/metro-store-CDDZHtPl.mjs +92 -0
  42. package/dist/ndjson-DcAtEx_K.mjs +131 -0
  43. package/dist/ndjson-DvHSsNU9.d.mts +16 -0
  44. package/dist/ownership-B9QIdQOw.mjs +475 -0
  45. package/dist/ownership-D8HmSv2E.mjs +60 -0
  46. package/dist/ownership-claim-CeJLEpjD.mjs +569 -0
  47. package/dist/process-output-C7IlIzd7.mjs +40 -0
  48. package/dist/project-COj6nzzh.mjs +243 -0
  49. package/dist/reclaim-BPvN2HlP.mjs +347 -0
  50. package/dist/reload-DYZEBbZy.mjs +332 -0
  51. package/dist/remote-cache-DOi5-zLj.mjs +934 -0
  52. package/dist/server-bare-BYHqEeyw.mjs +352 -0
  53. package/dist/server-expo-BEMr_m6p.mjs +312 -0
  54. package/dist/server-expo-rb0k-0w7.mjs +2 -0
  55. package/dist/settings-BSwrY4FZ.mjs +687 -0
  56. package/dist/spawn-entry-nCE6m770.mjs +14 -0
  57. package/dist/start-ByCylYtB.mjs +822 -0
  58. package/dist/state-IYEY-zi8.mjs +268 -0
  59. package/dist/stats-DZ4Q5J-7.mjs +62 -0
  60. package/dist/stats-wqxPm7Tv.mjs +307 -0
  61. package/dist/status-Dia0XTqG.mjs +363 -0
  62. package/dist/stop-BApyaF21.mjs +591 -0
  63. package/dist/stop-D6o5zAdm.mjs +3 -0
  64. package/dist/supervisor-run.d.mts +84 -0
  65. package/dist/supervisor-run.mjs +210 -0
  66. package/dist/workspace-process-lock-CxOO9-pf.mjs +47 -0
  67. package/dist/worktree-Dx3ZC5lS.mjs +539 -0
  68. package/dist/worktree-DxEmUI_n.mjs +1322 -0
  69. package/dist/xcode-CDB6fytw.mjs +1705 -0
  70. package/package.json +61 -8
  71. package/shim/android-cas-pch.cmake +35 -0
  72. package/shim/android-cas.gradle +29 -0
  73. package/shim/android-cas.toolchain.cmake +12 -0
  74. package/shim/android-optimizations.gradle +30 -0
  75. package/shim/bundle-response.cjs +46 -0
  76. package/shim/bundle-response.d.cts +5 -0
  77. package/shim/expo-metro-config.cjs +188 -0
  78. package/shim/metro-cache-generation.cjs +20 -0
  79. package/shim/metro-cache-generation.d.cts +5 -0
  80. package/skill/SKILL.md +14 -0
@@ -0,0 +1,1705 @@
1
+ import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
+ import { C as withConfigLock, _ as releaseAndroidConsolePort, b as setDevice, g as loadConfig, i as clearDevice, t as allConsolePortsAndSerials, u as getConfigDir, y as saveConfig } from "./config-D7uu8Gbq.mjs";
3
+ import { r as register } from "./cache-manifest-oeH18ERr.mjs";
4
+ import { E as workspaceId, S as sharedCompilationCache, w as workspaceDerivedData } from "./state-IYEY-zi8.mjs";
5
+ import { s as ownedDeviceLabel } from "./project-COj6nzzh.mjs";
6
+ import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
7
+ import { r as pidExists } from "./metro-B01H3EuD.mjs";
8
+ import { C as selectParked, S as readParked, _ as teardownParkedIosSim, b as dropParked, g as teardownParkedAvd, m as teardownOwnedAvd, x as parkedMaxSetting, y as adoptParked } from "./remote-cache-DOi5-zLj.mjs";
9
+ import { H as resolveOptimizations, l as androidDataPartitionSizeGbSetting, o as androidAvdConfigSetting, v as iosSimSlimProfileSetting } from "./settings-BSwrY4FZ.mjs";
10
+ import { a as formatElapsed, l as phaseLine } from "./command-output-8wHMEde0.mjs";
11
+ import { _ as resetIosKeychain, b as resolveOwnedIosSim, c as iosRuntimeMatches, g as renameIosSim, h as parseRuntimeVersion, i as createOwnedIosSim, l as listAllIosSims, n as bootIosSim, p as ownedSimName, u as listIosDeviceTypes, v as resetIosPrivacy, y as resolveIosCreation } from "./ios-BZxKPuqP.mjs";
12
+ import { A as resolveOwnedAvdSerial, C as ownedAvdMatchesConfiguration, D as pickDefaultSystemImage, P as waitForBoot, T as ownedAvdSystemImage, _ as listAdbDevices, c as bootAndroidEmulator, g as getAvdNameForSerial, l as configureNewOwnedAvd, o as assertOwnedAvdStopped, s as avdPoolConfiguration, u as createOwnedAvd, v as listAvds, w as ownedAvdName, x as nextConsolePort, y as listInstalledSystemImages } from "./android-xmyGBNy5.mjs";
13
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-CxOO9-pf.mjs";
14
+ import { t as cleanLine } from "./server-expo-BEMr_m6p.mjs";
15
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
16
+ import { createHash, randomUUID } from "node:crypto";
17
+ import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
18
+ import chalk from "chalk";
19
+ //#region src/engine/simslim.ts
20
+ async function reconcileSimSlim({ udid, profile, previouslyManaged = false, out = () => {}, spawn }) {
21
+ if (!profile && !previouslyManaged) return {
22
+ managed: false,
23
+ profile: null
24
+ };
25
+ const action = profile ? "on" : "off";
26
+ const args = profile ? [
27
+ "on",
28
+ udid,
29
+ "--profile",
30
+ profile
31
+ ] : ["off", udid];
32
+ const lines = [];
33
+ const onLine = (raw) => {
34
+ const line = stripAnsi(raw).trim();
35
+ if (!line) return;
36
+ lines.push(line);
37
+ if (lines.length > 20) lines.shift();
38
+ out(`SimSlim: ${line}`);
39
+ };
40
+ const stdout = createLineReader(onLine);
41
+ const stderr = createLineReader(onLine);
42
+ let child;
43
+ try {
44
+ child = (spawn ?? ((cmd, childArgs, opts) => getExecutor().spawn(cmd, childArgs, opts)))("simslim", args, { stdio: [
45
+ "ignore",
46
+ "pipe",
47
+ "pipe"
48
+ ] });
49
+ } catch (error) {
50
+ throw simslimLaunchError(error);
51
+ }
52
+ child.stdout?.on("data", (chunk) => stdout.push(chunk));
53
+ child.stderr?.on("data", (chunk) => stderr.push(chunk));
54
+ const result = await waitForChild(child);
55
+ stdout.flush();
56
+ stderr.flush();
57
+ if (result.error) throw simslimLaunchError(result.error);
58
+ if (result.code !== 0) {
59
+ const detail = lines.length ? ` ${lines.join(" | ")}` : "";
60
+ throw new Error(`SimSlim ${action} failed with exit code ${result.code ?? "unknown"}.${detail}`);
61
+ }
62
+ return {
63
+ managed: Boolean(profile),
64
+ profile: profile ?? null
65
+ };
66
+ }
67
+ function simslimLaunchError(error) {
68
+ const cause = error;
69
+ if (cause?.code === "ENOENT") return new Error("SimSlim is configured but the `simslim` command is not installed. Run `brew install mobai-app/tap/simslim`.", { cause });
70
+ return new Error(`Could not start SimSlim: ${String(cause?.message || cause)}`, { cause });
71
+ }
72
+ function simslimIsOnPath() {
73
+ try {
74
+ return Boolean(getExecutor().runQuiet("command -v simslim", { timeoutMs: 5e3 }));
75
+ } catch {
76
+ return false;
77
+ }
78
+ }
79
+ //#endregion
80
+ //#region src/engine/device.ts
81
+ function startIosBoot(udid, configure) {
82
+ const done = (async () => {
83
+ await bootIosSim(udid);
84
+ await configure();
85
+ })();
86
+ done.catch(() => {});
87
+ return {
88
+ udid,
89
+ done
90
+ };
91
+ }
92
+ async function ensureOwnedDevice({ platform, project, projectPath, settingsRoot = projectPath, label = ownedDeviceLabel(projectPath), settings, flags = {}, note = () => {}, out = () => {}, logFile = null, alive = pidExists, configureAvd = configureNewOwnedAvd, teardownAvd = teardownOwnedAvd, reconcileIosSimulator = reconcileSimSlim }) {
93
+ const record = project?.platforms?.[platform] ?? null;
94
+ if (platform === "ios") return ensureOwnedIosDevice({
95
+ record,
96
+ projectPath,
97
+ settingsRoot,
98
+ label,
99
+ settings,
100
+ flags,
101
+ note,
102
+ out,
103
+ reconcileIosSimulator
104
+ });
105
+ return ensureOwnedAndroidDevice({
106
+ record,
107
+ projectPath,
108
+ settingsRoot,
109
+ label,
110
+ settings,
111
+ flags,
112
+ note,
113
+ out,
114
+ logFile,
115
+ alive,
116
+ configureAvd,
117
+ teardownAvd
118
+ });
119
+ }
120
+ async function ensureOwnedIosDevice({ record, projectPath, settingsRoot, label, settings, flags, note, out, reconcileIosSimulator }) {
121
+ const simslimProfile = iosSimSlimProfileSetting(settings, settingsRoot);
122
+ if (record?.deviceUdid) {
123
+ if (record.owned) {
124
+ const resolved = resolveOwnedIosSim(record.deviceUdid);
125
+ if (resolved.notOwned) note(chalk.yellow(`Note: recorded sim is now named "${resolved.notOwned}", not Stim-owned by name -- creating a fresh owned sim instead of booting it.`));
126
+ else if (resolved.missing) {} else {
127
+ const sim = resolved.sim;
128
+ const wantedType = flags.deviceType || settings?.ios?.deviceType;
129
+ const deviceTypes = listIosDeviceTypes();
130
+ const mismatch = deviceTypeMismatch(sim.deviceTypeIdentifier, wantedType, deviceTypes);
131
+ if (mismatch) throw new Error(`${mismatch}. Stim will not silently boot a different model. Run \`stim worktree remove\` (or \`stim gc --delete\`) to reap the current sim, then \`stim ios\` again to create the requested one.`);
132
+ const model = {
133
+ model: deviceTypes.find((d) => d.identifier === sim.deviceTypeIdentifier)?.name ?? null,
134
+ runtime: parseRuntimeVersion(sim.runtime)
135
+ };
136
+ const name = await withIosDeviceNameLock(() => renameToOwnedName(sim, label, model, projectPath));
137
+ const updated = {
138
+ deviceUdid: sim.udid,
139
+ owned: true,
140
+ deviceName: name,
141
+ ...record.simslimManaged ? { simslimManaged: true } : {},
142
+ ...record.adopted ? { adopted: true } : {},
143
+ ...record.adoptionPending ? { adoptionPending: true } : {},
144
+ ...record.parkedCacheKey ? { parkedCacheKey: record.parkedCacheKey } : {}
145
+ };
146
+ const configure = async () => {
147
+ if (record.adoptionPending) resetAdoptedSim(sim.udid, out);
148
+ return configureOwnedIosSim({
149
+ record: updated,
150
+ projectPath,
151
+ profile: simslimProfile,
152
+ out,
153
+ reconcileIosSimulator
154
+ });
155
+ };
156
+ const facts = {
157
+ deviceType: model.model,
158
+ runtime: model.runtime
159
+ };
160
+ if (sim.state !== "Booted") {
161
+ out(chalk.dim(phaseLine("device", `booting ${name} (${sim.udid})`)));
162
+ return {
163
+ ...updated,
164
+ booting: startIosBoot(sim.udid, configure),
165
+ ...facts
166
+ };
167
+ }
168
+ return {
169
+ ...await configure(),
170
+ ...facts
171
+ };
172
+ }
173
+ } else {
174
+ const sim = listAllIosSims().find((s) => s.udid === record.deviceUdid);
175
+ if (sim) {
176
+ if (sim.state !== "Booted") {
177
+ note(chalk.yellow(`Note: assigned sim ${sim.name} (${sim.udid}) is shut down and is not owned by Stim, so it will not be booted automatically.`));
178
+ note(chalk.dim("Boot it yourself, or run `stim gc --delete` to clear the assignment so Stim can create an owned sim."));
179
+ }
180
+ return record;
181
+ }
182
+ }
183
+ }
184
+ const choice = resolveIosCreation({
185
+ deviceType: flags.deviceType || settings.ios?.deviceType,
186
+ runtime: flags.runtime || settings.ios?.runtime
187
+ });
188
+ const adopted = parkedMaxSetting("ios").max > 0 ? await withIosDeviceNameLock(() => takeParkedIosSim({
189
+ projectPath,
190
+ label,
191
+ choice,
192
+ out
193
+ })) : null;
194
+ if (adopted) {
195
+ out(chalk.dim(phaseLine("device", `booting ${adopted.deviceName} (${adopted.deviceUdid})`)));
196
+ const booting = startIosBoot(adopted.deviceUdid, async () => {
197
+ resetAdoptedSim(adopted.deviceUdid, out);
198
+ await configureOwnedIosSim({
199
+ record: adopted,
200
+ projectPath,
201
+ profile: simslimProfile,
202
+ out,
203
+ reconcileIosSimulator
204
+ });
205
+ });
206
+ return {
207
+ ...adopted,
208
+ booting,
209
+ deviceType: choice.deviceType,
210
+ runtime: choice.runtime
211
+ };
212
+ }
213
+ const created = await withIosDeviceNameLock(() => {
214
+ const suffix = ownedIosNameSuffix(label, {
215
+ model: choice.deviceType,
216
+ runtime: choice.runtime
217
+ }, projectPath);
218
+ const result = createOwnedIosSim(label, { suffix }, choice);
219
+ setDevice(projectPath, "ios", {
220
+ deviceUdid: result.udid,
221
+ owned: true,
222
+ deviceName: result.name
223
+ });
224
+ return result;
225
+ });
226
+ const newRecord = {
227
+ deviceUdid: created.udid,
228
+ owned: true,
229
+ deviceName: created.name
230
+ };
231
+ const booting = startIosBoot(created.udid, () => configureOwnedIosSim({
232
+ record: newRecord,
233
+ projectPath,
234
+ profile: simslimProfile,
235
+ out,
236
+ reconcileIosSimulator
237
+ }));
238
+ return {
239
+ ...newRecord,
240
+ created: true,
241
+ booting,
242
+ deviceType: created.deviceType,
243
+ runtime: created.runtime
244
+ };
245
+ }
246
+ function withIosDeviceNameLock(fn) {
247
+ return withWorkspaceProcessLock(getConfigDir(), "ios-device-names", async () => fn(), { external: true });
248
+ }
249
+ function ownedIosNameSuffix(label, model, projectPath, udid) {
250
+ const names = new Set(listAllIosSims({ includeUnavailable: true }).filter((sim) => sim.udid !== udid).map((sim) => sim.name));
251
+ for (const project of Object.values(loadConfig()?.projects ?? {})) {
252
+ const record = project.platforms?.ios;
253
+ if (record?.deviceUdid !== udid && record?.deviceName) names.add(record.deviceName);
254
+ }
255
+ for (const parked of readParked("ios")) if (parked.udid !== udid) names.add(parked.name);
256
+ let suffix = "";
257
+ for (let attempt = 0; names.has(ownedSimName(label, model, suffix)); attempt++) suffix = ` ${workspaceId(projectPath)}${attempt ? `-${attempt}` : ""}`;
258
+ return suffix;
259
+ }
260
+ function renameToOwnedName(sim, label, model, projectPath) {
261
+ const wanted = ownedSimName(label, model, ownedIosNameSuffix(label, model, projectPath, sim.udid));
262
+ if (sim.name === wanted) return wanted;
263
+ try {
264
+ renameIosSim(sim.udid, wanted);
265
+ return wanted;
266
+ } catch {
267
+ return sim.name;
268
+ }
269
+ }
270
+ function resetAdoptedSim(udid, out) {
271
+ out(chalk.dim(phaseLine("device", `resetting privacy grants and the keychain on ${udid}`)));
272
+ resetIosPrivacy(udid);
273
+ resetIosKeychain(udid);
274
+ }
275
+ function takeParkedIosSim({ projectPath, label, choice, out }) {
276
+ const candidates = selectParked(readParked("ios"), {
277
+ deviceTypeIdentifier: choice.deviceTypeId,
278
+ runtimeIdentifier: choice.runtimeId
279
+ });
280
+ if (candidates.length === 0) return null;
281
+ const listed = new Map(listAllIosSims({ includeUnavailable: true }).map((sim) => [sim.udid, sim]));
282
+ for (const parked of candidates) {
283
+ const sim = listed.get(parked.udid);
284
+ if (!sim || !sim.available) {
285
+ const result = sim ? teardownParkedIosSim(parked.udid, { label: parked.name }) : null;
286
+ if (sim ? result?.status === "torn-down" : dropParked("ios", parked.udid)) out(chalk.dim(phaseLine("device", `deleted parked ${parked.name} (${parked.udid}): ${sim ? "unavailable" : "gone"}`)));
287
+ else if (result?.status === "failed") out(chalk.yellow(phaseLine("device", `could not delete unavailable parked ${parked.name} (${parked.udid}): ${result.reason}`)));
288
+ continue;
289
+ }
290
+ if (!sim.name.startsWith("stim-")) {
291
+ out(chalk.yellow(phaseLine("device", `kept parked record for ${parked.udid}: simulator is now named ${JSON.stringify(sim.name)} and is not Stim-owned`)));
292
+ continue;
293
+ }
294
+ const model = {
295
+ model: choice.deviceType,
296
+ runtime: choice.runtime
297
+ };
298
+ const name = ownedSimName(label, model, ownedIosNameSuffix(label, model, projectPath, parked.udid));
299
+ const device = {
300
+ deviceUdid: parked.udid,
301
+ owned: true,
302
+ deviceName: name,
303
+ adopted: true,
304
+ adoptionPending: true,
305
+ ...parked.simslimManaged ? { simslimManaged: true } : {},
306
+ ...parked.cacheKey ? { parkedCacheKey: parked.cacheKey } : {}
307
+ };
308
+ if (!adoptParked({
309
+ platform: "ios",
310
+ projectPath,
311
+ udid: parked.udid,
312
+ device
313
+ })) continue;
314
+ try {
315
+ renameIosSim(parked.udid, name);
316
+ } catch {}
317
+ return device;
318
+ }
319
+ return null;
320
+ }
321
+ function clearIosAdoptionPending(projectPath) {
322
+ withConfigLock(() => {
323
+ const cfg = loadConfig();
324
+ const ios = cfg?.projects?.[projectPath]?.platforms?.ios;
325
+ if (!cfg || !ios?.adoptionPending) return;
326
+ delete ios.adopted;
327
+ delete ios.adoptionPending;
328
+ delete ios.parkedCacheKey;
329
+ saveConfig(cfg);
330
+ });
331
+ }
332
+ async function configureOwnedIosSim({ record, projectPath, profile, out, reconcileIosSimulator }) {
333
+ if (profile) out(chalk.dim(phaseLine("device", `applying the SimSlim profile to ${record.deviceUdid}`)));
334
+ else if (record.simslimManaged) out(chalk.dim(phaseLine("device", `restoring stock simulator services on ${record.deviceUdid}`)));
335
+ const previouslyManaged = Boolean(record.simslimManaged);
336
+ if (profile && !record.simslimManaged) {
337
+ const pending = {
338
+ ...record,
339
+ simslimManaged: true
340
+ };
341
+ setDevice(projectPath, "ios", pending);
342
+ record = pending;
343
+ }
344
+ const result = await reconcileIosSimulator({
345
+ udid: record.deviceUdid,
346
+ profile,
347
+ previouslyManaged,
348
+ out
349
+ });
350
+ const updated = { ...record };
351
+ if (result.managed) updated.simslimManaged = true;
352
+ else delete updated.simslimManaged;
353
+ setDevice(projectPath, "ios", updated);
354
+ return updated;
355
+ }
356
+ function findOtherProjectOwningAvd(avdName, projectPath) {
357
+ const cfg = loadConfig();
358
+ for (const [path, proj] of Object.entries(cfg?.projects || {})) {
359
+ if (path === projectPath) continue;
360
+ if (proj?.platforms?.android?.avdName === avdName) return path;
361
+ }
362
+ return null;
363
+ }
364
+ async function ensureOwnedAndroidDevice({ record, projectPath, settingsRoot, label, settings, flags, note, out, logFile, alive, configureAvd, teardownAvd }) {
365
+ const avdConfig = androidAvdConfigSetting(settings, settingsRoot);
366
+ const configuration = avdPoolConfiguration(androidDataPartitionSizeGbSetting(settings), avdConfig);
367
+ if (record?.setupIncomplete && record.avdName) {
368
+ const cleanup = teardownAvd(record.avdName, { del: true });
369
+ if (cleanup.status === "failed" || cleanup.status === "skipped") throw new Error(`Owned AVD ${record.avdName} has incomplete setup and could not be deleted (${cleanup.reason || cleanup.status}). Fix the cause, then retry; Stim kept the device record for cleanup.`);
370
+ clearDevice(projectPath, "android");
371
+ record = null;
372
+ }
373
+ if (record?.avdName) {
374
+ if (record.owned) {
375
+ const resolved = resolveOwnedAvdSerial(record.avdName);
376
+ if (resolved.notOwned) note(chalk.yellow(`Note: recorded AVD ${record.avdName} is not Stim-owned by name -- creating a fresh owned AVD instead of reusing it.`));
377
+ else if (resolved.serial) {
378
+ const consolePort = Number(resolved.serial.replace(/^emulator-/, ""));
379
+ const updated = {
380
+ ...record,
381
+ avdName: record.avdName,
382
+ consolePort,
383
+ owned: true,
384
+ deviceName: record.deviceName ?? record.avdName
385
+ };
386
+ setDevice(projectPath, "android", updated);
387
+ return {
388
+ ...updated,
389
+ systemImage: ownedAvdSystemImage(record.avdName)
390
+ };
391
+ } else if (!resolved.missing) {
392
+ out(chalk.dim(`Recorded port for owned AVD ${record.avdName} is not currently ours; booting it on a freshly allocated port...`));
393
+ return {
394
+ ...await bootOwnedAvdOnFreshPort({
395
+ avdName: record.avdName,
396
+ metadata: record,
397
+ projectPath,
398
+ deviceName: record.deviceName,
399
+ out,
400
+ logFile,
401
+ alive
402
+ }),
403
+ systemImage: ownedAvdSystemImage(record.avdName)
404
+ };
405
+ }
406
+ } else if (listAvds().includes(record.avdName)) {
407
+ if (!listAdbDevices().emulators.some((e) => e.consolePort === record.consolePort)) {
408
+ note(chalk.yellow(`Note: assigned AVD ${record.avdName} (emulator-${record.consolePort}) is shut down and is not owned by Stim, so it will not be booted automatically.`));
409
+ note(chalk.dim("Boot it yourself, or run `stim gc --delete` to clear the assignment so Stim can create an owned AVD."));
410
+ }
411
+ return record;
412
+ }
413
+ } else if (record?.serial) {
414
+ note(chalk.yellow(`Note: this project has a stored assignment to physical device ${record.serial}, which Stim no longer keeps.`));
415
+ note(chalk.dim("Creating an owned emulator instead. Pass `--device` to build for a connected device."));
416
+ }
417
+ if (parkedMaxSetting("android").max > 0) {
418
+ const systemImage = pickDefaultSystemImage(listInstalledSystemImages(), { systemImage: flags.systemImage || settings.android?.systemImage })?.pkg;
419
+ const candidates = readParked("android").filter((entry) => entry.systemImage === systemImage && entry.configuration === configuration).toSorted((a, b) => a.parkedAt.localeCompare(b.parkedAt));
420
+ for (const parked of candidates) {
421
+ if (parked.deletionClaim !== void 0) continue;
422
+ const resolved = resolveOwnedAvdSerial(parked.name);
423
+ if (resolved.missing) {
424
+ dropParked("android", parked.udid);
425
+ continue;
426
+ }
427
+ if (resolved.notOwned || resolved.serial) continue;
428
+ try {
429
+ assertOwnedAvdStopped(parked.name);
430
+ } catch (error) {
431
+ out(phaseLine("device", `kept parked ${parked.name}: ${String(error?.message || error)}`));
432
+ continue;
433
+ }
434
+ if (ownedAvdSystemImage(parked.name) !== systemImage || !ownedAvdMatchesConfiguration(parked.name, configuration)) {
435
+ const result = teardownParkedAvd(parked.name);
436
+ if (result.status === "failed") out(phaseLine("device", `kept ${parked.name}: ${result.reason}`));
437
+ continue;
438
+ }
439
+ const adopted = {
440
+ avdName: parked.name,
441
+ deviceName: parked.name,
442
+ owned: true,
443
+ poolConfiguration: configuration,
444
+ adoptionPending: true
445
+ };
446
+ if (!adoptParked({
447
+ platform: "android",
448
+ projectPath,
449
+ udid: parked.udid,
450
+ device: adopted
451
+ })) continue;
452
+ return {
453
+ ...await bootOwnedAvdOnFreshPort({
454
+ avdName: parked.name,
455
+ projectPath,
456
+ metadata: adopted,
457
+ out,
458
+ logFile,
459
+ alive
460
+ }),
461
+ adopted: true,
462
+ systemImage
463
+ };
464
+ }
465
+ }
466
+ let created;
467
+ let fresh = false;
468
+ try {
469
+ created = withConfigLock(() => {
470
+ const current = loadConfig()?.projects?.[projectPath]?.platforms?.android;
471
+ if (current?.avdName && current.avdName !== record?.avdName) throw new Error(`Another Stim run assigned AVD ${current.avdName} to this workspace. Retry to use it.`);
472
+ if (readParked("android").some((entry) => entry.name === ownedAvdName(label)) || findOtherProjectOwningAvd(ownedAvdName(label), projectPath)) label = `${label}-${randomUUID().slice(0, 8)}`;
473
+ const result = createOwnedAvd(label, { systemImage: flags.systemImage || settings.android?.systemImage });
474
+ setDevice(projectPath, "android", {
475
+ avdName: result.avdName,
476
+ owned: true,
477
+ deviceName: result.avdName,
478
+ setupIncomplete: true,
479
+ poolConfiguration: configuration
480
+ });
481
+ return result;
482
+ });
483
+ fresh = true;
484
+ } catch (e) {
485
+ const message = String(e?.message || e);
486
+ const avdName = ownedAvdName(label);
487
+ if (message.includes("already exists") && listAvds().includes(avdName)) {
488
+ created = withConfigLock(() => {
489
+ if (readParked("android").some((entry) => entry.name === avdName)) throw new Error(`AVD ${avdName} was parked by another Stim run. Retry to adopt it safely.`, { cause: e });
490
+ const owner = findOtherProjectOwningAvd(avdName, projectPath);
491
+ if (owner) throw new Error(`AVD ${avdName} already exists and is owned by another project (${owner}). Retry to allocate a distinct owned emulator.`, { cause: e });
492
+ const current = loadConfig()?.projects?.[projectPath]?.platforms?.android;
493
+ if (current?.avdName) {
494
+ const state = current.setupIncomplete ? "has incomplete setup" : "was registered";
495
+ throw new Error(`AVD ${current.avdName} ${state} by another concurrent Stim run. Retry after that run finishes so the recorded device is resolved safely.`, { cause: e });
496
+ }
497
+ const result = {
498
+ avdName,
499
+ systemImage: ownedAvdSystemImage(avdName)
500
+ };
501
+ setDevice(projectPath, "android", {
502
+ avdName,
503
+ owned: true,
504
+ deviceName: avdName
505
+ });
506
+ return result;
507
+ });
508
+ out(chalk.dim(phaseLine("device", `recovered ${avdName} (unrecorded from a prior run)`)));
509
+ } else throw e;
510
+ }
511
+ if (fresh) try {
512
+ configureAvd(created.avdName, {
513
+ dataPartitionSizeGb: androidDataPartitionSizeGbSetting(settings),
514
+ avdConfig
515
+ });
516
+ } catch (error) {
517
+ const cleanup = teardownAvd(created.avdName, { del: true });
518
+ const kept = cleanup.status === "failed" || cleanup.status === "skipped";
519
+ if (!kept) clearDevice(projectPath, "android");
520
+ const orphan = kept ? ` The owned AVD remains tracked for cleanup (${cleanup.reason || cleanup.status}); fix the cause, then retry or run \`stim gc --delete\`.` : "";
521
+ throw new Error(`Created owned AVD ${created.avdName}, but could not configure its AVD settings: ${String(error?.message || error)}${orphan}`, { cause: error });
522
+ }
523
+ return {
524
+ ...await bootOwnedAvdOnFreshPort({
525
+ avdName: created.avdName,
526
+ metadata: fresh ? { poolConfiguration: configuration } : void 0,
527
+ projectPath,
528
+ deviceName: created.avdName,
529
+ out,
530
+ logFile,
531
+ alive
532
+ }),
533
+ created: true,
534
+ systemImage: created.systemImage
535
+ };
536
+ }
537
+ function claimAndroidConsolePort({ projectPath, avdName, deviceName, livePorts = [], metadata }, { lock = withConfigLock, recordedPorts = () => allConsolePortsAndSerials().androidConsolePorts, record = setDevice } = {}) {
538
+ return lock(() => {
539
+ const consolePort = nextConsolePort([...recordedPorts(), ...livePorts]);
540
+ const claim = {
541
+ ...metadata?.poolConfiguration ? { poolConfiguration: metadata.poolConfiguration } : {},
542
+ ...metadata?.adoptionPending ? { adoptionPending: true } : {},
543
+ avdName,
544
+ consolePort,
545
+ owned: true,
546
+ deviceName: deviceName ?? avdName
547
+ };
548
+ record(projectPath, "android", claim);
549
+ return claim;
550
+ });
551
+ }
552
+ function liveAndroidConsolePorts() {
553
+ const adbLive = listAdbDevices();
554
+ return [...adbLive.emulators.map((e) => e.consolePort), ...adbLive.unhealthy.map((u) => u.consolePort).filter((p) => p != null)];
555
+ }
556
+ async function bootOwnedAvdOnFreshPort({ avdName, metadata, projectPath, deviceName, out, logFile = null, alive = pidExists }) {
557
+ const claim = claimAndroidConsolePort({
558
+ projectPath,
559
+ avdName,
560
+ deviceName,
561
+ livePorts: liveAndroidConsolePorts(),
562
+ metadata
563
+ });
564
+ const serial = `emulator-${claim.consolePort}`;
565
+ try {
566
+ const pid = bootAndroidEmulator(avdName, claim.consolePort, { logFile });
567
+ out(chalk.dim(phaseLine("device", `waiting for ${serial} to finish booting`)));
568
+ const result = await waitForBoot(serial, 12e4, { aborted: emulatorGone(pid, alive) });
569
+ if (!result.ok) throw new Error(`${bootFailurePrefix(serial, result.exited, 12e4)} Diagnostic: ${JSON.stringify(result.diagnostic)}`);
570
+ const running = getAvdNameForSerial(serial);
571
+ if (running && running !== avdName) throw new Error(`${serial} is running AVD ${running}, not this workspace's owned AVD ${avdName}; refusing to use it.`);
572
+ return {
573
+ ...claim,
574
+ serial
575
+ };
576
+ } catch (error) {
577
+ releaseAndroidConsolePort(projectPath, claim.consolePort);
578
+ throw error;
579
+ }
580
+ }
581
+ function emulatorGone(pid, alive) {
582
+ if (!pid) return () => false;
583
+ return () => !alive(pid);
584
+ }
585
+ function bootFailurePrefix(serial, exited, timeoutMs) {
586
+ return exited ? `The emulator process for ${serial} exited before the device finished booting.` : `Emulator ${serial} did not finish booting within ${Math.round(timeoutMs / 1e3)}s.`;
587
+ }
588
+ function liveOwnedDeviceCount({ sims = [], adbEmulators = [], config = null } = {}) {
589
+ let count = 0;
590
+ for (const sim of sims) if (sim?.state === "Booted" && sim.name?.startsWith("stim-")) count++;
591
+ const livePorts = new Set(adbEmulators.map((e) => e.consolePort));
592
+ for (const proj of Object.values(config?.projects || {})) {
593
+ const android = proj?.platforms?.android;
594
+ if (android?.owned && android.avdName && typeof android.consolePort === "number" && livePorts.has(android.consolePort)) count++;
595
+ }
596
+ return count;
597
+ }
598
+ function workspaceHasLiveDevice({ platform, project, sims = [], adbEmulators = [] } = {}) {
599
+ if (!platform) return false;
600
+ const record = project?.platforms?.[platform];
601
+ if (!record) return false;
602
+ if (platform === "ios") return sims.some((s) => s.udid === record.deviceUdid && s.state === "Booted");
603
+ return typeof record.consolePort === "number" && adbEmulators.some((e) => e.consolePort === record.consolePort);
604
+ }
605
+ function deviceCapacityRefusal({ platform, project, max, sims = [], adb = null, config = null } = {}) {
606
+ if (!max || max <= 0) return null;
607
+ const adbEmulators = adb?.emulators || [];
608
+ if (workspaceHasLiveDevice({
609
+ platform,
610
+ project,
611
+ sims,
612
+ adbEmulators
613
+ })) return null;
614
+ const count = liveOwnedDeviceCount({
615
+ sims,
616
+ adbEmulators,
617
+ config
618
+ });
619
+ if (count < max) return null;
620
+ return {
621
+ code: "STIM_AT_CAPACITY",
622
+ message: `${count} Stim device(s) are already booted and concurrency.maxDevices is ${max}, so booting another would exceed the cap.`,
623
+ remedy: "stop an environment (stim stop) or raise concurrency.maxDevices"
624
+ };
625
+ }
626
+ function checkDeviceCapacity({ platform, project, max, sims = listAllIosSims, adb = listAdbDevices, config = loadConfig } = {}) {
627
+ if (!max || max <= 0) return null;
628
+ let simList = [];
629
+ let adbRes = {
630
+ emulators: [],
631
+ physical: [],
632
+ unhealthy: []
633
+ };
634
+ try {
635
+ simList = typeof sims === "function" ? sims() || [] : sims || [];
636
+ } catch {}
637
+ try {
638
+ adbRes = typeof adb === "function" ? adb() || adbRes : adb || adbRes;
639
+ } catch {}
640
+ let cfg = null;
641
+ try {
642
+ cfg = typeof config === "function" ? config() : config ?? null;
643
+ } catch {}
644
+ return deviceCapacityRefusal({
645
+ platform,
646
+ project,
647
+ max,
648
+ sims: simList,
649
+ adb: adbRes,
650
+ config: cfg
651
+ });
652
+ }
653
+ function deviceTypeMismatch(recordedTypeId, requestedName, deviceTypes) {
654
+ if (!requestedName || !recordedTypeId) return null;
655
+ const wanted = (deviceTypes || []).find((d) => d.name === requestedName);
656
+ if (!wanted) return null;
657
+ if (wanted.identifier === recordedTypeId) return null;
658
+ const recorded = (deviceTypes || []).find((d) => d.identifier === recordedTypeId);
659
+ return `this project's sim is ${recorded ? recorded.name : recordedTypeId}, but --device-type asked for ${requestedName}`;
660
+ }
661
+ function installedNames(names) {
662
+ const unique = [...new Set(names.filter((n) => typeof n === "string" && n !== ""))];
663
+ return unique.length > 0 ? unique.join(", ") : "none";
664
+ }
665
+ function unknownIosRuntimeRefusal(requested, runtimes) {
666
+ if (!requested) return null;
667
+ if ((runtimes || []).some((r) => iosRuntimeMatches(r, requested))) return null;
668
+ return {
669
+ message: `No installed simulator runtime matches "${requested}". Installed runtimes: ${installedNames((runtimes || []).map((r) => r.version))}.`,
670
+ remedy: "Pass `--runtime` (or set ios.runtime) a version printed above (\"26.5\") or a runtime's full name (\"iOS 26.5\"); nothing else matches. Install more runtimes through Xcode."
671
+ };
672
+ }
673
+ function creatableIosDeviceTypeNames(runtimes, runtime) {
674
+ return (runtime ? (runtimes || []).filter((r) => iosRuntimeMatches(r, runtime)) : runtimes || []).flatMap((r) => (r.supportedDeviceTypes || []).map((d) => d.name));
675
+ }
676
+ function unknownIosDeviceTypeRefusal(requested, runtimes, runtime) {
677
+ if (!requested) return null;
678
+ const creatable = creatableIosDeviceTypeNames(runtimes, runtime);
679
+ if (creatable.includes(requested)) return null;
680
+ return {
681
+ message: `No device type named "${requested}" can be created on ${runtime ? `runtime ${runtime}` : "any installed simulator runtime"}. ${runtime ? `Device types runtime ${runtime} supports` : "Device types the installed runtimes support"}: ${installedNames(creatable)}.`,
682
+ remedy: "Pass `--device-type` (or set ios.deviceType) one of the names printed above, exactly as `xcrun simctl list devicetypes` spells it. `xcrun simctl list devicetypes` also lists watchOS, tvOS and visionOS models, which no iOS runtime can create. Install more models through Xcode."
683
+ };
684
+ }
685
+ function unknownAndroidSystemImageRefusal(requested, images) {
686
+ if (!requested) return null;
687
+ if ((images || []).some((i) => i.pkg === requested)) return null;
688
+ return {
689
+ message: `No installed Android system image is named "${requested}". Installed system images: ${installedNames((images || []).map((i) => i.pkg))}.`,
690
+ remedy: "Pass `--system-image` (or set android.systemImage) to one of the package ids printed above. Install more with `sdkmanager \"system-images;android-36;google_apis;arm64-v8a\"`."
691
+ };
692
+ }
693
+ const BOOT_POLL_MS = 500;
694
+ async function ensureBooted({ platform, device, timeoutMs, pollMs = BOOT_POLL_MS, out = () => {}, logFile = null, alive = pidExists } = {}) {
695
+ if (platform === "ios") return ensureIosBooted({
696
+ device,
697
+ timeoutMs: timeoutMs ?? 6e5,
698
+ pollMs,
699
+ out
700
+ });
701
+ if (platform === "android") return ensureAndroidBooted({
702
+ device,
703
+ timeoutMs: timeoutMs ?? 24e4,
704
+ out,
705
+ logFile,
706
+ alive
707
+ });
708
+ return {
709
+ failed: true,
710
+ reason: `Unknown platform "${platform}".`
711
+ };
712
+ }
713
+ async function ensureIosBooted({ device, timeoutMs, pollMs, out }) {
714
+ const udid = device?.deviceUdid;
715
+ if (!udid) return {
716
+ failed: true,
717
+ reason: "No iOS simulator is recorded for this project."
718
+ };
719
+ const booting = device?.booting;
720
+ if (booting?.udid === udid) {
721
+ try {
722
+ await booting.done;
723
+ } catch (e) {
724
+ return {
725
+ failed: true,
726
+ reason: `Could not boot simulator ${udid}: ${e?.message || e}`
727
+ };
728
+ }
729
+ return {
730
+ ok: true,
731
+ udid
732
+ };
733
+ }
734
+ let resolved;
735
+ try {
736
+ resolved = resolveOwnedIosSim(udid);
737
+ } catch (e) {
738
+ return {
739
+ failed: true,
740
+ reason: `Could not list simulators: ${e?.message || e}`
741
+ };
742
+ }
743
+ if (resolved.missing) return {
744
+ failed: true,
745
+ reason: `Simulator ${udid} no longer exists. Run \`stim ios\` again to create a fresh owned sim.`
746
+ };
747
+ if (resolved.notOwned) return {
748
+ failed: true,
749
+ reason: `Simulator ${udid} is now named "${resolved.notOwned}" and is not Stim-owned; refusing to boot it.`
750
+ };
751
+ const sim = resolved.sim;
752
+ if (sim.state === "Booted") return {
753
+ ok: true,
754
+ udid
755
+ };
756
+ out(chalk.dim(phaseLine("device", `booting ${sim.name} (${udid})`)));
757
+ const bootDeadline = Date.now() + timeoutMs;
758
+ try {
759
+ await bootIosSim(udid, { timeoutMs });
760
+ } catch (e) {
761
+ return {
762
+ failed: true,
763
+ reason: `Could not boot simulator ${udid}: ${e?.message || e}`
764
+ };
765
+ }
766
+ const deadline = Math.max(bootDeadline, Date.now() + 2 * pollMs);
767
+ while (Date.now() < deadline) {
768
+ await sleep(pollMs);
769
+ let state = null;
770
+ try {
771
+ state = listAllIosSims({ timeoutMs: 3e4 }).find((s) => s.udid === udid)?.state ?? null;
772
+ } catch {}
773
+ if (state === "Booted") return {
774
+ ok: true,
775
+ udid
776
+ };
777
+ }
778
+ return {
779
+ failed: true,
780
+ reason: `Simulator ${udid} did not reach the Booted state within ${Math.round(timeoutMs / 1e3)}s.`
781
+ };
782
+ }
783
+ async function ensureAndroidBooted({ device, timeoutMs, out, logFile = null, alive = pidExists }) {
784
+ if (!device?.avdName) return {
785
+ failed: true,
786
+ reason: "No owned Android emulator is recorded for this project."
787
+ };
788
+ let resolved;
789
+ try {
790
+ resolved = resolveOwnedAvdSerial(device.avdName);
791
+ } catch (e) {
792
+ return {
793
+ failed: true,
794
+ reason: `Could not list AVDs: ${e?.message || e}`
795
+ };
796
+ }
797
+ if (resolved.missing) return {
798
+ failed: true,
799
+ reason: `AVD ${device.avdName} no longer exists. Run \`stim android\` again to create a fresh owned AVD.`
800
+ };
801
+ if (resolved.notOwned) return {
802
+ failed: true,
803
+ reason: `AVD ${device.avdName} is not Stim-owned by name; refusing to boot it.`
804
+ };
805
+ if (resolved.serial) {
806
+ const ready = await waitForBoot(resolved.serial, timeoutMs);
807
+ if (!ready.ok) return {
808
+ failed: true,
809
+ reason: `Emulator ${resolved.serial} never reported boot completion. Diagnostic: ${JSON.stringify(ready.diagnostic)}`
810
+ };
811
+ return {
812
+ ok: true,
813
+ serial: resolved.serial
814
+ };
815
+ }
816
+ const freshSerial = `emulator-${device.consolePort}`;
817
+ if (device.owned && device.serial === freshSerial) {
818
+ const ready = await waitForBoot(freshSerial, timeoutMs);
819
+ if (ready.ok) return {
820
+ ok: true,
821
+ serial: freshSerial
822
+ };
823
+ return {
824
+ failed: true,
825
+ reason: `Emulator ${freshSerial} never reported boot completion. Diagnostic: ${JSON.stringify(ready.diagnostic)}`
826
+ };
827
+ }
828
+ const serial = `emulator-${pickConsolePort(device.consolePort)}`;
829
+ out(chalk.dim(phaseLine("device", `booting ${device.avdName} as ${serial}`)));
830
+ let pid = null;
831
+ try {
832
+ pid = bootAndroidEmulator(device.avdName, Number(serial.replace(/^emulator-/, "")), { logFile });
833
+ } catch (e) {
834
+ return {
835
+ failed: true,
836
+ reason: `Could not start emulator for AVD ${device.avdName}: ${e?.message || e}`
837
+ };
838
+ }
839
+ const ready = await waitForBoot(serial, timeoutMs, { aborted: emulatorGone(pid, alive) });
840
+ if (!ready.ok) return {
841
+ failed: true,
842
+ reason: `${bootFailurePrefix(serial, ready.exited, timeoutMs)} Diagnostic: ${JSON.stringify(ready.diagnostic)}`
843
+ };
844
+ return {
845
+ ok: true,
846
+ serial
847
+ };
848
+ }
849
+ function pickConsolePort(recorded) {
850
+ const live = liveAndroidConsolePorts();
851
+ if (recorded && !live.includes(Number(recorded))) return Number(recorded);
852
+ return nextConsolePort([...allConsolePortsAndSerials().androidConsolePorts, ...live]);
853
+ }
854
+ function sleep(ms) {
855
+ return new Promise((r) => setTimeout(r, ms));
856
+ }
857
+ //#endregion
858
+ //#region src/engine/bundler.ts
859
+ const COCOAPODS_SPEC = /^ {4}cocoapods \(/m;
860
+ function bundlerPin(root) {
861
+ const gemfile = join(root, "Gemfile");
862
+ const lockfile = join(root, "Gemfile.lock");
863
+ if (!existsSync(gemfile)) return null;
864
+ let lock;
865
+ try {
866
+ lock = readFileSync(lockfile, "utf-8");
867
+ } catch {
868
+ return null;
869
+ }
870
+ if (!COCOAPODS_SPEC.test(lock)) return null;
871
+ return {
872
+ gemfile,
873
+ lockfile
874
+ };
875
+ }
876
+ function podInstallCommand(root, ...args) {
877
+ const pod = [
878
+ "pod",
879
+ "install",
880
+ ...args
881
+ ].join(" ");
882
+ return bundlerPin(root) ? `bundle exec ${pod}` : pod;
883
+ }
884
+ const BUILD_FAILED = "** BUILD FAILED **";
885
+ const POSITIONED = /^(\S[^\t]*?):(\d+):(?:(\d+):)?\s*(?:fatal\s+)?error:\s+(.+)$/;
886
+ const UNPOSITIONED = /^(\S[^\t]*?)?\berror:\s+(.+)$/;
887
+ const LD_ERROR = /^ld:\s+(?!warning:)(.+)$/;
888
+ const UNDEFINED_HEADER = /^(?:ld:\s+)?Undefined symbols?(?:\s+for architecture\s+\S+)?:\s*$/;
889
+ const UNDEFINED_SYMBOL = /^\s+"?([^",]+)"?,\s+referenced from:\s*$/;
890
+ function undefinedSymbolMessage(symbol) {
891
+ return `Undefined symbol: ${symbol}`;
892
+ }
893
+ const PODS_OUT_OF_SYNC = /The sandbox is not in sync with the Podfile\.lock/;
894
+ const SIGNING = [
895
+ /No profiles for '.*' were found/,
896
+ /requires a development team/i,
897
+ /[Cc]ode ?[Ss]igning [Ee]rror/,
898
+ /No signing certificate/i,
899
+ /code signing is required/i,
900
+ /errSecInternalComponent/,
901
+ /Provisioning profile .* doesn't (?:include|match)/i
902
+ ];
903
+ const NO_SUCH_SCHEME = /does not contain a scheme named/;
904
+ const CAS_CORRUPT = /not a IncludeTreeRoot node kind/;
905
+ const DEVICE_SIGNING_REMEDY = "Set a team and a Development profile for the target's configuration in Xcode > Signing & Capabilities, then build once from Xcode to install the profile. Stim never passes -allowProvisioningUpdates, because registering a device or minting a profile changes your Apple Developer account.";
906
+ const SIMULATOR_SIGNING_REMEDY = "Stim builds for the simulator here, which needs no signing. Check CODE_SIGNING_REQUIRED / DEVELOPMENT_TEAM in the target's configuration, or build for a phone with `stim ios --device`.";
907
+ function remedyFor(message, root, sdk) {
908
+ if (CAS_CORRUPT.test(message)) return "The compilation cache holds a damaged object. Run `stim gc --delete --cache \"compilation cache\"` to empty that cache, then build again. The next build is a cold one.";
909
+ if (PODS_OUT_OF_SYNC.test(message)) return `Run \`${root ? podInstallCommand(root) : "pod install"}\` in ios/ (stim ios does this when Podfile.lock and Pods/Manifest.lock disagree), then build again.`;
910
+ if (NO_SUCH_SCHEME.test(message)) return "Run `xcodebuild -list` in ios/ to see the schemes this project defines, and share the app scheme so it is visible to the build.";
911
+ for (const pattern of SIGNING) if (pattern.test(message)) return sdk.startsWith("iphoneos") ? DEVICE_SIGNING_REMEDY : SIMULATOR_SIGNING_REMEDY;
912
+ return null;
913
+ }
914
+ function fileFromPrefix(prefix) {
915
+ const trimmed = String(prefix).trim().replace(/:$/, "");
916
+ if (!trimmed) return null;
917
+ const head = (trimmed.split(": ")[0] ?? "").trim();
918
+ if (!head.includes("/")) return null;
919
+ return head;
920
+ }
921
+ function makeDiagnostic({ file = null, line = null, column = null, message }, root, sdk) {
922
+ const text = String(message).trim();
923
+ const out = { message: text };
924
+ if (file) out.file = file;
925
+ if (line !== null && line !== void 0) out.line = line;
926
+ if (column !== null && column !== void 0) out.column = column;
927
+ out.message = text;
928
+ const remedy = remedyFor(text, root, sdk);
929
+ if (remedy) out.remedy = remedy;
930
+ return out;
931
+ }
932
+ function dedupeKey(d) {
933
+ return `${d.file || ""}|${d.line || ""}|${d.column || ""}|${d.message}`;
934
+ }
935
+ function extractXcodeDiagnostics(transcript, root = null, sdk = "iphonesimulator") {
936
+ if (typeof transcript !== "string" || transcript === "") return [];
937
+ const lines = transcript.split("\n");
938
+ const out = [];
939
+ const seen = /* @__PURE__ */ new Set();
940
+ const push = (d) => {
941
+ const key = dedupeKey(d);
942
+ if (seen.has(key)) return;
943
+ seen.add(key);
944
+ out.push(d);
945
+ };
946
+ for (let i = 0; i < lines.length; i += 1) {
947
+ const rawLine = lines[i];
948
+ if (rawLine === void 0) continue;
949
+ const raw = rawLine.replace(/\r$/, "");
950
+ if (raw.includes(BUILD_FAILED)) break;
951
+ if (UNDEFINED_HEADER.test(raw)) {
952
+ for (let j = i + 1; j < lines.length; j += 1) {
953
+ const symLine = lines[j];
954
+ if (symLine === void 0) continue;
955
+ const sym = UNDEFINED_SYMBOL.exec(symLine.replace(/\r$/, ""));
956
+ if (sym) {
957
+ const symbol = sym[1];
958
+ if (symbol !== void 0) push(makeDiagnostic({ message: undefinedSymbolMessage(symbol) }, root, sdk));
959
+ continue;
960
+ }
961
+ if (/^\s+\S/.test(symLine) && !/^\S/.test(symLine)) continue;
962
+ i = j - 1;
963
+ break;
964
+ }
965
+ continue;
966
+ }
967
+ const ld = LD_ERROR.exec(raw);
968
+ if (ld) {
969
+ push(makeDiagnostic({ message: `ld: ${ld[1]}` }, root, sdk));
970
+ continue;
971
+ }
972
+ const positioned = POSITIONED.exec(raw);
973
+ if (positioned) {
974
+ const posMsg = positioned[4];
975
+ if (posMsg === void 0) continue;
976
+ push(makeDiagnostic({
977
+ file: positioned[1],
978
+ line: Number(positioned[2]),
979
+ column: positioned[3] === void 0 ? null : Number(positioned[3]),
980
+ message: posMsg
981
+ }, root, sdk));
982
+ continue;
983
+ }
984
+ const plain = UNPOSITIONED.exec(raw);
985
+ if (plain) {
986
+ const plainMsg = plain[2];
987
+ if (plainMsg === void 0) continue;
988
+ push(makeDiagnostic({
989
+ file: fileFromPrefix(plain[1] || ""),
990
+ message: plainMsg
991
+ }, root, sdk));
992
+ }
993
+ }
994
+ return out;
995
+ }
996
+ function capDiagnostics(diagnostics, max = 10) {
997
+ const list = Array.isArray(diagnostics) ? diagnostics : [];
998
+ if (list.length <= max) return {
999
+ diagnostics: list.slice(),
1000
+ truncated: 0
1001
+ };
1002
+ return {
1003
+ diagnostics: list.slice(0, max),
1004
+ truncated: list.length - max
1005
+ };
1006
+ }
1007
+ function describeDiagnostic(diagnostic) {
1008
+ if (!diagnostic || typeof diagnostic !== "object") return "";
1009
+ const { file, line, column, message } = diagnostic;
1010
+ if (!file) return String(message ?? "");
1011
+ return `${file}${line ? column ? `:${line}:${column}` : `:${line}` : ""}: ${message}`;
1012
+ }
1013
+ //#endregion
1014
+ //#region src/engine/xcode.ts
1015
+ const IOS_DIR = "ios";
1016
+ const PREBUILD_REMEDY = "Generate it with `npx expo prebuild -p ios` (stim ios does this automatically for an Expo project with no ios/ directory), or commit the native project.";
1017
+ function buildFailure(message, remedy) {
1018
+ return { error: {
1019
+ code: "STIM_BUILD_FAILED",
1020
+ message,
1021
+ remedy
1022
+ } };
1023
+ }
1024
+ function pickXcodeProject(entries) {
1025
+ const names = (Array.isArray(entries) ? entries : []).filter((e) => typeof e === "string");
1026
+ const workspaces = names.filter((e) => e.endsWith(".xcworkspace")).toSorted();
1027
+ const projects = names.filter((e) => e.endsWith(".xcodeproj")).toSorted();
1028
+ if (workspaces.length > 0) {
1029
+ const projectNames = new Set(projects.map((p) => basename(p, ".xcodeproj")));
1030
+ const file = workspaces.find((w) => projectNames.has(basename(w, ".xcworkspace"))) || workspaces[0];
1031
+ if (file === void 0) return null;
1032
+ return {
1033
+ kind: "workspace",
1034
+ flag: "-workspace",
1035
+ file,
1036
+ name: basename(file, ".xcworkspace")
1037
+ };
1038
+ }
1039
+ if (projects.length > 0) {
1040
+ const file = projects[0];
1041
+ if (file === void 0) return null;
1042
+ return {
1043
+ kind: "project",
1044
+ flag: "-project",
1045
+ file,
1046
+ name: basename(file, ".xcodeproj")
1047
+ };
1048
+ }
1049
+ return null;
1050
+ }
1051
+ function discoverXcodeProject(root) {
1052
+ const dir = join(root, IOS_DIR);
1053
+ let entries;
1054
+ try {
1055
+ entries = readdirSync(dir);
1056
+ } catch {
1057
+ return buildFailure(`No ${IOS_DIR}/ directory in ${root}.`, PREBUILD_REMEDY);
1058
+ }
1059
+ const picked = pickXcodeProject(entries);
1060
+ if (!picked) return buildFailure(`${dir} contains no .xcworkspace and no .xcodeproj.`, PREBUILD_REMEDY);
1061
+ return {
1062
+ ...picked,
1063
+ dir,
1064
+ path: join(dir, picked.file)
1065
+ };
1066
+ }
1067
+ function parseSchemeList(text) {
1068
+ const empty = {
1069
+ name: null,
1070
+ schemes: []
1071
+ };
1072
+ if (typeof text !== "string") return empty;
1073
+ const start = text.indexOf("{");
1074
+ const end = text.lastIndexOf("}");
1075
+ if (start < 0 || end <= start) return empty;
1076
+ let data;
1077
+ try {
1078
+ data = JSON.parse(text.slice(start, end + 1));
1079
+ } catch {
1080
+ return empty;
1081
+ }
1082
+ if (!data || typeof data !== "object") return empty;
1083
+ const record = data;
1084
+ const container = record.workspace || record.project;
1085
+ if (!container || typeof container !== "object") return empty;
1086
+ const info = container;
1087
+ const schemes = Array.isArray(info.schemes) ? info.schemes.filter((s) => typeof s === "string" && s.trim() !== "") : [];
1088
+ return {
1089
+ name: typeof info.name === "string" ? info.name : null,
1090
+ schemes
1091
+ };
1092
+ }
1093
+ const TEST_SCHEME = /(?:UI)?Tests$/;
1094
+ function pickScheme(schemes, containerName) {
1095
+ const list = (Array.isArray(schemes) ? schemes : []).filter((s) => typeof s === "string" && s.trim() !== "");
1096
+ if (list.length === 0) return null;
1097
+ const name = typeof containerName === "string" ? containerName.trim() : "";
1098
+ if (name) {
1099
+ const exact = list.find((s) => s === name);
1100
+ if (exact) return exact;
1101
+ const insensitive = list.find((s) => s.toLowerCase() === name.toLowerCase());
1102
+ if (insensitive) return insensitive;
1103
+ }
1104
+ const app = list.filter((s) => !TEST_SCHEME.test(s));
1105
+ return app.length === 1 ? app[0] : null;
1106
+ }
1107
+ const LIST_TIMEOUT_MS = 18e4;
1108
+ function listSchemes(project, { exec = null } = {}) {
1109
+ const executor = exec || getExecutor();
1110
+ try {
1111
+ return parseSchemeList(executor.runFile("xcodebuild", [
1112
+ project.flag,
1113
+ project.path,
1114
+ "-list",
1115
+ "-json"
1116
+ ], { timeoutMs: LIST_TIMEOUT_MS }));
1117
+ } catch {
1118
+ return null;
1119
+ }
1120
+ }
1121
+ function resolveScheme(project, { exec = null, scheme: requested = null } = {}) {
1122
+ const listing = listSchemes(project, { exec });
1123
+ if (listing === null) return { error: {
1124
+ code: "STIM_NO_SCHEME",
1125
+ message: `Could not list schemes for ${project.path}.`,
1126
+ remedy: `Run \`xcodebuild ${project.flag} ${project.path} -list\` to see what it reports.`
1127
+ } };
1128
+ if (requested !== null) {
1129
+ if (listing.schemes.includes(requested)) return {
1130
+ scheme: requested,
1131
+ schemes: listing.schemes
1132
+ };
1133
+ return { error: {
1134
+ code: "STIM_NO_SCHEME",
1135
+ message: `No shared Xcode scheme named ${JSON.stringify(requested)} in ${project.path}. Available schemes: ${listing.schemes.join(", ") || "none"}.`,
1136
+ remedy: "Pass an exact available name with --scheme, or share the intended app scheme in Xcode."
1137
+ } };
1138
+ }
1139
+ let scheme = pickScheme(listing.schemes, listing.name || project.name);
1140
+ if (!scheme && project.dir) try {
1141
+ const app = JSON.parse(readFileSync(join(project.dir, "..", "app.json"), "utf8"));
1142
+ scheme = pickScheme(listing.schemes, app?.name);
1143
+ } catch {}
1144
+ if (!scheme) {
1145
+ const found = listing.schemes.length ? listing.schemes.join(", ") : "none";
1146
+ return { error: {
1147
+ code: "STIM_NO_SCHEME",
1148
+ message: `Could not select an app scheme in ${project.path} (schemes: ${found}).`,
1149
+ remedy: listing.schemes.length > 0 ? "Pass --scheme <name> to select the intended shared app scheme. Stim does not guess between unmatched schemes." : "Share the app scheme in Xcode (Product > Scheme > Manage Schemes, tick Shared) so xcodebuild can see it."
1150
+ } };
1151
+ }
1152
+ return {
1153
+ scheme,
1154
+ schemes: listing.schemes
1155
+ };
1156
+ }
1157
+ const PREFIX_MAP_TARGET = "/^src";
1158
+ function prefixMapping(workspaceRoot) {
1159
+ return `${String(workspaceRoot).replace(/\/+$/, "")}=${PREFIX_MAP_TARGET}`;
1160
+ }
1161
+ function compilationPrefixMappings(workspaceRoot, derivedDataPath) {
1162
+ const source = resolve(workspaceRoot);
1163
+ const derived = resolve(derivedDataPath);
1164
+ const mappings = [prefixMapping(source)];
1165
+ const rel = relative(source, derived);
1166
+ if (rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) mappings.push(`${derived}=/^derived-data`);
1167
+ return mappings.join(" ");
1168
+ }
1169
+ function ccacheEnabled(podfileProperties) {
1170
+ if (!podfileProperties || typeof podfileProperties !== "object") return false;
1171
+ return podfileProperties["apple.ccacheEnabled"] === "true";
1172
+ }
1173
+ function readPodfileProperties(root) {
1174
+ try {
1175
+ const parsed = JSON.parse(readFileSync(join(root, IOS_DIR, "Podfile.properties.json"), "utf-8"));
1176
+ return parsed && typeof parsed === "object" ? parsed : null;
1177
+ } catch {
1178
+ return null;
1179
+ }
1180
+ }
1181
+ function compilationCacheSettings({ workspaceRoot, derivedDataPath, casPath, xcodeMajor, ccache = false, optimizations = resolveOptimizations({}, {}).ios }) {
1182
+ if (xcodeMajor === null || xcodeMajor === void 0) return [];
1183
+ if (xcodeMajor < 26) return [];
1184
+ if (ccache) return [];
1185
+ return [
1186
+ `COMPILATION_CACHE_ENABLE_CACHING=${optimizations.compilationCache ? "YES" : "NO"}`,
1187
+ `COMPILATION_CACHE_CAS_PATH=${casPath}`,
1188
+ `SWIFT_ENABLE_COMPILE_CACHE=${optimizations.compilationCache && optimizations.swiftCompilationCache ? "YES" : "NO"}`,
1189
+ `CLANG_ENABLE_PREFIX_MAPPING=${optimizations.prefixMapping ? "YES" : "NO"}`,
1190
+ `CLANG_OTHER_PREFIX_MAPPINGS=${optimizations.prefixMapping ? compilationPrefixMappings(workspaceRoot, derivedDataPath) : ""}`
1191
+ ];
1192
+ }
1193
+ function parseXcodeMajor(output) {
1194
+ const m = /^Xcode\s+(\d+)/m.exec(String(output || ""));
1195
+ if (!m) return null;
1196
+ const digits = m[1];
1197
+ if (digits === void 0) return null;
1198
+ const major = parseInt(digits, 10);
1199
+ return Number.isFinite(major) ? major : null;
1200
+ }
1201
+ function detectXcodeMajor(exec = null) {
1202
+ return parseXcodeMajor((exec || getExecutor()).runQuiet("xcodebuild -version", { timeoutMs: 1e4 }));
1203
+ }
1204
+ function resolveCompilationCacheSettings({ root, derivedDataPath, exec = null, casPath = sharedCompilationCache(), optimizations = resolveOptimizations({}, {}).ios, onNote = (line) => console.error(line) }) {
1205
+ const settings = compilationCacheSettings({
1206
+ workspaceRoot: root,
1207
+ derivedDataPath,
1208
+ casPath,
1209
+ xcodeMajor: detectXcodeMajor(exec),
1210
+ ccache: ccacheEnabled(readPodfileProperties(root)),
1211
+ optimizations
1212
+ });
1213
+ if (settings.length > 0 && optimizations.compilationCache) {
1214
+ register({
1215
+ dir: casPath,
1216
+ name: "Xcode compilation cache",
1217
+ prune: "atomic",
1218
+ note: "shared Xcode compilation cache"
1219
+ });
1220
+ onNote(chalk.dim(phaseLine("cache", `compilation cache on (CAS at ${casPath})`)));
1221
+ }
1222
+ return settings;
1223
+ }
1224
+ function xcodebuildArgs({ project, scheme, udid = null, destination = null, configuration = "Debug", sdk = "iphonesimulator", derivedDataPath, extraArgs = [], buildSettings = [] }) {
1225
+ return [
1226
+ project.flag,
1227
+ project.path,
1228
+ "-scheme",
1229
+ scheme,
1230
+ "-configuration",
1231
+ configuration,
1232
+ "-sdk",
1233
+ sdk,
1234
+ "-destination",
1235
+ destination || `id=${udid}`,
1236
+ "-derivedDataPath",
1237
+ derivedDataPath,
1238
+ ...extraArgs,
1239
+ "build",
1240
+ ...buildSettings
1241
+ ];
1242
+ }
1243
+ function productsDir(derivedDataPath, { configuration = "Debug", sdk = "iphonesimulator" } = {}) {
1244
+ return join(derivedDataPath, "Build", "Products", `${configuration}-${sdk}`);
1245
+ }
1246
+ function pickAppBundle(entries, preferredName = null) {
1247
+ const apps = (Array.isArray(entries) ? entries : []).filter((e) => typeof e === "string" && e.endsWith(".app")).toSorted();
1248
+ if (apps.length === 0) return null;
1249
+ if (preferredName) {
1250
+ const wanted = `${preferredName}.app`;
1251
+ const exact = apps.find((a) => a === wanted);
1252
+ if (exact) return exact;
1253
+ }
1254
+ return apps[0];
1255
+ }
1256
+ function findAppBundle(dir, preferredName = null) {
1257
+ let entries;
1258
+ try {
1259
+ entries = readdirSync(dir);
1260
+ } catch {
1261
+ return null;
1262
+ }
1263
+ const app = pickAppBundle(entries, preferredName);
1264
+ return app ? join(dir, app) : null;
1265
+ }
1266
+ function parseBundleId(plistJson) {
1267
+ if (typeof plistJson !== "string") return null;
1268
+ let data;
1269
+ try {
1270
+ data = JSON.parse(plistJson);
1271
+ } catch {
1272
+ return null;
1273
+ }
1274
+ if (!data || typeof data !== "object") return null;
1275
+ const id = data.CFBundleIdentifier;
1276
+ return typeof id === "string" && id.trim() !== "" ? id.trim() : null;
1277
+ }
1278
+ function readBundleId(appPath, { exec = null } = {}) {
1279
+ const executor = exec || getExecutor();
1280
+ try {
1281
+ const id = parseBundleId(executor.runFile("plutil", [
1282
+ "-convert",
1283
+ "json",
1284
+ "-o",
1285
+ "-",
1286
+ join(appPath, "Info.plist")
1287
+ ]));
1288
+ if (id) return id;
1289
+ } catch {}
1290
+ try {
1291
+ const value = executor.runFile("defaults", [
1292
+ "read",
1293
+ join(appPath, "Info"),
1294
+ "CFBundleIdentifier"
1295
+ ]);
1296
+ const trimmed = String(value).trim();
1297
+ return trimmed === "" ? null : trimmed;
1298
+ } catch {
1299
+ return null;
1300
+ }
1301
+ }
1302
+ function parseBundleExecutable(plistJson) {
1303
+ if (typeof plistJson !== "string") return null;
1304
+ let data;
1305
+ try {
1306
+ data = JSON.parse(plistJson);
1307
+ } catch {
1308
+ return null;
1309
+ }
1310
+ if (!data || typeof data !== "object") return null;
1311
+ const name = data.CFBundleExecutable;
1312
+ return typeof name === "string" && name.trim() !== "" ? name.trim() : null;
1313
+ }
1314
+ function readBundleExecutable(appPath, { exec = null } = {}) {
1315
+ const executor = exec || getExecutor();
1316
+ try {
1317
+ const name = parseBundleExecutable(executor.runFile("plutil", [
1318
+ "-convert",
1319
+ "json",
1320
+ "-o",
1321
+ "-",
1322
+ join(appPath, "Info.plist")
1323
+ ]));
1324
+ if (name) return name;
1325
+ } catch {}
1326
+ try {
1327
+ const value = executor.runFile("defaults", [
1328
+ "read",
1329
+ join(appPath, "Info"),
1330
+ "CFBundleExecutable"
1331
+ ]);
1332
+ const trimmed = String(value).trim();
1333
+ return trimmed === "" ? null : trimmed;
1334
+ } catch {
1335
+ return null;
1336
+ }
1337
+ }
1338
+ function tailLines(lines, count = 5) {
1339
+ return (Array.isArray(lines) ? lines : []).filter((l) => typeof l === "string" && l.trim() !== "").slice(-count);
1340
+ }
1341
+ const HEARTBEAT_INTERVAL_MS = 3e4;
1342
+ const HEARTBEAT_ACTIVITY = {
1343
+ build: "compiling",
1344
+ deps: "installing",
1345
+ pods: "installing"
1346
+ };
1347
+ function heartbeatLine(elapsedMs, label = "build", estimateMs = null) {
1348
+ const activity = HEARTBEAT_ACTIVITY[label] ?? "running";
1349
+ const elapsed = formatElapsed(elapsedMs);
1350
+ const inside = estimateMs && estimateMs > 0 ? elapsedMs > estimateMs ? `${elapsed}, usually ~${formatElapsed(estimateMs)}` : `${elapsed} of ~${formatElapsed(estimateMs)}` : elapsed;
1351
+ return phaseLine(label, `still ${activity} (${inside})`);
1352
+ }
1353
+ const defaultSchedule = (run, delayMs) => {
1354
+ const timer = setTimeout(run, delayMs);
1355
+ timer.unref?.();
1356
+ return () => clearTimeout(timer);
1357
+ };
1358
+ function startBuildHeartbeat({ intervalMs, elapsed, emit, label = "build", estimateMs = null, schedule = defaultSchedule }) {
1359
+ if (!(intervalMs > 0)) return () => {};
1360
+ let stopped = false;
1361
+ let lastBeat = 0;
1362
+ let cancel = null;
1363
+ function tick() {
1364
+ if (stopped) return;
1365
+ const ms = elapsed();
1366
+ const beat = Math.floor(ms / intervalMs) * intervalMs;
1367
+ if (beat > lastBeat) {
1368
+ lastBeat = beat;
1369
+ emit(heartbeatLine(beat, label, estimateMs));
1370
+ }
1371
+ cancel = schedule(tick, intervalMs - ms % intervalMs);
1372
+ }
1373
+ cancel = schedule(tick, intervalMs);
1374
+ return () => {
1375
+ stopped = true;
1376
+ cancel?.();
1377
+ };
1378
+ }
1379
+ function failedResult({ code, diagnostics, durationMs, exitCode = null, transcriptLines = 0, tail = [], compilationCache = COMPILATION_CACHE_UNAVAILABLE }) {
1380
+ const capped = capDiagnostics(diagnostics);
1381
+ return {
1382
+ failed: true,
1383
+ code,
1384
+ diagnostics: capped.diagnostics,
1385
+ truncated: capped.truncated,
1386
+ durationMs,
1387
+ exitCode,
1388
+ transcriptLines,
1389
+ tail,
1390
+ compilationCache
1391
+ };
1392
+ }
1393
+ const COMPILATION_CACHE_UNAVAILABLE = {
1394
+ status: "unavailable",
1395
+ hits: null,
1396
+ cacheableTasks: null,
1397
+ hitRatePercent: null
1398
+ };
1399
+ const COMPILATION_CACHE_NOT_RUN = {
1400
+ status: "not-run",
1401
+ hits: null,
1402
+ cacheableTasks: null,
1403
+ hitRatePercent: null
1404
+ };
1405
+ function parseCompilationCacheActivity(line) {
1406
+ if (typeof line !== "string") return null;
1407
+ const match = line.match(/(?:note:\s*)?(\d+) hits \/ (\d+) cacheable tasks \((\d+(?:\.\d+)?)%\)/);
1408
+ if (!match) return null;
1409
+ return {
1410
+ status: "reported",
1411
+ hits: Number(match[1]),
1412
+ cacheableTasks: Number(match[2]),
1413
+ hitRatePercent: Number(match[3])
1414
+ };
1415
+ }
1416
+ function compilationCacheActivityLine(activity) {
1417
+ if (activity.status === "reported") return `${activity.hits}/${activity.cacheableTasks} hits (${activity.hitRatePercent}%)`;
1418
+ if (activity.status === "not-run") return "not run; artifact cache supplied the app";
1419
+ return "unavailable; Xcode did not report reliable statistics";
1420
+ }
1421
+ async function buildIos({ root, udid = null, logWriter, project = null, scheme = null, configuration = "Debug", sdk = "iphonesimulator", destination = null, derivedDataPath = null, extraArgs = [], compilationCache = void 0, optimizations = resolveOptimizations({}, {}).ios, now = () => Date.now(), exec = null, heartbeatMs = HEARTBEAT_INTERVAL_MS, estimateMs = null, onHeartbeat = (line) => console.error(line), onNote = (line) => console.error(line) }) {
1422
+ if (!root || typeof root !== "string") throw new TypeError("buildIos requires {root}");
1423
+ if (!logWriter || typeof logWriter.write !== "function") throw new TypeError("buildIos requires {logWriter} with a write() method");
1424
+ if (!udid && !destination) throw new TypeError("buildIos requires {udid} (or an explicit {destination})");
1425
+ const executor = exec || getExecutor();
1426
+ const dd = derivedDataPath || (scheme ? join(workspaceDerivedData(root), `scheme-${createHash("sha256").update(scheme).digest("hex")}`) : workspaceDerivedData(root));
1427
+ const startedAt = now();
1428
+ const elapsed = () => now() - startedAt;
1429
+ const reportError = (message, remedy) => {
1430
+ logWriter.write({
1431
+ src: "build",
1432
+ level: "error",
1433
+ msg: message,
1434
+ event: "build_diagnostic",
1435
+ ...remedy ? { remedy } : {}
1436
+ });
1437
+ };
1438
+ let target = project;
1439
+ if (!target) {
1440
+ const discovered = discoverXcodeProject(root);
1441
+ if (discovered.error) {
1442
+ reportError(discovered.error.message, discovered.error.remedy);
1443
+ return failedResult({
1444
+ code: discovered.error.code,
1445
+ diagnostics: [{
1446
+ message: discovered.error.message,
1447
+ remedy: discovered.error.remedy ?? void 0
1448
+ }],
1449
+ durationMs: elapsed()
1450
+ });
1451
+ }
1452
+ target = discovered;
1453
+ }
1454
+ const resolvedTarget = target;
1455
+ const selected = resolveScheme(resolvedTarget, {
1456
+ exec: executor,
1457
+ scheme
1458
+ });
1459
+ if (selected.error) {
1460
+ reportError(selected.error.message, selected.error.remedy);
1461
+ return failedResult({
1462
+ code: selected.error.code,
1463
+ diagnostics: [{
1464
+ message: selected.error.message,
1465
+ remedy: selected.error.remedy
1466
+ }],
1467
+ durationMs: elapsed()
1468
+ });
1469
+ }
1470
+ const buildScheme = selected.scheme;
1471
+ const buildSettings = compilationCache === void 0 ? resolveCompilationCacheSettings({
1472
+ root,
1473
+ derivedDataPath: dd,
1474
+ exec: executor,
1475
+ onNote,
1476
+ optimizations
1477
+ }) : compilationCache || [];
1478
+ const args = xcodebuildArgs({
1479
+ project: resolvedTarget,
1480
+ scheme: buildScheme,
1481
+ udid,
1482
+ destination,
1483
+ configuration,
1484
+ sdk,
1485
+ derivedDataPath: dd,
1486
+ extraArgs,
1487
+ buildSettings
1488
+ });
1489
+ logWriter.write({
1490
+ src: "build",
1491
+ level: "info",
1492
+ msg: `xcodebuild ${args.join(" ")}`,
1493
+ event: "build_start"
1494
+ });
1495
+ const transcript = [];
1496
+ let compilationCacheActivity = COMPILATION_CACHE_UNAVAILABLE;
1497
+ const onLine = (line) => {
1498
+ const msg = cleanLine(line);
1499
+ transcript.push(msg);
1500
+ if (msg.trim() === "") return;
1501
+ logWriter.write({
1502
+ src: "build",
1503
+ level: "debug",
1504
+ msg
1505
+ });
1506
+ const activity = parseCompilationCacheActivity(msg);
1507
+ if (activity) {
1508
+ compilationCacheActivity = activity;
1509
+ logWriter.write({
1510
+ src: "build",
1511
+ level: "info",
1512
+ msg: compilationCacheActivityLine(activity),
1513
+ event: "compilation_cache",
1514
+ hits: activity.hits,
1515
+ cacheableTasks: activity.cacheableTasks,
1516
+ hitRatePercent: activity.hitRatePercent
1517
+ });
1518
+ }
1519
+ };
1520
+ let child;
1521
+ try {
1522
+ child = executor.spawn("xcodebuild", args, {
1523
+ cwd: resolvedTarget.dir || root,
1524
+ stdio: [
1525
+ "ignore",
1526
+ "pipe",
1527
+ "pipe"
1528
+ ],
1529
+ detached: false,
1530
+ env: {
1531
+ ...process.env,
1532
+ NSUnbufferedIO: "YES",
1533
+ FORCE_COLOR: "0"
1534
+ }
1535
+ });
1536
+ } catch (err) {
1537
+ const message = `Could not run xcodebuild: ${err.message}`;
1538
+ const remedy = "Install Xcode and select it with `sudo xcode-select -s /Applications/Xcode.app`.";
1539
+ reportError(message, remedy);
1540
+ return failedResult({
1541
+ code: "STIM_BUILD_FAILED",
1542
+ diagnostics: [{
1543
+ message,
1544
+ remedy
1545
+ }],
1546
+ durationMs: elapsed()
1547
+ });
1548
+ }
1549
+ const outReader = createLineReader(onLine);
1550
+ const errReader = createLineReader(onLine);
1551
+ child.stdout?.setEncoding?.("utf-8");
1552
+ child.stderr?.setEncoding?.("utf-8");
1553
+ child.stdout?.on("data", (chunk) => outReader.push(chunk));
1554
+ child.stderr?.on("data", (chunk) => errReader.push(chunk));
1555
+ const stopHeartbeat = startBuildHeartbeat({
1556
+ intervalMs: heartbeatMs,
1557
+ elapsed,
1558
+ emit: onHeartbeat,
1559
+ estimateMs
1560
+ });
1561
+ const outcome = await new Promise((settlePromise) => {
1562
+ let settled = false;
1563
+ const finish = (value) => {
1564
+ if (settled) return;
1565
+ settled = true;
1566
+ outReader.flush();
1567
+ errReader.flush();
1568
+ settlePromise(value);
1569
+ };
1570
+ child.on("close", (code, signal) => finish({
1571
+ code,
1572
+ signal
1573
+ }));
1574
+ child.on("error", (error) => finish({
1575
+ code: null,
1576
+ signal: null,
1577
+ error
1578
+ }));
1579
+ });
1580
+ stopHeartbeat();
1581
+ const durationMs = elapsed();
1582
+ const text = transcript.join("\n");
1583
+ if (outcome.error) {
1584
+ const message = `Could not run xcodebuild: ${outcome.error.message}`;
1585
+ const remedy = "Install Xcode and select it with `sudo xcode-select -s /Applications/Xcode.app`.";
1586
+ reportError(message, remedy);
1587
+ return failedResult({
1588
+ code: "STIM_BUILD_FAILED",
1589
+ diagnostics: [{
1590
+ message,
1591
+ remedy
1592
+ }],
1593
+ durationMs,
1594
+ transcriptLines: transcript.length,
1595
+ tail: tailLines(transcript),
1596
+ compilationCache: compilationCacheActivity
1597
+ });
1598
+ }
1599
+ if (outcome.code !== 0) {
1600
+ const diagnostics = extractXcodeDiagnostics(text, root, sdk);
1601
+ const capped = capDiagnostics(diagnostics);
1602
+ for (const d of capped.diagnostics) reportError(describeDiagnostic(d), d.remedy);
1603
+ if (capped.diagnostics.length === 0) reportError(`xcodebuild exited ${outcome.code ?? `on ${outcome.signal}`} with no recognizable diagnostic; see the transcript above.`, null);
1604
+ return failedResult({
1605
+ code: "STIM_BUILD_FAILED",
1606
+ diagnostics,
1607
+ durationMs,
1608
+ exitCode: outcome.code,
1609
+ transcriptLines: transcript.length,
1610
+ tail: tailLines(transcript),
1611
+ compilationCache: compilationCacheActivity
1612
+ });
1613
+ }
1614
+ const products = productsDir(dd, {
1615
+ configuration,
1616
+ sdk
1617
+ });
1618
+ let appPath = null;
1619
+ if (scheme) try {
1620
+ const targets = JSON.parse(executor.runFile("xcodebuild", [
1621
+ resolvedTarget.flag,
1622
+ resolvedTarget.path,
1623
+ "-scheme",
1624
+ buildScheme,
1625
+ "-configuration",
1626
+ configuration,
1627
+ "-sdk",
1628
+ sdk,
1629
+ "-destination",
1630
+ destination || `id=${udid}`,
1631
+ "-derivedDataPath",
1632
+ dd,
1633
+ ...extraArgs,
1634
+ ...buildSettings,
1635
+ "-showBuildSettings",
1636
+ "-json"
1637
+ ], { timeoutMs: 3e4 }));
1638
+ const paths = /* @__PURE__ */ new Set();
1639
+ for (const item of Array.isArray(targets) ? targets : []) {
1640
+ const settings = item?.buildSettings;
1641
+ if (settings?.PRODUCT_TYPE !== "com.apple.product-type.application" || settings.PLATFORM_NAME !== sdk) continue;
1642
+ const dir = settings.TARGET_BUILD_DIR;
1643
+ const name = settings.FULL_PRODUCT_NAME;
1644
+ if (typeof dir !== "string" || !isAbsolute(dir) || typeof name !== "string" || basename(name) !== name || !name.endsWith(".app")) continue;
1645
+ const path = join(dir, name);
1646
+ if (statSync(path).isDirectory()) paths.add(path);
1647
+ }
1648
+ if (paths.size === 1) appPath = [...paths][0];
1649
+ } catch {}
1650
+ else appPath = findAppBundle(products, buildScheme);
1651
+ if (!appPath) {
1652
+ const message = scheme ? `xcodebuild succeeded, but the built app for scheme ${JSON.stringify(buildScheme)} could not be identified unambiguously from its build settings.` : `xcodebuild reported success but no .app is in ${products}.`;
1653
+ const remedy = "Check that the scheme builds one application target for this platform, not a library, test bundle, or several apps.";
1654
+ reportError(message, remedy);
1655
+ return failedResult({
1656
+ code: "STIM_BUILD_FAILED",
1657
+ diagnostics: [{
1658
+ message,
1659
+ remedy
1660
+ }],
1661
+ durationMs,
1662
+ exitCode: 0,
1663
+ transcriptLines: transcript.length,
1664
+ tail: tailLines(transcript),
1665
+ compilationCache: compilationCacheActivity
1666
+ });
1667
+ }
1668
+ const bundleId = readBundleId(appPath, { exec: executor });
1669
+ if (!bundleId) {
1670
+ const message = `No readable CFBundleIdentifier in ${join(appPath, "Info.plist")}.`;
1671
+ const remedy = "Check PRODUCT_BUNDLE_IDENTIFIER in the target's Debug configuration.";
1672
+ reportError(message, remedy);
1673
+ return failedResult({
1674
+ code: "STIM_BUILD_FAILED",
1675
+ diagnostics: [{
1676
+ message,
1677
+ remedy
1678
+ }],
1679
+ durationMs,
1680
+ exitCode: 0,
1681
+ transcriptLines: transcript.length,
1682
+ tail: tailLines(transcript),
1683
+ compilationCache: compilationCacheActivity
1684
+ });
1685
+ }
1686
+ logWriter.write({
1687
+ src: "build",
1688
+ level: "info",
1689
+ msg: `BUILD SUCCEEDED ${appPath} (${bundleId}) in ${durationMs}ms`,
1690
+ event: "build_done"
1691
+ });
1692
+ return {
1693
+ appPath,
1694
+ bundleId,
1695
+ durationMs,
1696
+ scheme: buildScheme,
1697
+ project: resolvedTarget,
1698
+ derivedDataPath: dd,
1699
+ productsDir: products,
1700
+ transcriptLines: transcript.length,
1701
+ compilationCache: compilationCacheActivity
1702
+ };
1703
+ }
1704
+ //#endregion
1705
+ export { unknownIosDeviceTypeRefusal as C, unknownAndroidSystemImageRefusal as S, simslimIsOnPath as T, checkDeviceCapacity as _, ccacheEnabled as a, ensureOwnedDevice as b, discoverXcodeProject as c, resolveScheme as d, startBuildHeartbeat as f, podInstallCommand as g, bundlerPin as h, buildIos as i, readBundleExecutable as l, describeDiagnostic as m, COMPILATION_CACHE_UNAVAILABLE as n, compilationCacheActivityLine as o, tailLines as p, HEARTBEAT_INTERVAL_MS as r, detectXcodeMajor as s, COMPILATION_CACHE_NOT_RUN as t, readBundleId as u, clearIosAdoptionPending as v, unknownIosRuntimeRefusal as w, liveOwnedDeviceCount as x, ensureBooted as y };