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,365 @@
1
+ import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
2
+ import { o as readNdjsonGenerations } from "./ndjson-CDY9IkVC.mjs";
3
+ import { s as isAppLaunchError } from "./command-output-D5z2_bcF.mjs";
4
+ import { t as DEV_MENU_LAUNCH_ARGS } from "./ios-device-BiOsSVzd.mjs";
5
+ import { d as deviceShellArg, n as ANDROID_DISABLE_AUTO_LAUNCH_EXTRA, o as androidAppProcess } from "./app-install-CrJ6t_bv.mjs";
6
+ import { join } from "node:path";
7
+ //#region src/engine/app-readiness.ts
8
+ const APP_READINESS_TIMEOUT_MS = 3e4;
9
+ function appReadinessSignal(record, platform) {
10
+ if (record.src !== "device" || !platform || record.platform !== platform) return null;
11
+ if (record.level !== "info" && record.level !== "debug") return null;
12
+ if (typeof record.msg !== "string") return null;
13
+ const text = record.msg.trim();
14
+ const message = /^(?:'[^']*'|"[^"]*")$/.test(text) ? text.slice(1, -1) : text;
15
+ return /^\[stim:readiness\] (pending|ready)$/.exec(message)?.[1] ?? null;
16
+ }
17
+ //#endregion
18
+ //#region src/engine/launch-verify.ts
19
+ const RELEASE_VERIFY_WAIT_MS = 3e3;
20
+ async function verifyReleaseLaunch({ pid, waitMs = RELEASE_VERIFY_WAIT_MS, alive = isProcessAlive, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) }) {
21
+ const startedAt = now();
22
+ if (!pid) return {
23
+ verified: false,
24
+ reason: "no-pid",
25
+ waitedMs: 0
26
+ };
27
+ await sleep(Math.max(0, waitMs));
28
+ const waitedMs = now() - startedAt;
29
+ return alive(pid) ? {
30
+ verified: true,
31
+ waitedMs
32
+ } : {
33
+ verified: false,
34
+ reason: "exited",
35
+ waitedMs
36
+ };
37
+ }
38
+ async function verifyAndroidReleaseLaunch({ serial, packageName, waitMs = RELEASE_VERIFY_WAIT_MS, exec = null, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) }) {
39
+ const e = exec || getExecutor();
40
+ const startedAt = now();
41
+ await sleep(Math.max(0, waitMs));
42
+ const pid = androidAppProcess(serial, packageName, { exec: e });
43
+ const waitedMs = now() - startedAt;
44
+ if (pid === void 0) return {
45
+ verified: false,
46
+ reason: "probe-failed",
47
+ waitedMs
48
+ };
49
+ return pid === null ? {
50
+ verified: false,
51
+ reason: "exited",
52
+ waitedMs,
53
+ pid: null
54
+ } : {
55
+ verified: true,
56
+ waitedMs,
57
+ pid
58
+ };
59
+ }
60
+ function isProcessAlive(pid) {
61
+ try {
62
+ process.kill(pid, 0);
63
+ return true;
64
+ } catch (err) {
65
+ return err?.code === "EPERM";
66
+ }
67
+ }
68
+ const LAUNCH_UNVERIFIED = "unverified";
69
+ const LAUNCH_BUNDLING = "bundling";
70
+ const LAUNCH_FATAL = "fatal";
71
+ const VERIFY_TIMEOUT_MS = 2e4;
72
+ const STABILITY_WINDOW_MS = 3e3;
73
+ const VERIFY_POLL_MS = 500;
74
+ const BUNDLE_EVENTS = /* @__PURE__ */ new Set([
75
+ "bundle_build_started",
76
+ "bundle_build_done",
77
+ "bundle_build_failed",
78
+ "bundling_error",
79
+ "transformer_error",
80
+ "bundle_response_started",
81
+ "bundle_response_finished",
82
+ "bundle_response_failed"
83
+ ]);
84
+ function isBundleProof(record, since = 0, platform = null) {
85
+ if (!record || typeof record !== "object") return false;
86
+ const rec = record;
87
+ const ts = Number(rec.ts);
88
+ if (!Number.isFinite(ts) || ts < Number(since || 0)) return false;
89
+ if (platform && recordPlatform(rec) !== platform) return false;
90
+ if (typeof rec.event === "string" && BUNDLE_EVENTS.has(rec.event)) return true;
91
+ if (rec.src === "metro" && typeof rec.msg === "string" && expoBundleLine(rec.msg)) return true;
92
+ return false;
93
+ }
94
+ const BUNDLE_URL_PATH = /\.bundle\b|\/_expo\/|expo-development-client/i;
95
+ function isBundleRequestProof(record, since = 0, port = null, platform = null) {
96
+ if (!record || typeof record !== "object" || !port) return false;
97
+ const rec = record;
98
+ const ts = Number(rec.ts);
99
+ if (!Number.isFinite(ts) || ts < Number(since || 0)) return false;
100
+ if (typeof rec.msg !== "string") return false;
101
+ if (rec.level === "error" || rec.level === "fatal") return false;
102
+ const sourcePlatform = recordPlatform(rec);
103
+ if (platform && sourcePlatform && sourcePlatform !== platform) return false;
104
+ const digits = String(port).replace(/[^0-9]/g, "");
105
+ if (!digits) return false;
106
+ if (!new RegExp(`:${digits}\\b`).test(rec.msg)) return false;
107
+ return BUNDLE_URL_PATH.test(rec.msg);
108
+ }
109
+ function expoBundleLine(msg) {
110
+ return /\bBundl(?:ing|ed)\b/.test(msg);
111
+ }
112
+ function nativeLaunchFailure({ readCrashes, since, platform, processAlive, mode, waitedMs }) {
113
+ if (!readCrashes) return null;
114
+ const crashes = readCrashes().filter((record) => record.event === "native_crash" && after(record, since) && recordCouldBelongToPlatform(record, platform));
115
+ return crashes.length ? {
116
+ verified: false,
117
+ fatal: true,
118
+ errors: crashes,
119
+ processAlive: processAlive?.() ?? null,
120
+ mode,
121
+ waitedMs
122
+ } : null;
123
+ }
124
+ async function verifyLaunch({ slot, logsDir, since, metroPort = null, platform = null, mode = null, requireBundleResponse, timeoutMs = VERIFY_TIMEOUT_MS, stabilityMs = STABILITY_WINDOW_MS, pollMs = VERIFY_POLL_MS, readRecords = null, readDeviceRecords = null, readClientRecords = null, readNativeCrashes = null, processAlive = null, onReadinessPending = null, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) } = {}) {
125
+ const read = readRecords || (() => readMetroRecords(logsDir));
126
+ const readDevice = readDeviceRecords || (() => readNdjson(logsDir, "device.ndjson"));
127
+ const readClient = readClientRecords || (() => readNdjson(logsDir, "client.ndjson"));
128
+ const startedAt = now();
129
+ const bundleDeadline = startedAt + Math.max(0, timeoutMs);
130
+ let proof = null;
131
+ let activity = null;
132
+ let stabilityDeadline = null;
133
+ let pendingAt = null;
134
+ let deliveryId = null;
135
+ let runtimeLoadingAt = null;
136
+ let runtimeStartedAt = null;
137
+ let nextCrashCheck = startedAt + 1e3;
138
+ while (true) {
139
+ const metroRecords = read().filter((record) => after(record, since) && (slot === void 0 || record.slot === slot));
140
+ const bundleRecords = metroRecords.filter((record) => !requireBundleResponse || String(record.event).startsWith("bundle_response_"));
141
+ const deviceRecords = readDevice().filter((record) => after(record, since) && (slot === void 0 || (record.slot ?? "default") === slot));
142
+ const clientRecords = readClient().filter((record) => after(record, since) && (slot === void 0 || record.slot === slot));
143
+ if (now() >= nextCrashCheck) {
144
+ nextCrashCheck = now() + 2e3;
145
+ const crash = nativeLaunchFailure({
146
+ readCrashes: readNativeCrashes,
147
+ since,
148
+ platform,
149
+ processAlive,
150
+ mode,
151
+ waitedMs: now() - startedAt
152
+ });
153
+ if (crash) return crash;
154
+ }
155
+ if (deliveryId === null) {
156
+ const request = bundleRecords.find((record) => record.event === "bundle_response_started" && typeof record.requestId === "string" && isBundleProof(record, since, platform));
157
+ if (request) {
158
+ deliveryId = request.requestId;
159
+ proof = null;
160
+ stabilityDeadline = null;
161
+ }
162
+ }
163
+ for (const record of bundleRecords) {
164
+ if (isBundleProof(record, since, platform)) activity = record;
165
+ const completed = deliveryId === null ? isBundleReadyProof(record, since, platform) : record.event === "bundle_response_finished" && record.requestId === deliveryId && isBundleProof(record, since, platform);
166
+ if (!proof && completed) {
167
+ proof = record;
168
+ stabilityDeadline = Number(record.ts) + Math.max(0, stabilityMs);
169
+ }
170
+ }
171
+ const signals = deviceRecords.filter((record) => Number(record.ts) <= now()).toSorted((a, b) => Number(a.ts) - Number(b.ts));
172
+ if (platform === "android" && runtimeLoadingAt === null) {
173
+ const loading = signals.find((record) => record.src === "device" && record.platform === "android" && /^(?:unknown:)?BridgelessReact\(\d+\)$/.test(String(record.proc)) && /^ReactHost\{\d+\}\.getOrCreateReactInstanceTask\(\): Loading JS Bundle$/.test(String(record.msg)) && (stabilityDeadline === null || Number(record.ts) <= stabilityDeadline));
174
+ if (loading) runtimeLoadingAt = Number(loading.ts);
175
+ }
176
+ if (runtimeLoadingAt !== null && runtimeStartedAt === null) {
177
+ const executing = signals.find((record) => record.src === "device" && record.platform === "android" && Number(record.ts) >= runtimeLoadingAt && (/^ReactNativeJS\(\d+\)$/.test(String(record.proc)) || appReadinessSignal(record, platform) !== null));
178
+ if (executing) runtimeStartedAt = Number(executing.ts);
179
+ }
180
+ const runtimeWaiting = runtimeLoadingAt !== null && runtimeStartedAt === null;
181
+ const completedAt = proof ? Math.max(Number(proof.ts), runtimeStartedAt ?? 0) : null;
182
+ if (completedAt !== null) stabilityDeadline = completedAt + Math.max(0, stabilityMs);
183
+ const readinessDeadline = completedAt !== null ? completedAt + APP_READINESS_TIMEOUT_MS : bundleDeadline;
184
+ if (pendingAt === null) {
185
+ const pending = signals.find((record) => appReadinessSignal(record, platform) === "pending" && (runtimeWaiting || stabilityDeadline === null || Number(record.ts) <= stabilityDeadline));
186
+ if (pending) {
187
+ pendingAt = Number(pending.ts);
188
+ onReadinessPending?.();
189
+ }
190
+ }
191
+ const ready = pendingAt !== null && signals.some((record) => Number(record.ts) >= pendingAt && Number(record.ts) <= readinessDeadline && appReadinessSignal(record, platform) === "ready");
192
+ const bundleErrors = bundleRecords.filter((record) => isFatalLaunchError(record, platform) || deliveryId !== null && record.event === "bundle_response_failed" && record.requestId === deliveryId && isBundleProof(record, since, platform));
193
+ if (bundleErrors.length) {
194
+ const alive = processAlive ? processAlive() : null;
195
+ const fatalRecord = bundleErrors[bundleErrors.length - 1];
196
+ const fatalAt = Number(fatalRecord?.ts ?? since ?? 0);
197
+ return {
198
+ verified: false,
199
+ fatal: true,
200
+ errors: metroRecords.filter((record) => after(record, fatalAt) && recordCouldBelongToPlatform(record, platform) && isLaunchError(record, platform)),
201
+ processAlive: alive,
202
+ record: fatalRecord,
203
+ mode,
204
+ waitedMs: now() - startedAt,
205
+ ...pendingAt !== null ? { readiness: "error" } : {}
206
+ };
207
+ }
208
+ if (proof && stabilityDeadline !== null && (pendingAt !== null || now() >= stabilityDeadline)) {
209
+ const errorSince = pendingAt === null ? Number(proof.ts ?? since ?? 0) : Number(since ?? 0);
210
+ const errors = [
211
+ ...metroRecords.filter((record) => after(record, errorSince) && recordCouldBelongToPlatform(record, platform)),
212
+ ...deviceRecords.filter((record) => after(record, errorSince) && recordCouldBelongToPlatform(record, platform)),
213
+ ...clientRecords.filter((record) => after(record, errorSince) && recordCouldBelongToPlatform(record, platform))
214
+ ].filter((record) => isLaunchError(record, platform));
215
+ const alive = processAlive ? processAlive() : null;
216
+ const waitedMs = now() - startedAt;
217
+ if (alive === false) return {
218
+ verified: false,
219
+ fatal: true,
220
+ errors,
221
+ processAlive: alive,
222
+ record: proof,
223
+ mode,
224
+ waitedMs,
225
+ ...pendingAt !== null ? { readiness: "error" } : {}
226
+ };
227
+ const actionableErrors = errors.filter((record) => !isIosConnectionRefusal(record, platform));
228
+ const appErrors = actionableErrors.some(isAppLaunchError);
229
+ if (!runtimeWaiting && pendingAt === null || appErrors || ready || now() >= readinessDeadline) return {
230
+ verified: true,
231
+ record: proof,
232
+ errors: actionableErrors,
233
+ processAlive: alive,
234
+ mode,
235
+ waitedMs,
236
+ ...pendingAt !== null ? { readiness: appErrors ? "error" : ready ? "ready" : "timed-out" } : {}
237
+ };
238
+ }
239
+ if ((!proof || runtimeWaiting) && now() >= bundleDeadline) {
240
+ const requested = findBundleRequest(deviceRecords, since, metroPort, platform) ?? activity;
241
+ const waitedMs = now() - startedAt;
242
+ if (requested) return {
243
+ verified: false,
244
+ timedOut: true,
245
+ requested: true,
246
+ record: requested,
247
+ mode,
248
+ waitedMs
249
+ };
250
+ return {
251
+ verified: false,
252
+ timedOut: true,
253
+ mode,
254
+ waitedMs
255
+ };
256
+ }
257
+ await sleep(Math.min(pollMs, Math.max(0, (runtimeWaiting ? bundleDeadline : proof && pendingAt !== null ? readinessDeadline : stabilityDeadline ?? bundleDeadline) - now())));
258
+ }
259
+ }
260
+ function after(record, since) {
261
+ const ts = Number(record.ts);
262
+ return Number.isFinite(ts) && ts >= Number(since ?? 0);
263
+ }
264
+ function isLaunchError(record, platform) {
265
+ return record.level === "error" || record.level === "fatal" || isFatalLaunchError(record, platform);
266
+ }
267
+ const TCP_REFUSAL = /^TCP Conn 0x[0-9a-f]+ Failed : error 0:61 \[61\]$/i;
268
+ function isIosConnectionRefusal(record, platform) {
269
+ return platform === "ios" && record.src === "device" && record.level === "error" && typeof record.msg === "string" && TCP_REFUSAL.test(record.msg);
270
+ }
271
+ function isFatalLaunchError(record, platform) {
272
+ if (platform && recordPlatform(record) !== platform) return false;
273
+ return record.event === "bundle_build_failed" || record.event === "bundling_error" || record.event === "transformer_error" || (record.event === "expo_stdout" || record.event === "expo_stderr") && typeof record.msg === "string" && /\bBundling failed\b/.test(record.msg);
274
+ }
275
+ function isBundleReadyProof(record, since = 0, platform = null) {
276
+ if (!isBundleProof(record, since, platform)) return false;
277
+ const rec = record;
278
+ if (rec.event === "bundle_build_done") return true;
279
+ return rec.src === "metro" && typeof rec.msg === "string" && /\bBundled\b/.test(rec.msg);
280
+ }
281
+ function findBundleRequest(records, since, port, platform) {
282
+ for (const record of records) if (isBundleRequestProof(record, since ?? 0, port, platform)) return record;
283
+ return null;
284
+ }
285
+ function recordCouldBelongToPlatform(record, platform) {
286
+ if (!platform) return true;
287
+ const sourcePlatform = recordPlatform(record);
288
+ return sourcePlatform === null || sourcePlatform === platform;
289
+ }
290
+ function recordPlatform(record) {
291
+ if (record.platform === "ios" || record.platform === "android") return record.platform;
292
+ if (typeof record.msg !== "string") return null;
293
+ const match = record.msg.match(/\b(iOS|Android)\s+Bundl(?:ed|ing)\b/i);
294
+ if (!match) return null;
295
+ return match[1].toLowerCase() === "ios" ? "ios" : "android";
296
+ }
297
+ function readMetroRecords(logsDir) {
298
+ return readNdjson(logsDir, "metro.ndjson");
299
+ }
300
+ function readCollectorRecords(logsDir) {
301
+ return readNdjson(logsDir, "device.ndjson");
302
+ }
303
+ function readNdjson(logsDir, name) {
304
+ return logsDir ? readNdjsonGenerations(join(logsDir, name)) : [];
305
+ }
306
+ function unverifiedLaunchLines({ platform, metroPort, waitedMs = VERIFY_TIMEOUT_MS, bundleId = null, udid = null, serial = null, devClientUrl: url = null, mode = null, remote = false, physical = false, devClient = false, lanOrigin = null, metroOrigin = null, localNetworkPending = false, component = null }) {
307
+ const seconds = Math.round(Number(waitedMs || 0) / 1e3);
308
+ const localNetwork = localNetworkPending && platform === "ios" && physical;
309
+ const lines = [`The app was started, but nothing fetched a bundle from this workspace's Metro (port ${metroPort}) within ${seconds}s.`];
310
+ const origin = metroOrigin || `http://localhost:${metroPort}`;
311
+ const picker = `If expo-dev-launcher's DEVELOPMENT SERVERS picker is showing, tap the entry for ${origin} -- NOT another workspace's, which would load a different project's bundle onto this device.`;
312
+ let step = 0;
313
+ const push = (text) => lines.push(` ${++step}. ${text}`);
314
+ lines.push(localNetwork ? `THE PHONE'S LOCAL NETWORK PERMISSION IS NOT GRANTED: the LAN connections this launch made failed with NSURLErrorDomain -1009 and the path reason "unsatisfied (Local network prohibited)", which is what iOS returns while the "would like to find and connect to devices on your local network" prompt is unanswered OR was answered Don't Allow earlier -- the log reads the same either way. Nothing the app sends reaches ${lanOrigin || origin} until it is granted. Do this, in order:` : "The app is launched; what is unproven is that it is talking to THIS dev server. Do this, in order:");
315
+ if (remote) {
316
+ push(`Check that ${origin} is reachable FROM THE DEVICE's network, not just from this machine. That is the usual cause: a tunnel that stopped, or one this machine can reach and the device cannot.`);
317
+ push("If an \"Open in <app>?\" alert or the expo-dev-launcher picker is showing on the remote device, confirm it with your device tool (`agent-device snapshot -i`, then `agent-device press`).");
318
+ lines.push(`Then check \`stim logs --source metro\`${mode ? ` (${mode})` : ""} for a bundle request.`);
319
+ return lines;
320
+ }
321
+ if (platform === "ios" && physical) {
322
+ const target = lanOrigin || origin;
323
+ const relaunch = `xcrun devicectl device process launch --device ${udid} --terminate-existing ` + (url ? `--payload-url '${url}' ` : "") + bundleId + (url ? ` -- ${DEV_MENU_LAUNCH_ARGS.join(" ")}` : "");
324
+ if (localNetwork) {
325
+ push(`See the prompt: agent-device alert get --platform ios --udid ${udid}. It reads the alert without opening anything, so it works while the app sits behind it.`);
326
+ push(`Tap Allow: agent-device alert accept --platform ios --udid ${udid}. A second \`alert get\` then finds none.`);
327
+ push("If the FIRST `alert get` already finds no alert, this permission was denied on an earlier run -- a Don't Allow persists across upgrade installs. Turn the app on by hand under Settings > Privacy & Security > Local Network; there is no API for that switch.");
328
+ if (devClient) {
329
+ push(`The app does NOT retry after the grant -- it stays on "Failed to load app ... The Internet connection appears to be offline." with a Reload button. Press it: agent-device snapshot -i --platform ios --udid ${udid}, then agent-device press 'label="Reload"' --platform ios --udid ${udid}. This launch carried -EXDevMenuShowsAtLaunch 0, so the Expo dev menu is not over the app; if the app was started another way and the menu is on screen, agent-device press 'label="Close"' --platform ios --udid ${udid} dismisses it.`);
330
+ push(`Without agent-device, relaunching also recovers: ${relaunch}. It costs the device log -- it replaces the process the collector follows, so \`stim logs --source device\` stops for the rest of this run. Pressing Reload keeps the collector alive.`);
331
+ push("By hand: tap Allow on the phone, then tap Reload on the app.");
332
+ } else {
333
+ push(`The app does NOT retry after the grant. A bare app is expected to show React Native's "Could not connect to development server" RedBox -- NOT VERIFIED ON HARDWARE, so read the screen rather than trusting that: agent-device snapshot -i --platform ios --udid ${udid}, then press its Reload button by the ref or label that snapshot reports.`);
334
+ push(`Cleanest for a bare app: relaunch, which re-reads ip.txt: ${relaunch}. It costs the device log -- it replaces the process the collector follows, so \`stim logs --source device\` stops for the rest of this run.`);
335
+ push("By hand: tap Allow on the phone, then tap Reload on the RedBox.");
336
+ }
337
+ lines.push("`agent-device metro reload` does NOT recover either screen: it only reaches an app already connected to Metro's websocket, and this app never connected.");
338
+ lines.push(`Then check \`stim logs --source metro\`${mode ? ` (${mode})` : ""} for a bundle request.`);
339
+ return lines;
340
+ }
341
+ push(`Tap Allow on the phone's "would like to find and connect to devices on your local network" prompt if it is showing, or turn the app on under Settings > Privacy & Security > Local Network. iOS gates every LAN connection behind that permission, it cannot be PRE-granted from this machine -- but once the prompt is up a device tool can accept it (agent-device alert get, then agent-device alert accept, both --platform ios --udid ${udid}) -- and until it is granted nothing the app sends reaches ${target}.`);
342
+ push("Check the phone is on the same network as this Mac -- the same Wi-Fi SSID, not cellular, not a VPN -- and that the network does not isolate clients from each other.");
343
+ push("Check macOS is not blocking inbound connections: System Settings > Network > Firewall, or `/usr/libexec/ApplicationFirewall/socketfilterfw --getglobalstate`. The gate on this machine passes either way, because macOS routes a host connection to its own address over loopback.");
344
+ push(`If this Mac has several network interfaces, ${target} may not be the one the phone shares: set ios.lanHost in .stim.json to the address it can reach.`);
345
+ if (devClient) {
346
+ push(picker);
347
+ if (url && udid) push(`Retry the deep link: xcrun devicectl device process launch --device ${udid} --terminate-existing --payload-url '${url}' ${bundleId} -- ${DEV_MENU_LAUNCH_ARGS.join(" ")}`);
348
+ } else lines.push("AND READ THIS: a Debug device build with no dev client carries the JS bundle baked in when the artifact was built, so an unreachable Metro is silent. The app on screen is not broken -- it is running THAT bundle, which on a cache hit is another workspace's JS, not this workspace's.");
349
+ lines.push(`Then check \`stim logs --source metro\`${mode ? ` (${mode})` : ""} for a bundle request.`);
350
+ return lines;
351
+ }
352
+ if (platform === "ios") {
353
+ if (devClient) push(picker);
354
+ if (url && udid) push(`Retry the deep link: xcrun simctl openurl ${udid} '${url}'`);
355
+ else if (udid && bundleId) push(`Re-launch: xcrun simctl launch --terminate-running-process ${udid} ${bundleId}`);
356
+ } else {
357
+ if (url && serial) push(`Re-send the dev-client deep link -- this is the command that points the app at THIS workspace's Metro: adb -s ${serial} shell am start -a android.intent.action.VIEW -d ${deviceShellArg(deviceShellArg(url))} --ez ${ANDROID_DISABLE_AUTO_LAUNCH_EXTRA} true`);
358
+ if (devClient) push(picker);
359
+ if (serial && bundleId) push(`If the app is stuck, restart its process: ${component ? `adb -s ${serial} shell am force-stop ${bundleId} && adb -s ${serial} shell am start -n ${component}` : url ? `adb -s ${serial} shell am force-stop ${bundleId} && adb -s ${serial} shell am start -a android.intent.action.VIEW -d ${deviceShellArg(deviceShellArg(url))} --ez ${ANDROID_DISABLE_AUTO_LAUNCH_EXTRA} true` : `adb -s ${serial} shell am force-stop ${bundleId} && adb -s ${serial} shell monkey -p ${bundleId} 1`}`);
360
+ }
361
+ lines.push(`Then check \`stim logs --source metro\`${mode ? ` (${mode})` : ""} for a bundle request.`);
362
+ return lines;
363
+ }
364
+ //#endregion
365
+ export { VERIFY_TIMEOUT_MS as a, readMetroRecords as c, verifyLaunch as d, verifyReleaseLaunch as f, RELEASE_VERIFY_WAIT_MS as i, unverifiedLaunchLines as l, LAUNCH_FATAL as n, isBundleProof as o, APP_READINESS_TIMEOUT_MS as p, LAUNCH_UNVERIFIED as r, readCollectorRecords as s, LAUNCH_BUNDLING as t, verifyAndroidReleaseLaunch as u };
@@ -1,11 +1,11 @@
1
- import { B as validateDeviceSlot, g as loadConfig, h as isPathPrefix } from "./config-B7iyKWkL.mjs";
2
- import { n as SOURCES, t as LEVELS } from "./ndjson-BZwI1NBv.mjs";
3
- import { _ as workspaceLogsDir, r as readWorkspaceState } from "./workspace-state-BT5E3SA2.mjs";
4
- import { a as findProjectRoot } from "./project-DDZWf_16.mjs";
5
- import { _ as launchErrorPreview } from "./command-output-B3ycpsYv.mjs";
6
- import { i as captureWorkspaceCrashes, t as errorDiagnostics } from "./error-diagnostics-D1Kaf-eT.mjs";
7
- import { a as followLogs, c as queryLogs, i as fileSizes, n as buildCriteria, o as logFiles, r as compileGrep, s as parseSince } from "./logs-query-BGL-Pl12.mjs";
1
+ import { V as parseDeviceSlotOption, g as loadConfig$1, h as isPathPrefix } from "./config-BEq0Qw_a.mjs";
2
+ import { n as SOURCES, t as LEVELS } from "./ndjson-CDY9IkVC.mjs";
3
+ import { j as workspaceLogsDir, m as readWorkspaceState$1 } from "./metro-BWLOJ_Wr.mjs";
4
+ import { o as findProjectRoot } from "./project-BXZzOABt.mjs";
5
+ import { f as refuseNoProject, v as launchErrorPreview } from "./command-output-D5z2_bcF.mjs";
6
+ import { i as captureWorkspaceCrashes, t as errorDiagnostics } from "./error-diagnostics-xxWKNO3-.mjs";
8
7
  import { realpathSync } from "node:fs";
