stim 1.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 (63) hide show
  1. package/README.md +22 -10
  2. package/dist/android-CC5bUer7.mjs +204 -0
  3. package/dist/android-DnZlT3aU.mjs +2279 -0
  4. package/dist/android-cas-DGlTStWu.mjs +1078 -0
  5. package/dist/android-cas-compiler.mjs +1 -1
  6. package/dist/android-xmyGBNy5.mjs +665 -0
  7. package/dist/app-install-oZIQRbdD.mjs +1141 -0
  8. package/dist/build-lock-vE45ZzJZ.mjs +645 -0
  9. package/dist/build-slots-CgacA9zQ.mjs +152 -0
  10. package/dist/{cache-manifest-4tH64LQ9.mjs → cache-manifest-oeH18ERr.mjs} +2 -2
  11. package/dist/cache-manifest.mjs +1 -1
  12. package/dist/cli.mjs +24 -27166
  13. package/dist/collector-run.mjs +6 -4
  14. package/dist/command-output-8wHMEde0.mjs +289 -0
  15. package/dist/{config-7kmtuhO2.mjs → config-D7uu8Gbq.mjs} +43 -12
  16. package/dist/deps-64wVnpZc.mjs +435 -0
  17. package/dist/dev-client-Bvky1hya.mjs +557 -0
  18. package/dist/device-DJKv5Qej.mjs +250 -0
  19. package/dist/device-lease-D-G3SgVe.mjs +335 -0
  20. package/dist/device-pool-BlHXnYYs.mjs +408 -0
  21. package/dist/device-remote-CCgkG82J.mjs +1209 -0
  22. package/dist/doctor-BRF9aAJ3.mjs +530 -0
  23. package/dist/doctor-DlVxTtMq.mjs +1137 -0
  24. package/dist/error-diagnostics-B5IJwv3-.mjs +636 -0
  25. package/dist/gc-DhXdgSdt.mjs +1651 -0
  26. package/dist/guide-DrhrDBo5.mjs +4319 -0
  27. package/dist/ios-BZxKPuqP.mjs +382 -0
  28. package/dist/ios-CK1Vrk6Q.mjs +2721 -0
  29. package/dist/ios-device-CYbuRNXR.mjs +81 -0
  30. package/dist/ios-device-CdSm0GDw.mjs +413 -0
  31. package/dist/logs-Dxoy16Qv.mjs +176 -0
  32. package/dist/logs-query-oqn47Ffx.mjs +273 -0
  33. package/dist/metro-B01H3EuD.mjs +184 -0
  34. package/dist/metro-store-CDDZHtPl.mjs +92 -0
  35. package/dist/ownership-B9QIdQOw.mjs +475 -0
  36. package/dist/ownership-D8HmSv2E.mjs +60 -0
  37. package/dist/ownership-claim-CeJLEpjD.mjs +569 -0
  38. package/dist/{project-Dit1CckW.mjs → project-COj6nzzh.mjs} +12 -3
  39. package/dist/reclaim-BPvN2HlP.mjs +347 -0
  40. package/dist/reload-DYZEBbZy.mjs +332 -0
  41. package/dist/remote-cache-DOi5-zLj.mjs +934 -0
  42. package/dist/{server-bare-BmTVVmBB.mjs → server-bare-BYHqEeyw.mjs} +3 -2
  43. package/dist/{server-expo-CHJe1r6j.mjs → server-expo-BEMr_m6p.mjs} +5 -4
  44. package/dist/server-expo-rb0k-0w7.mjs +2 -0
  45. package/dist/settings-BSwrY4FZ.mjs +687 -0
  46. package/dist/spawn-entry-nCE6m770.mjs +14 -0
  47. package/dist/start-ByCylYtB.mjs +822 -0
  48. package/dist/{state-2nush3MK.mjs → state-IYEY-zi8.mjs} +21 -22
  49. package/dist/stats-DZ4Q5J-7.mjs +62 -0
  50. package/dist/stats-wqxPm7Tv.mjs +307 -0
  51. package/dist/status-Dia0XTqG.mjs +363 -0
  52. package/dist/stop-BApyaF21.mjs +591 -0
  53. package/dist/stop-D6o5zAdm.mjs +3 -0
  54. package/dist/supervisor-run.d.mts +0 -1
  55. package/dist/supervisor-run.mjs +4 -4
  56. package/dist/workspace-process-lock-CxOO9-pf.mjs +47 -0
  57. package/dist/worktree-Dx3ZC5lS.mjs +539 -0
  58. package/dist/worktree-DxEmUI_n.mjs +1322 -0
  59. package/dist/xcode-CDB6fytw.mjs +1705 -0
  60. package/package.json +5 -5
  61. package/dist/android-jc8MnUyL.mjs +0 -514
  62. package/dist/metro-store-COLl1pOk.mjs +0 -1249
  63. package/dist/server-expo-BZJm--wA.mjs +0 -2
