stim 1.8.0 → 1.10.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 (91) hide show
  1. package/README.md +23 -155
  2. package/dist/{android-DWssqHQ8.mjs → android-787HhP5w.mjs} +168 -39
  3. package/dist/{android-BRgBYzU0.mjs → android-DBkeHwpm.mjs} +250 -200
  4. package/dist/{android-DeYdjjBP.mjs → android-DPov-fv2.mjs} +2 -2
  5. package/dist/{android-cas-SWIjzeuL.mjs → android-cas-CITmIeWY.mjs} +22 -14
  6. package/dist/android-cas-compiler.mjs +2 -2
  7. package/dist/app-install-CrJ6t_bv.mjs +819 -0
  8. package/dist/budget-BP4jZZL4.mjs +620 -0
  9. package/dist/build-progress-BXx-ZBe4.mjs +512 -0
  10. package/dist/{cache-manifest-CYJi8-Hx.mjs → cache-manifest-Pubfp8wV.mjs} +4 -4
  11. package/dist/cache-manifest.mjs +1 -1
  12. package/dist/cli.mjs +22 -19
  13. package/dist/collector-run.d.mts +7 -1
  14. package/dist/collector-run.mjs +10 -9
  15. package/dist/{command-output-B3ycpsYv.mjs → command-output-D5z2_bcF.mjs} +11 -2
  16. package/dist/{config-B7iyKWkL.mjs → config-BEq0Qw_a.mjs} +134 -121
  17. package/dist/{dependency-state-D0M7oKA7.mjs → dependency-state-r9UesA9a.mjs} +2 -2
  18. package/dist/{deps-oCXVNEqQ.mjs → deps-DP4eWqea.mjs} +8 -5
  19. package/dist/{detached-entry-Bpo6ZY_I.mjs → detached-entry-DJHoca49.mjs} +1 -1
  20. package/dist/{device-BUoZyMoR.mjs → dev-client-92U593KT.mjs} +1090 -660
  21. package/dist/{device-RdujjHLB.mjs → device-DhTZe7L4.mjs} +13 -20
  22. package/dist/{build-lock-CgtsB6TV.mjs → device-lease-CbEaIk7b.mjs} +481 -27
  23. package/dist/{device-pool-BxRwdufO.mjs → device-pool-Di8Q-9Ro.mjs} +7 -7
  24. package/dist/{device-remote-ByN8P6CB.mjs → device-remote-Dy1jGj3U.mjs} +645 -119
  25. package/dist/{doctor-BMt156qe.mjs → doctor-B-inAJmt.mjs} +137 -113
  26. package/dist/{error-diagnostics-D1Kaf-eT.mjs → error-diagnostics-xxWKNO3-.mjs} +22 -17
  27. package/dist/exec-DK8KaVHK.mjs +133 -0
  28. package/dist/{gc-v2YcoJvm.mjs → gc-ChPFZMRC.mjs} +836 -813
  29. package/dist/{guide-BTAlD1_y.mjs → guide-DwmaD_Wa.mjs} +953 -209
  30. package/dist/guide-status-CWvti2Lg.mjs +125 -0
  31. package/dist/idle-DpRLp5lj.mjs +157 -0
  32. package/dist/{build-slots-C-oAHRT1.mjs → in-use-CjUluqIX.mjs} +102 -8
  33. package/dist/{ios-BcCiI4TS.mjs → ios-B2Fpqvlb.mjs} +60 -71
  34. package/dist/{ios-BP04pY5f.mjs → ios-Dd-hYJBh.mjs} +241 -234
  35. package/dist/{ios-device-CFXA46ez.mjs → ios-device-BiOsSVzd.mjs} +3 -2
  36. package/dist/{ios-device-CauFJiYx.mjs → ios-device-CtRdEVjx.mjs} +149 -52
  37. package/dist/launch-verify-CQS_Kg-C.mjs +365 -0
  38. package/dist/{logs-CakdBTtm.mjs → logs-MARIy6m9.mjs} +20 -15
  39. package/dist/metro-BWLOJ_Wr.mjs +409 -0
  40. package/dist/{named-ports-DzMOlmaX.mjs → named-ports-DiTfSz6p.mjs} +4 -4
  41. package/dist/{ndjson-BZwI1NBv.mjs → ndjson-CDY9IkVC.mjs} +23 -43
  42. package/dist/{ndjson-DvHSsNU9.d.mts → ndjson-Du3j0AQ9.d.mts} +1 -0
  43. package/dist/{ownership-Cp52QAK1.mjs → ownership-D2xbLVoz.mjs} +2 -3
  44. package/dist/{teardown-BJqEzPo_.mjs → ownership-DZjdx8Gq.mjs} +664 -612
  45. package/dist/{ports-BIbJaKNW.mjs → ports-CLSSFOuz.mjs} +3 -3
  46. package/dist/{process-output-Dl5uKE03.mjs → process-output-Fpl8PYi8.mjs} +1 -1
  47. package/dist/{project-DDZWf_16.mjs → project-BXZzOABt.mjs} +8 -3
  48. package/dist/{reload-BdypPbjM.mjs → reload-DoETJ3rl.mjs} +11 -13
  49. package/dist/{server-bare-BQkyxa_b.mjs → server-bare-DVbb7kXH.mjs} +2 -2
  50. package/dist/{server-expo-BVAA1OqJ.mjs → server-expo-BvOCeakp.mjs} +8 -7
  51. package/dist/server-expo-DBVTdult.mjs +2 -0
  52. package/dist/{settings-BXXorVM2.mjs → settings-DOugPlPz.mjs} +59 -159
  53. package/dist/settings-EWYNwe72.mjs +378 -0
  54. package/dist/settings.schema.json +1064 -0
  55. package/dist/simslim-Ca3487H6.mjs +436 -0
  56. package/dist/{slot-launch-ZUUe0TQP.mjs → slot-launch-C5cg0fmk.mjs} +4 -4
  57. package/dist/spawn-claims-CUleC2cM.mjs +49 -0
  58. package/dist/{start-CmxtOKjU.mjs → start-F9gv5dcC.mjs} +159 -114
  59. package/dist/start-VoFsPSes.mjs +2 -0
  60. package/dist/{state-DZobzxLW.mjs → state-WGdIbi_d.mjs} +5 -3
  61. package/dist/{stats-BYcZ6n1h.mjs → stats-BAgJaLA4.mjs} +66 -5
  62. package/dist/{stats-CLg5RuDS.mjs → stats-DBYJ5bxL.mjs} +4 -4
  63. package/dist/status-DLCD-o3s.mjs +764 -0
  64. package/dist/status-Dr4fRkym.mjs +216 -0
  65. package/dist/{guide-status-Craq9z0t.mjs → stim-installations-CINXy_su.mjs} +3 -122
  66. package/dist/{stop-C8JW6kKp.mjs → stop-Dj4pfmyv.mjs} +111 -31
  67. package/dist/{stop-B1kOpYei.mjs → stop-ZvOeTmKb.mjs} +2 -2
  68. package/dist/supervisor-run.d.mts +11 -2
  69. package/dist/supervisor-run.mjs +228 -42
  70. package/dist/{workspace-process-lock-BYfZz_S-.mjs → workspace-process-lock-BXA96myD.mjs} +14 -9
  71. package/dist/workspaces-BTUyFfkv.mjs +1003 -0
  72. package/dist/worktree-4aZg2NnI.mjs +2 -0
  73. package/dist/{worktree-CFLA-6MX.mjs → worktree-BMS7GeqB.mjs} +523 -61
  74. package/dist/{worktree-FG_ZKKdb.mjs → worktree-BlizKDpZ.mjs} +200 -54
  75. package/dist/{xcode-B6YaWeg0.mjs → xcode-B2tBkf0m.mjs} +66 -57
  76. package/package.json +6 -6
  77. package/skill/SKILL.md +2 -0
  78. package/dist/app-install-BDbsSgCl.mjs +0 -1175
  79. package/dist/dev-client-BmT-ioFI.mjs +0 -797
  80. package/dist/device-lease-Ffz_M5t0.mjs +0 -359
  81. package/dist/exec-B29XNuhP.mjs +0 -77
  82. package/dist/logs-query-BGL-Pl12.mjs +0 -290
  83. package/dist/metro-C7bcJ0uo.mjs +0 -233
  84. package/dist/ownership-wolE-0Do.mjs +0 -470
  85. package/dist/public-http-probe-CyMDrQ3c.mjs +0 -61
  86. package/dist/reclaim-KJ-0uWEF.mjs +0 -361
  87. package/dist/server-expo-YDDG3eP7.mjs +0 -2
  88. package/dist/spawn-entry-nCE6m770.mjs +0 -14
  89. package/dist/status-DdkLki47.mjs +0 -426
  90. package/dist/workspace-state-BT5E3SA2.mjs +0 -146
  91. package/dist/{metro-store-CDDZHtPl.mjs → metro-store-DzeWkfqW.mjs} +1 -1