8
+ import { buildCriteria, compileGrep, fileSizes, followLogs, logFiles, parseSince, queryLogs, readLogRecords } from "@stim-cli/core/state";
9
9
  import { relative, sep } from "node:path";
10
10
  import chalk from "chalk";
11
11
  //#region src/commands/logs.ts
@@ -70,7 +70,7 @@ function nearestRegisteredLogsProject(root, candidates) {
70
70
  })[0] ?? null;
71
71
  }
72
72
  function registeredDescendantWithLogs(root) {
73
- const candidates = Object.keys(loadConfig()?.projects ?? {}).flatMap((path) => {
73
+ const candidates = Object.keys(loadConfig$1()?.projects ?? {}).flatMap((path) => {
74
74
  try {
75
75
  const canonical = realpathSync(path);
76
76
  return logFiles(workspaceLogsDir(canonical)).length > 0 ? [canonical] : [];
@@ -93,11 +93,11 @@ const LEVEL_COLOURS = {
93
93
  fatal: chalk.bgRed
94
94
  };
95
95
  function logsCommand(program) {
96
- program.command("logs").description("Query this workspace's merged NDJSON log timeline (bundler, client, device, build). Prints and exits; an existing timeline with nothing matching is a successful, empty result. Use --follow to stream.").option("--slot <name>", "Only records attributed to this device slot", validateDeviceSlot).option("--source <s...>", "Only these sources: metro, client, device, build, or all").option("--level <l>", `Minimum level: ${LEVELS.join(", ")}`).option("--since <d>", "Only records newer than this, e.g. 30s, 5m, 2h").option("--grep <re>", "Only records whose message matches this regular expression").option("--tail <n>", "Only the last n matching records").option("--errors", "Errors and fatals since the last marker, from metro, client and build, plus confirmed native app-crash reports. Add --source device or --source all for general device errors.").option("--follow", "Keep streaming new records until interrupted").option("--json", "Emit the raw records, one per line (valid NDJSON; zero matches is zero bytes, exit 0)").action(async (opts) => {
96
+ program.command("logs").description("Query this workspace's merged NDJSON log timeline (bundler, client, device, build). Prints and exits; an existing timeline with nothing matching is a successful, empty result. Use --follow to stream.").option("--slot <name>", "Only records attributed to this device slot", parseDeviceSlotOption).option("--source <s...>", "Only these sources: metro, client, device, build, or all").option("--level <l>", `Minimum level: ${LEVELS.join(", ")}`).option("--since <d>", "Only records newer than this, e.g. 30s, 5m, 2h").option("--grep <re>", "Only records whose message matches this regular expression").option("--tail <n>", "Only the last n matching records").option("--errors", "Errors and fatals since the last marker, from metro, client and build, plus confirmed native app-crash reports. Add --source device or --source all for general device errors.").option("--follow", "Keep streaming new records until interrupted").option("--json", "Emit the raw records, one per line (valid NDJSON; zero matches is zero bytes, exit 0)").action(async (opts) => {
97
97
  const root = findProjectRoot(process.cwd());
98
98
  if (!root) {
99
- console.error(chalk.red("Not in a React Native project (no package.json found)."));
100
- process.exit(1);
99
+ refuseNoProject({ json: false });
100
+ return;
101
101
  }
102
102
  const dir = workspaceLogsDir(root);
103
103
  let sources;
@@ -150,15 +150,20 @@ function logsCommand(program) {
150
150
  };
151
151
  console.log(formatRecord(rendered, { paint: record?.level ? LEVEL_COLOURS[record.level] : void 0 }));
152
152
  };
153
+ const captured = captureWorkspaceCrashes(root, dir);
153
154
  const offsets = opts.follow ? fileSizes(dir) : null;
154
- captureWorkspaceCrashes(root, dir);
155
- const rawRecords = queryLogs(query);
156
- const supervisorPort = readWorkspaceState(root)?.supervisor?.port;
155
+ const timeline = opts.follow ? readLogRecords(dir) : captured;
156
+ const rawRecords = queryLogs({
157
+ ...query,
158
+ records: timeline
159
+ });
160
+ const supervisorPort = readWorkspaceState$1(root)?.supervisor?.port;
157
161
  const records = opts.json ? rawRecords : await errorDiagnostics(rawRecords, {
158
162
  root,
159
163
  logsDir: dir,
160
164
  port: typeof supervisorPort === "number" ? supervisorPort : null,
161
- allowRequest: true
165
+ allowRequest: true,
166
+ timeline
162
167
  });
163
168
  const errorCount = records.filter((record) => record.errorContext !== true).length;
164
169
  let seenErrors = 0;