@@ -0,0 +1,81 @@
1
+ import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
+ //#region src/collector/ios-device.ts
3
+ const CONSOLE_ENV = { OS_ACTIVITY_DT_MODE: "enable" };
4
+ const DEV_MENU_LAUNCH_ARGS = [
5
+ "-EXDevMenuShowsAtLaunch",
6
+ "0",
7
+ "-EXDevMenuShowFloatingActionButton",
8
+ "0"
9
+ ];
10
+ const FATAL_MARKERS = [
11
+ /^\*\*\* Terminating app due to uncaught exception/,
12
+ /^libc\+\+abi: terminating/,
13
+ /^\*\*\* Assertion failure in/,
14
+ /^(?:\S+:\d+: )?Fatal error: /
15
+ ];
16
+ const MIRROR_LINE = /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d+[+-]\d{4}) (.+?)\[(\d+):(\d+)\] ?(?:\[([^\]\s]+)\] )?([\s\S]*)$/;
17
+ function deviceConsoleLevel(message) {
18
+ const text = typeof message === "string" ? message : "";
19
+ return FATAL_MARKERS.some((marker) => marker.test(text)) ? "fatal" : "info";
20
+ }
21
+ function parseDeviceConsoleLine(line, { now = Date.now } = {}) {
22
+ if (typeof line !== "string") return null;
23
+ const text = line.replace(/\r$/, "").trimEnd();
24
+ if (!text.trim()) return null;
25
+ const match = MIRROR_LINE.exec(text);
26
+ const record = {
27
+ ts: now(),
28
+ src: "device",
29
+ level: "info",
30
+ msg: text,
31
+ raw: true
32
+ };
33
+ if (!match) {
34
+ record.level = text.startsWith("ERROR: ") ? "error" : deviceConsoleLevel(text);
35
+ return record;
36
+ }
37
+ const [, timestamp, proc, pid, , category, message] = match;
38
+ if (message === void 0 || !message.trim()) return null;
39
+ const parsed = Date.parse(timestamp);
40
+ if (Number.isFinite(parsed)) record.ts = parsed;
41
+ record.msg = message;
42
+ record.level = deviceConsoleLevel(message);
43
+ record.proc = `${proc}(${pid})`;
44
+ if (category) record.category = category;
45
+ return record;
46
+ }
47
+ function deviceConsoleArgs({ udid, bundleId, payloadUrl = null }) {
48
+ const args = [
49
+ "devicectl",
50
+ "device",
51
+ "process",
52
+ "launch",
53
+ "--quiet",
54
+ "--device",
55
+ udid,
56
+ "--console",
57
+ "--terminate-existing",
58
+ "--environment-variables",
59
+ JSON.stringify(CONSOLE_ENV)
60
+ ];
61
+ if (payloadUrl) args.push("--payload-url", payloadUrl);
62
+ args.push(bundleId);
63
+ if (payloadUrl) args.push("--", ...DEV_MENU_LAUNCH_ARGS);
64
+ return args;
65
+ }
66
+ function startIosDeviceConsole({ udid, bundleId, payloadUrl = null, spawnFn = null }) {
67
+ return (spawnFn || ((cmd, args, opts) => getExecutor().spawn(cmd, args, opts)))("xcrun", deviceConsoleArgs({
68
+ udid,
69
+ bundleId,
70
+ payloadUrl
71
+ }), {
72
+ stdio: [
73
+ "ignore",
74
+ "pipe",
75
+ "pipe"
76
+ ],
77
+ detached: false
78
+ });
79
+ }
80
+ //#endregion
81
+ export { startIosDeviceConsole as i, deviceConsoleLevel as n, parseDeviceConsoleLine as r, DEV_MENU_LAUNCH_ARGS as t };
@@ -0,0 +1,413 @@
1
+ import { t as getExecutor } from "./exec-bsN9MJXb.mjs";
2
+ import { r as INSTALL_ERROR } from "./app-install-oZIQRbdD.mjs";
3
+ import { mkdtempSync, readFileSync, rmSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ import { tmpdir } from "node:os";
6
+ //#region src/engine/ios-device.ts
7
+ const DEVICECTL_TIMEOUT_MS = 3e4;
8
+ function text(value) {
9
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
10
+ }
11
+ function record(value) {
12
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
13
+ }
14
+ function parseDevicectlDevices(payload) {
15
+ let data = payload;
16
+ if (typeof payload === "string") try {
17
+ data = JSON.parse(payload);
18
+ } catch {
19
+ return [];
20
+ }
21
+ const devices = record(record(data).result).devices;
22
+ if (!Array.isArray(devices)) return [];
23
+ const out = [];
24
+ for (const raw of devices) {
25
+ const entry = record(raw);
26
+ const hardware = record(entry.hardwareProperties);
27
+ const properties = record(entry.deviceProperties);
28
+ const connection = record(entry.connectionProperties);
29
+ const udid = text(hardware.udid);
30
+ if (!udid) continue;
31
+ out.push({
32
+ udid,
33
+ name: text(properties.name) ?? udid,
34
+ bootState: text(properties.bootState),
35
+ developerModeStatus: text(properties.developerModeStatus),
36
+ pairingState: text(connection.pairingState),
37
+ transportType: text(connection.transportType)
38
+ });
39
+ }
40
+ return out;
41
+ }
42
+ function listIosDevices({ exec = null } = {}) {
43
+ const executor = exec || getExecutor();
44
+ const dir = mkdtempSync(join(tmpdir(), "stim-devicectl-"));
45
+ const out = join(dir, "devices.json");
46
+ try {
47
+ executor.runFile("xcrun", [
48
+ "devicectl",
49
+ "list",
50
+ "devices",
51
+ "-j",
52
+ out
53
+ ], { timeoutMs: DEVICECTL_TIMEOUT_MS });
54
+ return parseDevicectlDevices(readFileSync(out, "utf-8"));
55
+ } catch {
56
+ return [];
57
+ } finally {
58
+ rmSync(dir, {
59
+ recursive: true,
60
+ force: true
61
+ });
62
+ }
63
+ }
64
+ const DEVELOPER_MODE_REMEDY = "Turn on Settings > Privacy & Security > Developer Mode on the phone, restart it, then reconnect.";
65
+ const PAIRING_REMEDY = "Unlock the phone, tap Trust on the pairing prompt, then reconnect the cable.";
66
+ const CABLE_REMEDY = "Connect the phone with a cable and check `xcrun devicectl list devices`, then retry.";
67
+ const WIRED_TRANSPORTS = /* @__PURE__ */ new Set(["wired", "usb"]);
68
+ function isWired(device) {
69
+ return device.transportType === null || WIRED_TRANSPORTS.has(device.transportType.toLowerCase());
70
+ }
71
+ function describe(device) {
72
+ return `${device.udid} (${device.name})`;
73
+ }
74
+ function wirelessRefusal(device) {
75
+ return {
76
+ error: `${describe(device)} is paired over ${device.transportType}, not a cable, and Stim installs over the cable only.`,
77
+ remedy: CABLE_REMEDY
78
+ };
79
+ }
80
+ function unhealthy(device) {
81
+ if (device.pairingState !== null && device.pairingState.toLowerCase() !== "paired") return {
82
+ error: `${describe(device)} is connected but ${device.pairingState}, so devicectl cannot drive it.`,
83
+ remedy: PAIRING_REMEDY
84
+ };
85
+ if (device.developerModeStatus !== null && device.developerModeStatus.toLowerCase() !== "enabled") return {
86
+ error: `${describe(device)} has Developer Mode ${device.developerModeStatus}, so it will not run a development build.`,
87
+ remedy: DEVELOPER_MODE_REMEDY
88
+ };
89
+ return null;
90
+ }
91
+ function iosPoolCandidates(devices) {
92
+ return (Array.isArray(devices) ? devices : []).filter((device) => isWired(device) && unhealthy(device) === null);
93
+ }
94
+ function iosPoolNoCandidatesRefusal(devices) {
95
+ const listed = Array.isArray(devices) ? devices : [];
96
+ const cabled = listed.filter(isWired);
97
+ const problems = cabled.map((device) => unhealthy(device));
98
+ if (cabled.length > 0 && problems.every((problem) => problem !== null)) {
99
+ const reasons = problems;
100
+ return {
101
+ error: reasons.map((reason) => reason.error).join(" "),
102
+ remedy: [...new Set(reasons.map((reason) => reason.remedy))].join(" ")
103
+ };
104
+ }
105
+ return resolveIosPhysicalDevice(null, listed);
106
+ }
107
+ function resolveIosPhysicalDevice(requested, devices) {
108
+ const listed = Array.isArray(devices) ? devices : [];
109
+ const cabled = listed.filter(isWired);
110
+ const wireless = listed.filter((d) => !isWired(d));
111
+ if (requested) {
112
+ const match = cabled.find((d) => d.udid.toLowerCase() === requested.toLowerCase());
113
+ if (match) return unhealthy(match) ?? {
114
+ udid: match.udid,
115
+ name: match.name
116
+ };
117
+ const overNetwork = wireless.find((d) => d.udid.toLowerCase() === requested.toLowerCase());
118
+ if (overNetwork) return wirelessRefusal(overNetwork);
119
+ return {
120
+ error: cabled.length ? `${requested} is not connected. devicectl reports these cabled devices: ${cabled.map(describe).join(", ")}.` : `${requested} is not connected by cable, and devicectl reports no cabled device at all.`,
121
+ remedy: "Check the cable and `xcrun devicectl list devices`, then retry with a UDID it lists."
122
+ };
123
+ }
124
+ if (cabled.length === 1) {
125
+ const only = cabled[0];
126
+ return unhealthy(only) ?? {
127
+ udid: only.udid,
128
+ name: only.name
129
+ };
130
+ }
131
+ if (cabled.length > 1) return {
132
+ error: `Several devices are connected: ${cabled.map(describe).join(", ")}.`,
133
+ remedy: "Name the one to build for with `stim ios --device <udid>`."
134
+ };
135
+ const onlyWireless = wireless[0];
136
+ if (onlyWireless) return wirelessRefusal(onlyWireless);
137
+ return {
138
+ error: "No physical iOS device is connected.",
139
+ remedy: "Plug the phone in, unlock it, tap Trust, turn on Settings > Privacy & Security > Developer Mode, then check `xcrun devicectl list devices`."
140
+ };
141
+ }
142
+ const DEVICECTL_INSTALL_TIMEOUT_MS = 3e5;
143
+ const LOCKED_REMEDY = "Unlock the phone and keep it awake, then run the command again.";
144
+ function errorText(error) {
145
+ const withOutput = error;
146
+ const stderr = String(withOutput?.stderr ?? "").trim();
147
+ const stdout = String(withOutput?.stdout ?? "").trim();
148
+ return [
149
+ String(withOutput?.message ?? error).trim(),
150
+ stderr,
151
+ stdout
152
+ ].filter(Boolean).join("\n");
153
+ }
154
+ function iosInstallFailureKind(output) {
155
+ const out = String(output ?? "");
156
+ if (/MismatchedApplicationIdentifierEntitlement|application-identifier entitlement[^\n]*does not match/i.test(out)) return "signer";
157
+ if (/device is locked|is locked\b|passcode/i.test(out)) return "locked";
158
+ if (/not paired|pairing|trust this computer|untrusted host/i.test(out)) return "untrusted-host";
159
+ if (/developer mode/i.test(out)) return "developer-mode";
160
+ if (/not enough (?:free )?(?:disk )?space|insufficient (?:disk )?space|storage is full/i.test(out)) return "storage";
161
+ return null;
162
+ }
163
+ function iosInstallRemedy(kind, { udid, bundleId }) {
164
+ switch (kind) {
165
+ case "signer": return `${bundleId ?? "The app"} is already installed on ${udid} under a different team, and Stim could not remove it. Delete the app on the phone (its data goes with it), then run the command again.`;
166
+ case "locked": return LOCKED_REMEDY;
167
+ case "untrusted-host": return "Unlock the phone, tap Trust on the pairing prompt, then run the command again.";
168
+ case "developer-mode": return "Turn on Settings > Privacy & Security > Developer Mode on the phone, restart it, then run the command again.";
169
+ case "storage": return "Free space on the phone (Settings > General > iPhone Storage), then run the command again.";
170
+ default: return `Check that ${udid} is still connected and unlocked (\`xcrun devicectl list devices\`), and that the app was built for the iphoneos SDK.`;
171
+ }
172
+ }
173
+ function installIosDeviceApp({ udid, appPath, bundleId = null }, { exec = null } = {}) {
174
+ const e = exec || getExecutor();
175
+ const install = () => {
176
+ e.runFile("xcrun", [
177
+ "devicectl",
178
+ "device",
179
+ "install",
180
+ "app",
181
+ "--device",
182
+ udid,
183
+ appPath
184
+ ], { timeoutMs: DEVICECTL_INSTALL_TIMEOUT_MS });
185
+ };
186
+ try {
187
+ install();
188
+ return {
189
+ ok: true,
190
+ appPath
191
+ };
192
+ } catch (err) {
193
+ const failure = errorText(err);
194
+ const kind = iosInstallFailureKind(failure);
195
+ if (kind !== "signer" || !bundleId) return {
196
+ failed: true,
197
+ code: INSTALL_ERROR,
198
+ reason: `devicectl could not install ${appPath} on ${udid}: ${failure}`,
199
+ remedy: iosInstallRemedy(kind, {
200
+ udid,
201
+ bundleId
202
+ })
203
+ };
204
+ try {
205
+ e.runFile("xcrun", [
206
+ "devicectl",
207
+ "device",
208
+ "uninstall",
209
+ "app",
210
+ "--device",
211
+ udid,
212
+ bundleId
213
+ ], { timeoutMs: DEVICECTL_INSTALL_TIMEOUT_MS });
214
+ } catch (uninstallErr) {
215
+ return {
216
+ failed: true,
217
+ code: INSTALL_ERROR,
218
+ reason: `devicectl refused to install ${appPath} over the copy of ${bundleId} already on ${udid}, which a different team signed, and the uninstall failed too: ${errorText(uninstallErr)}`,
219
+ remedy: iosInstallRemedy("signer", {
220
+ udid,
221
+ bundleId
222
+ })
223
+ };
224
+ }
225
+ try {
226
+ install();
227
+ } catch (retryErr) {
228
+ return {
229
+ failed: true,
230
+ code: INSTALL_ERROR,
231
+ reason: `devicectl could not install ${appPath} on ${udid} even after uninstalling ${bundleId}: ${errorText(retryErr)}`,
232
+ remedy: iosInstallRemedy(null, {
233
+ udid,
234
+ bundleId
235
+ })
236
+ };
237
+ }
238
+ return {
239
+ ok: true,
240
+ appPath,
241
+ uninstalled: true,
242
+ note: `${bundleId} was already installed on ${udid} under a different team, so it was uninstalled (its data went with it) before this app could be installed. An uninstall also clears the phone's developer trust and its Local Network permission, so the launch below may need Settings > General > VPN & Device Management again`
243
+ };
244
+ }
245
+ }
246
+ function parseDeviceProcesses(payload) {
247
+ let data = payload;
248
+ if (typeof payload === "string") try {
249
+ data = JSON.parse(payload);
250
+ } catch {
251
+ return [];
252
+ }
253
+ const running = record(record(data).result).runningProcesses;
254
+ if (!Array.isArray(running)) return [];
255
+ const out = [];
256
+ for (const raw of running) {
257
+ const entry = record(raw);
258
+ const pid = Number(entry.processIdentifier);
259
+ const executable = text(entry.executable);
260
+ if (!Number.isInteger(pid) || pid <= 0 || !executable) continue;
261
+ out.push({
262
+ pid,
263
+ executable
264
+ });
265
+ }
266
+ return out;
267
+ }
268
+ function deviceProcessPid(processes, appName) {
269
+ const marker = `/${appName}.app/`;
270
+ for (const entry of processes) if (entry.executable.includes(marker)) return entry.pid;
271
+ return null;
272
+ }
273
+ function iosDeviceProcess({ udid, appName }, { exec = null } = {}) {
274
+ const executor = exec || getExecutor();
275
+ const dir = mkdtempSync(join(tmpdir(), "stim-devicectl-"));
276
+ const out = join(dir, "processes.json");
277
+ try {
278
+ executor.runFile("xcrun", [
279
+ "devicectl",
280
+ "device",
281
+ "info",
282
+ "processes",
283
+ "--device",
284
+ udid,
285
+ "--quiet",
286
+ "--json-output",
287
+ out
288
+ ], { timeoutMs: DEVICECTL_TIMEOUT_MS });
289
+ return deviceProcessPid(parseDeviceProcesses(readFileSync(out, "utf-8")), appName);
290
+ } catch {
291
+ return;
292
+ } finally {
293
+ rmSync(dir, {
294
+ recursive: true,
295
+ force: true
296
+ });
297
+ }
298
+ }
299
+ const LAUNCH_REFUSALS = [
300
+ ["untrusted-developer", /profile has not been explicitly trusted by the user/i],
301
+ ["untrusted-developer", /FBSOpenApplicationErrorDomain error 3\b/],
302
+ ["untrusted-developer", /denied by service delegate[^\n]*for reason: Security/],
303
+ ["locked", /device is locked|is locked\b|passcode/i],
304
+ ["not-installed", /(?:application|app)[^\n]*\b(?:is )?(?:unknown to FrontBoard|not installed)/i]
305
+ ];
306
+ function iosLaunchRefusalKind(output) {
307
+ for (const line of String(output ?? "").split(/\r?\n/)) for (const [kind, pattern] of LAUNCH_REFUSALS) if (pattern.test(line)) return kind;
308
+ return null;
309
+ }
310
+ function iosLaunchRemedy(kind, { udid, bundleId }) {
311
+ switch (kind) {
312
+ case "untrusted-developer": return "The phone has not trusted this build's developer certificate, which it refuses to do without a human. On the phone open Settings > General > VPN & Device Management, tap the developer profile under DEVELOPER APP, tap Trust, then run the command again.";
313
+ case "locked": return LOCKED_REMEDY;
314
+ case "not-installed": return `${bundleId} is not installed on ${udid} any more. Run the command again to install it.`;
315
+ default: return `Run \`xcrun devicectl device process launch --console --device ${udid} ${bundleId}\` to see what the phone reports.`;
316
+ }
317
+ }
318
+ const LAUNCH_PROBE_TIMEOUT_MS = 45e3;
319
+ const LAUNCH_PROBE_POLL_MS = 1e3;
320
+ const COLLECTOR_ENDED_EVENTS = /* @__PURE__ */ new Set(["collector_failed", "collector_stopped"]);
321
+ function launchEvidence(records) {
322
+ const lines = [];
323
+ for (const entry of records) {
324
+ const msg = typeof entry.msg === "string" ? entry.msg.trim() : "";
325
+ if (!msg) continue;
326
+ if (entry.event === "collector_started" || entry.event === "collector_empty") continue;
327
+ lines.push(msg);
328
+ }
329
+ return lines.slice(-6);
330
+ }
331
+ function collectorRecordsFor(records, collectorPid) {
332
+ if (!collectorPid) return [];
333
+ const marker = new RegExp(`\\bcollector pid ${collectorPid}\\b`);
334
+ const start = records.findIndex((entry) => entry.event === "collector_started" && typeof entry.msg === "string" && marker.test(entry.msg));
335
+ return start < 0 ? [] : records.slice(start);
336
+ }
337
+ async function awaitIosDeviceLaunch({ udid, bundleId, appName, collectorPid = null, readRecords, probe, timeoutMs = LAUNCH_PROBE_TIMEOUT_MS, pollMs = LAUNCH_PROBE_POLL_MS, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) }) {
338
+ const probeProcess = probe ?? (() => iosDeviceProcess({
339
+ udid,
340
+ appName
341
+ }));
342
+ const deadline = now() + Math.max(0, timeoutMs);
343
+ for (;;) {
344
+ const records = collectorRecordsFor(readRecords(), collectorPid);
345
+ const ended = records.find((entry) => typeof entry.event === "string" && COLLECTOR_ENDED_EVENTS.has(entry.event));
346
+ const pid = probeProcess();
347
+ if (typeof pid === "number") return { pid };
348
+ if (ended) {
349
+ const lines = launchEvidence(records);
350
+ const kind = iosLaunchRefusalKind(lines.join("\n"));
351
+ return {
352
+ failed: true,
353
+ reason: `devicectl could not keep ${bundleId} running on ${udid}: the console ended before the app appeared in the device's process list.`,
354
+ remedy: iosLaunchRemedy(kind, {
355
+ udid,
356
+ bundleId
357
+ }),
358
+ lines
359
+ };
360
+ }
361
+ if (now() >= deadline) {
362
+ const lines = launchEvidence(records);
363
+ return {
364
+ failed: true,
365
+ reason: `${bundleId} did not appear in ${udid}'s process list within ${Math.round(timeoutMs / 1e3)}s of the launch.`,
366
+ remedy: iosLaunchRemedy(iosLaunchRefusalKind(lines.join("\n")), {
367
+ udid,
368
+ bundleId
369
+ }),
370
+ lines
371
+ };
372
+ }
373
+ await sleep(Math.min(pollMs, Math.max(0, deadline - now())));
374
+ }
375
+ }
376
+ async function verifyIosDeviceReleaseLaunch({ udid, appName, waitMs = 3e3, probe, now = Date.now, sleep = (ms) => new Promise((r) => setTimeout(r, ms)) }) {
377
+ const probeProcess = probe ?? (() => iosDeviceProcess({
378
+ udid,
379
+ appName
380
+ }));
381
+ const startedAt = now();
382
+ await sleep(Math.max(0, waitMs));
383
+ const pid = probeProcess();
384
+ const waitedMs = now() - startedAt;
385
+ if (pid === void 0) return {
386
+ verified: false,
387
+ reason: "probe-failed",
388
+ waitedMs
389
+ };
390
+ return pid === null ? {
391
+ verified: false,
392
+ reason: "exited",
393
+ waitedMs,
394
+ pid: null
395
+ } : {
396
+ verified: true,
397
+ waitedMs,
398
+ pid
399
+ };
400
+ }
401
+ const LOCAL_NETWORK_PROHIBITED = /_NSURLErrorNWPathKey=unsatisfied \(Local network prohibited\)/;
402
+ function localNetworkPending(records, { since = 0, pid = null, lanOrigin }) {
403
+ if (!lanOrigin) return false;
404
+ const fromApp = pid ? new RegExp(`\\(${pid}\\)$`) : null;
405
+ for (const entry of records) {
406
+ if (Number(entry.ts) < since) continue;
407
+ if (fromApp && !(typeof entry.proc === "string" && fromApp.test(entry.proc))) continue;
408
+ if (LOCAL_NETWORK_PROHIBITED.test(typeof entry.msg === "string" ? entry.msg : "")) return true;
409
+ }
410
+ return false;
411
+ }
412
+ //#endregion
413
+ export { iosDeviceProcess as a, listIosDevices as c, verifyIosDeviceReleaseLaunch as d, installIosDeviceApp as i, localNetworkPending as l, LAUNCH_PROBE_TIMEOUT_MS as n, iosPoolCandidates as o, awaitIosDeviceLaunch as r, iosPoolNoCandidatesRefusal as s, DEVICECTL_INSTALL_TIMEOUT_MS as t, resolveIosPhysicalDevice as u };
@@ -0,0 +1,176 @@
1
+ import { n as SOURCES, t as LEVELS } from "./ndjson-DcAtEx_K.mjs";
2
+ import { D as workspaceLogsDir, p as readWorkspaceState } from "./state-IYEY-zi8.mjs";
3
+ import { a as findProjectRoot } from "./project-COj6nzzh.mjs";
4
+ import { _ as launchErrorPreview } from "./command-output-8wHMEde0.mjs";
5
+ import { i as captureWorkspaceCrashes, t as errorDiagnostics } from "./error-diagnostics-B5IJwv3-.mjs";
6
+ import { a as followLogs, i as fileSizes, n as buildCriteria, o as parseSince, r as compileGrep, s as queryLogs } from "./logs-query-oqn47Ffx.mjs";
7
+ import chalk from "chalk";
8
+ //#region src/commands/logs.ts
9
+ const LEVEL_WIDTH = 5;
10
+ const SRC_WIDTH = 6;
11
+ const INDENT = " ";
12
+ function padTimePart(value, width = 2) {
13
+ return String(value).padStart(width, "0");
14
+ }
15
+ function formatTime(ts) {
16
+ if (typeof ts !== "number" || !Number.isFinite(ts)) return "--:--:--.---";
17
+ const d = new Date(ts);
18
+ return `${padTimePart(d.getHours())}:${padTimePart(d.getMinutes())}:${padTimePart(d.getSeconds())}.${padTimePart(d.getMilliseconds(), 3)}`;
19
+ }
20
+ function formatStackFrame(frame) {
21
+ if (!frame || typeof frame !== "object") return null;
22
+ const where = [
23
+ frame.file,
24
+ frame.line,
25
+ frame.column
26
+ ].filter((p) => p !== void 0 && p !== null && p !== "").join(":");
27
+ if (frame.fn && where) return `at ${frame.fn} (${where})`;
28
+ if (frame.fn) return `at ${frame.fn}`;
29
+ if (where) return `at ${where}`;
30
+ return null;
31
+ }
32
+ function formatRecord(record, { paint } = {}) {
33
+ const colour = paint || ((t) => t);
34
+ const level = String(record?.level ?? "").padEnd(LEVEL_WIDTH);
35
+ const src = String(record?.src ?? "").padEnd(SRC_WIDTH);
36
+ const [first, ...rest] = (record?.msg === void 0 || record?.msg === null ? "" : String(record.msg)).split("\n");
37
+ const lines = [`${formatTime(record?.ts)} ${colour(level)} ${src} ${first}`];
38
+ for (const line of rest) lines.push(`${INDENT}${line}`);
39
+ for (const frame of Array.isArray(record?.stack) ? record.stack : []) {
40
+ const rendered = formatStackFrame(frame);
41
+ if (rendered) lines.push(`${INDENT}${rendered}`);
42
+ }
43
+ return lines.join("\n");
44
+ }
45
+ function parseTail(value) {
46
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0) return { n: value };
47
+ if (typeof value !== "string" || !/^\d+$/.test(value.trim())) return { error: `Invalid --tail value ${JSON.stringify(value)}. Use a non-negative whole number, e.g. --tail 50.` };
48
+ return { n: parseInt(value.trim(), 10) };
49
+ }
50
+ function validateSources(sources) {
51
+ if (sources === void 0 || sources === null) return { sources: void 0 };
52
+ const list = Array.isArray(sources) ? sources : [sources];
53
+ if (list.includes("all")) return { sources: [...SOURCES] };
54
+ const unknown = list.filter((s) => !SOURCES.includes(s));
55
+ if (unknown.length > 0) return { error: `Unknown --source value(s): ${unknown.join(", ")}. Use one or more of: ${SOURCES.join(", ")}, or all.` };
56
+ return { sources: list };
57
+ }
58
+ function validateLevel(level) {
59
+ if (LEVELS.includes(level)) return { level };
60
+ return { error: `Invalid --level value ${JSON.stringify(level)}. Use one of: ${LEVELS.join(", ")}.` };
61
+ }
62
+ const ERRORS_PRINT_CAP = 20;
63
+ const LEVEL_COLOURS = {
64
+ debug: chalk.dim,
65
+ info: chalk.reset,
66
+ warn: chalk.yellow,
67
+ error: chalk.red,
68
+ fatal: chalk.bgRed
69
+ };
70
+ function logsCommand(program) {
71
+ program.command("logs").description("Query this workspace's merged NDJSON log timeline (bundler, client, device, build). Prints and exits; nothing matching is a successful, empty result. Use --follow to stream.").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) => {
72
+ const root = findProjectRoot(process.cwd());
73
+ if (!root) {
74
+ console.error(chalk.red("Not in a React Native project (no package.json found)."));
75
+ process.exit(1);
76
+ }
77
+ const dir = workspaceLogsDir(root);
78
+ let sources;
79
+ if (opts.source !== void 0) {
80
+ const checked = validateSources(opts.source);
81
+ if (checked.error) fail(checked.error);
82
+ sources = checked.sources;
83
+ }
84
+ let minLevel;
85
+ if (opts.level !== void 0) {
86
+ const checked = validateLevel(opts.level);
87
+ if (checked.error) fail(checked.error);
88
+ minLevel = checked.level;
89
+ }
90
+ if (opts.since !== void 0) {
91
+ const parsed = parseSince(opts.since);
92
+ if (parsed.error) fail(parsed.error);
93
+ }
94
+ let tail;
95
+ if (opts.tail !== void 0) {
96
+ const parsed = parseTail(opts.tail);
97
+ if (parsed.error) fail(parsed.error);
98
+ tail = parsed.n;
99
+ }
100
+ if (opts.grep !== void 0) {
101
+ const compiled = compileGrep(opts.grep);
102
+ if (compiled.error) fail(compiled.error);
103
+ }
104
+ const query = {
105
+ dir,
106
+ sources,
107
+ minLevel,
108
+ since: opts.since,
109
+ grep: opts.grep,
110
+ tail,
111
+ errorsOnly: Boolean(opts.errors),
112
+ errorContext: Boolean(opts.errors && !opts.json && !opts.follow)
113
+ };
114
+ const emit = (record) => {
115
+ if (opts.json) {
116
+ console.log(JSON.stringify(record));
117
+ return;
118
+ }
119
+ const rendered = {
120
+ ...record,
121
+ msg: launchErrorPreview([record], root, { full: true }).join("\n"),
122
+ stack: void 0
123
+ };
124
+ console.log(formatRecord(rendered, { paint: record?.level ? LEVEL_COLOURS[record.level] : void 0 }));
125
+ };
126
+ const offsets = opts.follow ? fileSizes(dir) : null;
127
+ captureWorkspaceCrashes(root, dir);
128
+ const rawRecords = queryLogs(query);
129
+ const supervisorPort = readWorkspaceState(root)?.supervisor?.port;
130
+ const records = opts.json ? rawRecords : await errorDiagnostics(rawRecords, {
131
+ root,
132
+ logsDir: dir,
133
+ port: typeof supervisorPort === "number" ? supervisorPort : null,
134
+ allowRequest: true
135
+ });
136
+ const errorCount = records.filter((record) => record.errorContext !== true).length;
137
+ let seenErrors = 0;
138
+ const capped = opts.errors && !opts.json && tail === void 0 && errorCount > 20 ? records.filter((record) => {
139
+ if (record.errorContext === true) return seenErrors <= 20;
140
+ seenErrors += 1;
141
+ return seenErrors <= 20;
142
+ }) : records;
143
+ for (const record of capped) emit(record);
144
+ const hidden = errorCount - capped.filter((record) => record.errorContext !== true).length;
145
+ if (hidden > 0) console.log(chalk.dim(`Showing 20 of ${errorCount} matching error records; ${hidden} not shown. Add --tail ${rawRecords.length} to include all captured records before grouping, or --json for raw records. Stacks are shown in full.`));
146
+ if (!opts.follow) {
147
+ if (records.length === 0 && !opts.json) console.error(chalk.dim(`No matching log records in ${dir}`));
148
+ return;
149
+ }
150
+ const criteria = buildCriteria({
151
+ sources,
152
+ minLevel,
153
+ since: opts.since,
154
+ grep: opts.grep,
155
+ errorsOnly: Boolean(opts.errors)
156
+ });
157
+ const stop = followLogs({
158
+ dir,
159
+ offsets,
160
+ criteria,
161
+ onRecord: emit
162
+ });
163
+ const finish = () => {
164
+ stop();
165
+ process.exit(0);
166
+ };
167
+ process.on("SIGINT", finish);
168
+ process.on("SIGTERM", finish);
169
+ });
170
+ }
171
+ function fail(message) {
172
+ console.error(chalk.red(message));
173
+ process.exit(1);
174
+ }
175
+ //#endregion
176
+ export { ERRORS_PRINT_CAP, logsCommand as default, formatRecord, formatStackFrame, formatTime, parseTail, validateLevel, validateSources };