@@ -0,0 +1,436 @@
1
+ import { D as process_identity_exports, H as projectDeviceSlots, g as loadConfig, k as ownership_claim_exports, u as getConfigDir, z as deviceSlotPlatforms } from "./config-BEq0Qw_a.mjs";
2
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
+ import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
4
+ import { g as releaseBuildLock, m as acquireBuildLock } from "./device-lease-CbEaIk7b.mjs";
5
+ import { w as listAdbDevices } from "./android-787HhP5w.mjs";
6
+ import { l as listAllIosSims, s as iosRuntimeMatches } from "./ios-B2Fpqvlb.mjs";
7
+ import { _ as easCliSupport, s as readInstalledEasCliVersion, v as easCliUpgradeRemedy, z as resolveEasCliBin } from "./device-remote-Dy1jGj3U.mjs";
8
+ import { existsSync, realpathSync, statSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { createHash } from "node:crypto";
11
+ //#region src/engine/eas-build.ts
12
+ const MIN_EAS_CLI_BUILD_DOWNLOAD_VERSION = "18.9.0";
13
+ function isEasBuildFailure(result) {
14
+ return result?.ok === false;
15
+ }
16
+ function easOptionRefusal({ profile, isExpo, buildSelector, buildCache }) {
17
+ if (profile === void 0) return null;
18
+ if (profile.trim() && isExpo && buildSelector === void 0 && buildCache !== false) return null;
19
+ return {
20
+ code: "STIM_BAD_ARG",
21
+ message: "--eas-profile requires an Expo project and a non-empty development profile.",
22
+ remedy: "Use --eas-profile <name> without --scheme, --configuration, --variant, or --no-build-cache."
23
+ };
24
+ }
25
+ function object(value) {
26
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
27
+ }
28
+ function shellArg(value) {
29
+ return /^[a-zA-Z0-9_.-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
30
+ }
31
+ function easDeviceBuildRemedy(profile) {
32
+ return `If the device is not registered, run npx eas-cli device:create. With approval for cloud build costs and signing changes, run npx eas-cli build --platform ios --profile ${shellArg(profile)} to produce a build with a valid profile for this device, then retry the same Stim command.`;
33
+ }
34
+ var EasFailure = class extends Error {
35
+ code;
36
+ remedy;
37
+ constructor(code, message, remedy) {
38
+ super(message);
39
+ this.code = code;
40
+ this.remedy = remedy;
41
+ }
42
+ };
43
+ function selectEasBuild(builds, { platform, profile, fingerprint, projectId, physical = false }) {
44
+ if (!Array.isArray(builds)) throw new Error("EAS build:list did not return an array.");
45
+ for (const value of builds) {
46
+ const build = object(value);
47
+ if (typeof build.id === "string" && build.id.length > 0 && build.status === "FINISHED" && build.platform === platform.toUpperCase() && build.buildProfile === profile && object(build.fingerprint).hash === fingerprint && (object(build.app).id ?? object(build.project).id) === projectId && (platform !== "ios" || build.isForIosSimulator === !physical) && build.distribution === "INTERNAL" && typeof object(build.artifacts).applicationArchiveUrl === "string") return build.id;
48
+ }
49
+ return null;
50
+ }
51
+ async function resolveEasDevelopmentBuild({ root, platform, profile, note, isExpo = true, physical = false, selectors = [], buildCache, readEasCliVersion = readInstalledEasCliVersion }) {
52
+ if (profile === void 0) return null;
53
+ const refusal = easOptionRefusal({
54
+ profile,
55
+ isExpo,
56
+ buildSelector: selectors.find((value) => value != null) ?? void 0,
57
+ buildCache
58
+ });
59
+ if (refusal) return {
60
+ ok: false,
61
+ ...refusal
62
+ };
63
+ const retry = "the same Stim command";
64
+ const buildCommand = `npx eas-cli build --platform ${platform} --profile ${shellArg(profile)}`;
65
+ let lock = null;
66
+ try {
67
+ const cli = resolveEasCliBin(root);
68
+ if (!cli) throw new EasFailure("STIM_EAS_UNAVAILABLE", "EAS CLI is not installed.", "Install eas-cli, run eas login, then retry.");
69
+ const easCli = easCliSupport(readEasCliVersion(cli.file, root), MIN_EAS_CLI_BUILD_DOWNLOAD_VERSION);
70
+ if (!easCli.supported) throw new EasFailure("STIM_EAS_UNAVAILABLE", easCli.version ? `eas-cli ${easCli.version} (${cli.file}) cannot download a build by ID; --eas-profile needs eas-cli ${MIN_EAS_CLI_BUILD_DOWNLOAD_VERSION} or later.` : `Could not read an eas-cli version from \`${cli.file} --version\`; --eas-profile needs eas-cli ${MIN_EAS_CLI_BUILD_DOWNLOAD_VERSION} or later.`, `${easCliUpgradeRemedy(MIN_EAS_CLI_BUILD_DOWNLOAD_VERSION)}, then retry ${retry}.`);
71
+ let profileEnv = {};
72
+ const run = (args, timeoutMs = 12e4) => {
73
+ try {
74
+ return JSON.parse(getExecutor().runFile(cli.file, [
75
+ ...args,
76
+ "--json",
77
+ "--non-interactive"
78
+ ], {
79
+ cwd: root,
80
+ timeoutMs,
81
+ env: profileEnv
82
+ }));
83
+ } catch {
84
+ throw new EasFailure("STIM_EAS_UNAVAILABLE", `EAS ${args[0]} failed or returned invalid JSON.`, `Run npx eas-cli ${args.map(shellArg).join(" ")} to inspect the error, then retry ${retry}.`);
85
+ }
86
+ };
87
+ note(`eas resolving profile ${profile}`);
88
+ const config = object(run([
89
+ "config",
90
+ "--platform",
91
+ platform,
92
+ "--profile",
93
+ profile
94
+ ]));
95
+ const buildProfile = object(config.buildProfile);
96
+ profileEnv = object(buildProfile.env);
97
+ const projectId = object(object(object(config.appConfig).extra).eas).projectId;
98
+ if (buildProfile.developmentClient !== true || buildProfile.distribution !== "internal" || platform === "ios" && buildProfile.simulator === true !== !physical) throw new EasFailure("STIM_BAD_ARG", `EAS profile ${profile} is not an internal development build for this ${platform} target.`, `Set developmentClient: true and distribution: internal${platform === "ios" ? `, with ios.simulator: ${!physical}` : ""} in that eas.json profile.`);
99
+ if (typeof projectId !== "string" || !projectId) throw new EasFailure("STIM_BAD_ARG", "The Expo app is not linked to an EAS project.", "Link the intended EAS project with eas init, then retry.");
100
+ if (platform === "ios" ? buildProfile.buildConfiguration !== void 0 && buildProfile.buildConfiguration !== "Debug" : buildProfile.gradleCommand !== void 0 && (typeof buildProfile.gradleCommand !== "string" || !/^:app:assemble\w*Debug$/.test(buildProfile.gradleCommand))) throw new EasFailure("STIM_BAD_ARG", `EAS profile ${profile} overrides the development build configuration.`, "Use ios.buildConfiguration: Debug or an android.gradleCommand that assembles a single Debug APK, or remove the override.");
101
+ note(`eas fingerprinting ${platform} with profile ${profile}`);
102
+ const fingerprint = object(run([
103
+ "fingerprint:generate",
104
+ "--platform",
105
+ platform,
106
+ "--build-profile",
107
+ profile
108
+ ])).hash;
109
+ if (typeof fingerprint !== "string" || !/^[a-f0-9]{40,64}$/.test(fingerprint)) throw new EasFailure("STIM_EAS_UNAVAILABLE", "EAS returned no valid native fingerprint.", `Run npx eas-cli fingerprint:generate --platform ${platform} --build-profile ${shellArg(profile)} to inspect the result.`);
110
+ note(`eas looking for ${fingerprint.slice(0, 8)}`);
111
+ const buildId = selectEasBuild(run([
112
+ "build:list",
113
+ "--platform",
114
+ platform,
115
+ "--build-profile",
116
+ profile,
117
+ "--fingerprint-hash",
118
+ fingerprint,
119
+ "--status",
120
+ "finished",
121
+ ...platform === "ios" && !physical ? ["--simulator"] : ["--distribution", "internal"],
122
+ "--limit",
123
+ "1"
124
+ ]), {
125
+ platform,
126
+ profile,
127
+ fingerprint,
128
+ projectId,
129
+ physical
130
+ });
131
+ if (!buildId) throw new EasFailure("STIM_EAS_BUILD_MISSING", `No compatible EAS ${platform} build exists for profile ${profile} and fingerprint ${fingerprint}.`, `A cloud build may incur charges. With approval to run it, use ${buildCommand}, then retry ${retry}.`);
132
+ const cacheKey = `eas-${createHash("sha256").update(JSON.stringify({
133
+ projectId,
134
+ buildId
135
+ })).digest("hex")}-debug-${physical && platform === "ios" ? "device" : "sim"}`;
136
+ lock = acquireBuildLock({
137
+ platform,
138
+ key: cacheKey,
139
+ root
140
+ });
141
+ if (!lock.acquired) throw new EasFailure("STIM_EAS_UNAVAILABLE", `Another run holds the EAS artifact claim at ${lock.path}.`, `Wait for that run to finish, then retry ${retry}.`);
142
+ note(`eas resolving artifact for build ${buildId}`);
143
+ const result = object(run([
144
+ "build:download",
145
+ "--build-id",
146
+ buildId
147
+ ], 6e5));
148
+ if (typeof result.path !== "string" || !existsSync(result.path)) throw new Error("EAS returned no downloaded artifact.");
149
+ const path = realpathSync(result.path);
150
+ if (platform === "ios" ? !path.endsWith(".app") || !statSync(path).isDirectory() : !path.endsWith(".apk") || !statSync(path).isFile()) throw new Error("EAS returned an unexpected artifact path or type.");
151
+ note(`eas artifact ready ${fingerprint.slice(0, 8)}`);
152
+ return {
153
+ ok: true,
154
+ path,
155
+ fingerprint,
156
+ cacheKey,
157
+ cacheHit: "remote"
158
+ };
159
+ } catch (error) {
160
+ const claim = (0, ownership_claim_exports.claimFailure)(error, null);
161
+ if (claim) return {
162
+ ok: false,
163
+ code: claim.code,
164
+ message: claim.message,
165
+ remedy: claim.remedy
166
+ };
167
+ if (error instanceof EasFailure) return {
168
+ ok: false,
169
+ code: error.code,
170
+ message: error.message,
171
+ remedy: error.remedy
172
+ };
173
+ return {
174
+ ok: false,
175
+ code: "STIM_EAS_UNAVAILABLE",
176
+ message: `Could not resolve the EAS development build: ${error.message}`,
177
+ remedy: `Retry ${retry}. No cloud build was started.`
178
+ };
179
+ } finally {
180
+ releaseBuildLock(lock);
181
+ }
182
+ }
183
+ //#endregion
184
+ //#region src/engine/device-capacity.ts
185
+ function liveOwnedDeviceCount({ sims = [], adbEmulators = [], config = null } = {}) {
186
+ let count = 0;
187
+ for (const sim of sims) if (sim?.state === "Booted" && sim.name?.startsWith("stim-")) count++;
188
+ const livePorts = new Set(adbEmulators.map((e) => e.consolePort));
189
+ for (const proj of Object.values(config?.projects || {})) for (const { platforms } of projectDeviceSlots(proj)) {
190
+ const android = platforms.android;
191
+ if (android?.owned && android.avdName && typeof android.consolePort === "number" && livePorts.has(android.consolePort)) count++;
192
+ }
193
+ return count;
194
+ }
195
+ function workspaceHasLiveDevice({ platform, project, slot = "default", sims = [], adbEmulators = [] } = {}) {
196
+ if (!platform) return false;
197
+ const record = deviceSlotPlatforms(project, slot)?.[platform];
198
+ if (!record) return false;
199
+ if (platform === "ios") return sims.some((s) => s.udid === record.deviceUdid && s.state === "Booted");
200
+ return typeof record.consolePort === "number" && adbEmulators.some((e) => e.consolePort === record.consolePort);
201
+ }
202
+ function deviceCapacityRefusal({ platform, project, slot = "default", max, sims = [], adb = null, config = null } = {}) {
203
+ if (!max || max <= 0) return null;
204
+ const adbEmulators = adb?.emulators || [];
205
+ if (workspaceHasLiveDevice({
206
+ platform,
207
+ project,
208
+ slot,
209
+ sims,
210
+ adbEmulators
211
+ })) return null;
212
+ const count = liveOwnedDeviceCount({
213
+ sims,
214
+ adbEmulators,
215
+ config
216
+ });
217
+ if (count < max) return null;
218
+ return {
219
+ code: "STIM_AT_CAPACITY",
220
+ message: `${count} Stim device(s) are already booted and concurrency.maxDevices is ${max}, so booting another would exceed the cap.`,
221
+ remedy: "stop an environment (stim stop) or raise concurrency.maxDevices"
222
+ };
223
+ }
224
+ function checkDeviceCapacity({ platform, project, slot = "default", max, sims = listAllIosSims, adb = listAdbDevices, config = loadConfig } = {}) {
225
+ if (!max || max <= 0) return null;
226
+ let simList = [];
227
+ let adbRes = {
228
+ emulators: [],
229
+ physical: [],
230
+ unhealthy: []
231
+ };
232
+ try {
233
+ simList = typeof sims === "function" ? sims() || [] : sims || [];
234
+ } catch {}
235
+ try {
236
+ adbRes = typeof adb === "function" ? adb() || adbRes : adb || adbRes;
237
+ } catch {}
238
+ let cfg = null;
239
+ try {
240
+ cfg = typeof config === "function" ? config() : config ?? null;
241
+ } catch {}
242
+ return deviceCapacityRefusal({
243
+ platform,
244
+ project,
245
+ slot,
246
+ max,
247
+ sims: simList,
248
+ adb: adbRes,
249
+ config: cfg
250
+ });
251
+ }
252
+ function deviceTypeMismatch(recordedTypeId, requestedName, deviceTypes) {
253
+ if (!requestedName || !recordedTypeId) return null;
254
+ const wanted = (deviceTypes || []).find((d) => d.name === requestedName);
255
+ if (!wanted) return null;
256
+ if (wanted.identifier === recordedTypeId) return null;
257
+ const recorded = (deviceTypes || []).find((d) => d.identifier === recordedTypeId);
258
+ return `this project's sim is ${recorded ? recorded.name : recordedTypeId}, but --device-type asked for ${requestedName}`;
259
+ }
260
+ function installedNames(names) {
261
+ const unique = [...new Set(names.filter((n) => typeof n === "string" && n !== ""))];
262
+ return unique.length > 0 ? unique.join(", ") : "none";
263
+ }
264
+ function unknownIosRuntimeRefusal(requested, runtimes) {
265
+ if (!requested) return null;
266
+ if ((runtimes || []).some((r) => iosRuntimeMatches(r, requested))) return null;
267
+ return {
268
+ message: `No installed simulator runtime matches "${requested}". Installed runtimes: ${installedNames((runtimes || []).map((r) => r.version))}.`,
269
+ 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."
270
+ };
271
+ }
272
+ function creatableIosDeviceTypeNames(runtimes, runtime) {
273
+ return (runtime ? (runtimes || []).filter((r) => iosRuntimeMatches(r, runtime)) : runtimes || []).flatMap((r) => (r.supportedDeviceTypes || []).map((d) => d.name));
274
+ }
275
+ function unknownIosDeviceTypeRefusal(requested, runtimes, runtime) {
276
+ if (!requested) return null;
277
+ const creatable = creatableIosDeviceTypeNames(runtimes, runtime);
278
+ if (creatable.includes(requested)) return null;
279
+ return {
280
+ 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)}.`,
281
+ 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."
282
+ };
283
+ }
284
+ function unknownAndroidSystemImageRefusal(requested, images) {
285
+ if (!requested) return null;
286
+ if ((images || []).some((i) => i.pkg === requested)) return null;
287
+ return {
288
+ message: `No installed Android system image is named "${requested}". Installed system images: ${installedNames((images || []).map((i) => i.pkg))}.`,
289
+ 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\"`."
290
+ };
291
+ }
292
+ //#endregion
293
+ //#region src/engine/simslim.ts
294
+ async function reconcileSimSlim({ udid, profile, previouslyManaged = false, out = () => {}, spawn, timeoutMs = 72e4, cleanupMs = 1e4 }) {
295
+ if (!profile && !previouslyManaged) return {
296
+ managed: false,
297
+ profile: null
298
+ };
299
+ const action = profile ? "on" : "off";
300
+ const args = profile ? [
301
+ "on",
302
+ udid,
303
+ "--profile",
304
+ profile
305
+ ] : ["off", udid];
306
+ const lines = [];
307
+ const onLine = (raw) => {
308
+ const line = stripAnsi(raw).trim();
309
+ if (!line) return;
310
+ lines.push(line);
311
+ if (lines.length > 20) lines.shift();
312
+ out(`SimSlim: ${line}`);
313
+ };
314
+ const stdout = createLineReader(onLine);
315
+ const stderr = createLineReader(onLine);
316
+ const root = join(getConfigDir(), "simslim-locks", `${encodeURIComponent(udid.toLowerCase())}.lock`);
317
+ const label = `SimSlim for ${udid}`;
318
+ const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
319
+ root,
320
+ mode: "exclusive",
321
+ label
322
+ });
323
+ if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
324
+ const claim = attempt.acquired;
325
+ if (!claim) throw new ownership_claim_exports.ClaimRefusedError({
326
+ root,
327
+ claimPath: attempt.held?.path ?? root,
328
+ label,
329
+ reason: "another SimSlim operation is still using this simulator"
330
+ });
331
+ let child;
332
+ let result;
333
+ let identity;
334
+ let cancellation;
335
+ const stopGroup = () => {
336
+ if (child?.pid !== void 0 && identity && (0, process_identity_exports.inspectProcessIdentity)(identity) === "same") try {
337
+ process.kill(-child.pid, "SIGKILL");
338
+ } catch {}
339
+ };
340
+ const cancel = (signal) => {
341
+ cancellation ??= signal;
342
+ stopGroup();
343
+ };
344
+ const onInterrupt = () => cancel("SIGINT");
345
+ const onTerminate = () => cancel("SIGTERM");
346
+ const groupAlive = () => child?.pid !== void 0 && (0, ownership_claim_exports.processGroupAlive)(child.pid);
347
+ const settled = () => result !== void 0 && !groupAlive();
348
+ const waitUntil = async (deadline, cancellable = false) => {
349
+ while (!settled() && Date.now() < deadline) {
350
+ if (cancellable && cancellation) break;
351
+ await new Promise((resolve) => setTimeout(resolve, Math.min(50, deadline - Date.now())));
352
+ }
353
+ return settled();
354
+ };
355
+ process.on("SIGINT", onInterrupt);
356
+ process.on("SIGTERM", onTerminate);
357
+ process.on("exit", stopGroup);
358
+ try {
359
+ (0, ownership_claim_exports.markClaimChildPending)(claim);
360
+ try {
361
+ child = (spawn ?? ((cmd, childArgs, opts) => getExecutor().spawn(cmd, childArgs, opts)))("simslim", args, {
362
+ stdio: [
363
+ "ignore",
364
+ "pipe",
365
+ "pipe"
366
+ ],
367
+ detached: true
368
+ });
369
+ } catch (error) {
370
+ throw simslimLaunchError(error);
371
+ }
372
+ child.stdout?.on("data", (chunk) => stdout.push(chunk));
373
+ child.stderr?.on("data", (chunk) => stderr.push(chunk));
374
+ waitForChild(child).then((value) => {
375
+ result = value;
376
+ });
377
+ if (child.pid !== void 0) {
378
+ const captured = (0, process_identity_exports.captureProcessIdentity)(child.pid);
379
+ if (captured.ok) {
380
+ identity = {
381
+ pid: child.pid,
382
+ processToken: captured.token
383
+ };
384
+ try {
385
+ (0, ownership_claim_exports.setClaimChild)(claim, identity);
386
+ } catch {}
387
+ }
388
+ }
389
+ const completed = await waitUntil(Date.now() + timeoutMs, true);
390
+ if (cancellation || !completed) {
391
+ stopGroup();
392
+ const stopped = await waitUntil(Date.now() + cleanupMs);
393
+ const detail = lines.length ? ` ${lines.join(" | ")}` : "";
394
+ const recovery = stopped ? "Its process group has stopped. Retry stim ios to reconcile the retained simulator settings." : `Its process group could not be confirmed stopped. The claim remains at ${claim.path}. Inspect the SimSlim process group ${child.pid ?? "unknown"} before retrying. Only when nothing is using it, remove the claim: ${(0, ownership_claim_exports.claimRemoveCommand)(claim.path)}`;
395
+ const message = `SimSlim ${action} ${cancellation ? `interrupted by ${cancellation}` : `exceeded ${Math.round(timeoutMs / 1e3)}s`}. ${recovery}${detail}`;
396
+ if (cancellation) out(message);
397
+ throw Object.assign(new Error(message), { code: cancellation ? "EINTR" : "ETIMEDOUT" });
398
+ }
399
+ } finally {
400
+ if (!child || settled()) (0, ownership_claim_exports.releaseClaim)(claim);
401
+ else {
402
+ child.stdout?.destroy?.();
403
+ child.stderr?.destroy?.();
404
+ child.unref();
405
+ }
406
+ process.off("SIGINT", onInterrupt);
407
+ process.off("SIGTERM", onTerminate);
408
+ process.off("exit", stopGroup);
409
+ if (cancellation) process.exit(cancellation === "SIGINT" ? 130 : 143);
410
+ }
411
+ stdout.flush();
412
+ stderr.flush();
413
+ if (result?.error) throw simslimLaunchError(result.error);
414
+ if (result?.code !== 0) {
415
+ const detail = lines.length ? ` ${lines.join(" | ")}` : "";
416
+ throw new Error(`SimSlim ${action} failed with exit code ${result?.code ?? "unknown"}.${detail}`);
417
+ }
418
+ return {
419
+ managed: Boolean(profile),
420
+ profile: profile ?? null
421
+ };
422
+ }
423
+ function simslimLaunchError(error) {
424
+ const cause = error;
425
+ 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 });
426
+ return new Error(`Could not start SimSlim: ${String(cause?.message || cause)}`, { cause });
427
+ }
428
+ function simslimIsOnPath() {
429
+ try {
430
+ return Boolean(getExecutor().findExecutable("simslim"));
431
+ } catch {
432
+ return false;
433
+ }
434
+ }
435
+ //#endregion
436
+ export { liveOwnedDeviceCount as a, unknownIosRuntimeRefusal as c, isEasBuildFailure as d, resolveEasDevelopmentBuild as f, deviceTypeMismatch as i, MIN_EAS_CLI_BUILD_DOWNLOAD_VERSION as l, simslimIsOnPath as n, unknownAndroidSystemImageRefusal as o, checkDeviceCapacity as r, unknownIosDeviceTypeRefusal as s, reconcileSimSlim as t, easDeviceBuildRemedy as u };
@@ -1,7 +1,7 @@
1
- import { f as getProject } from "./config-B7iyKWkL.mjs";
2
- import { r as readWorkspaceState } from "./workspace-state-BT5E3SA2.mjs";
3
- import { h as deviceShellArg } from "./app-install-BDbsSgCl.mjs";
4
- import { n as fileLeaseIo } from "./device-lease-Ffz_M5t0.mjs";
1
+ import { f as getProject } from "./config-BEq0Qw_a.mjs";
2
+ import { m as readWorkspaceState } from "./metro-BWLOJ_Wr.mjs";
3
+ import { n as fileLeaseIo } from "./device-lease-CbEaIk7b.mjs";
4
+ import { d as deviceShellArg } from "./app-install-CrJ6t_bv.mjs";
5
5
  //#region src/engine/slot-launch.ts
6
6
  function launchSlotScope(root, slot = "default") {
7
7
  if (slot !== "default") return slot;
@@ -0,0 +1,49 @@
1
+ import { D as process_identity_exports, k as ownership_claim_exports } from "./config-BEq0Qw_a.mjs";
2
+ //#region src/engine/spawn-claims.ts
3
+ const declaring = /* @__PURE__ */ new Set();
4
+ /** Record every child later started through `spawnDeclared` on `claim` until `stopDeclaringSpawnsOn(claim)`. */
5
+ function declareSpawnsOn(claim) {
6
+ if (claim) declaring.add(claim);
7
+ }
8
+ function stopDeclaringSpawnsOn(claim) {
9
+ if (claim) declaring.delete(claim);
10
+ }
11
+ /**
12
+ * Start a child that every claim passed to `declareSpawnsOn` records until it exits. The child keeps
13
+ * the caller's process group, so a terminal interrupt still reaches it; the claim follows the child
14
+ * itself. A claim that cannot be marked stops the spawn rather than letting it run unprotected. A claim
15
+ * records one child, so declared spawns must not overlap.
16
+ */
17
+ function spawnDeclared(spawn) {
18
+ const claims = [...declaring];
19
+ if (claims.length === 0) return spawn();
20
+ const forget = () => {
21
+ for (const claim of claims) try {
22
+ (0, ownership_claim_exports.clearClaimChild)(claim);
23
+ } catch {}
24
+ };
25
+ let child;
26
+ try {
27
+ for (const claim of claims) (0, ownership_claim_exports.markClaimChildPending)(claim);
28
+ child = spawn();
29
+ } catch (error) {
30
+ forget();
31
+ throw error;
32
+ }
33
+ const pid = child.pid;
34
+ if (pid === void 0) {
35
+ forget();
36
+ return child;
37
+ }
38
+ const captured = (0, process_identity_exports.captureProcessIdentity)(pid);
39
+ if (captured.ok) for (const claim of claims) try {
40
+ (0, ownership_claim_exports.setClaimChild)(claim, {
41
+ pid,
42
+ processToken: captured.token
43
+ });
44
+ } catch {}
45
+ child.once("exit", forget);
46
+ return child;
47
+ }
48
+ //#endregion
49
+ export { spawnDeclared as n, stopDeclaringSpawnsOn as r, declareSpawnsOn as t };