stim 1.18.0 → 1.20.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 (178) hide show
  1. package/README.md +21 -4
  2. package/dist/{activity-BxnkDZDi.mjs → activity-BrGh9EAI.mjs} +3 -3
  3. package/dist/{agent-device-usage-output-CZ66YA4T.mjs → agent-device-usage-output-DVyv_hWj.mjs} +7 -7
  4. package/dist/android-0vv-OruW.mjs +6 -0
  5. package/dist/android-Brx6EKvJ.mjs +1258 -0
  6. package/dist/{android-DC3ZLuw0.mjs → android-BsYNR81W.mjs} +103 -45
  7. package/dist/{android-CBwCNicO.mjs → android-CK3fpEyZ.mjs} +2 -2
  8. package/dist/{android-cas-BBFyr9CA.mjs → android-cas-DCAuXryb.mjs} +9 -5
  9. package/dist/android-cas-compiler.mjs +1 -1
  10. package/dist/api-run.mjs +159 -8
  11. package/dist/{api-D0VtFhqk.mjs → api-zuX3ynYN.mjs} +9 -7
  12. package/dist/api.d.mts +104 -4
  13. package/dist/api.mjs +1 -1
  14. package/dist/{app-install-D-PUENug.mjs → app-install-0LgaQRT7.mjs} +39 -18
  15. package/dist/artifact-CnC0mfov.mjs +807 -0
  16. package/dist/artifact-DClXJNWx.mjs +772 -0
  17. package/dist/artifact-lifecycle-DzPpBYvK.mjs +30 -0
  18. package/dist/{web-DTU0uiWj.mjs → browser-web-CzBmhmgs.mjs} +136 -174
  19. package/dist/{budget-DIhL2BKx.mjs → budget-BONq9vXY.mjs} +3096 -3244
  20. package/dist/build-BixRI6sB.mjs +462 -0
  21. package/dist/build-plan-CP0tckNU.mjs +553 -0
  22. package/dist/{cache-manifest-BI7ozfmH.mjs → cache-manifest-qA3dT2x2.mjs} +1 -1
  23. package/dist/cache-manifest.mjs +1 -1
  24. package/dist/ccache-CjHQ5F8g.mjs +142 -0
  25. package/dist/{chrome-DJnmnS5q.mjs → chrome-Dp2lMLBe.mjs} +1 -1
  26. package/dist/{cli-DNJMwADx.mjs → cli-B2R8wH1F.mjs} +20 -20
  27. package/dist/cli.mjs +1 -1
  28. package/dist/{client-DzhapoMB.mjs → client-BLy1Pw2V.mjs} +172 -165
  29. package/dist/collector-run.d.mts +10 -0
  30. package/dist/collector-run.mjs +7 -7
  31. package/dist/{command-output-CBtzyIgF.mjs → command-output-BjPN99ZB.mjs} +2 -2
  32. package/dist/{config-CgYMahz7.mjs → config-D0LhTY3S.mjs} +13 -13
  33. package/dist/{created-devices-DKbeWsiK.mjs → created-devices-HU_DAb_Q.mjs} +7 -6
  34. package/dist/{dependency-state-xKoOypBm.mjs → dependency-state-B2m8eqsS.mjs} +2 -2
  35. package/dist/{detached-entry-BLfcgc4O.mjs → detached-entry-BXq4fIl-.mjs} +1 -1
  36. package/dist/{dev-client-CLnIH02x.mjs → dev-client-ClATaJoH.mjs} +2 -2
  37. package/dist/{device-D7oCHGWi.mjs → device-B7lsC0K1.mjs} +8 -8
  38. package/dist/device-DVCwAFlX.mjs +727 -0
  39. package/dist/{device-capacity-EUOOYr8T.mjs → device-capacity-Ca_ke1LE.mjs} +25 -15
  40. package/dist/device-host-worker.mjs +38 -18
  41. package/dist/{device-ios-6MewKKnc.mjs → device-ios-CRUqPxKw.mjs} +12 -11
  42. package/dist/{device-lease-Co-EwifR.mjs → device-lease-DHOBXGgS.mjs} +135 -22
  43. package/dist/{device-lease-run-w1xhCqIW.mjs → device-lease-run-CvDeHTkG.mjs} +4 -4
  44. package/dist/{device-pool-F5WvRPtD.mjs → device-pool-L2-5GxMy.mjs} +3 -3
  45. package/dist/{device-remote-tHbd1fKy.mjs → device-remote-Bmg42063.mjs} +17 -69
  46. package/dist/doctor-C2MSuMMg.mjs +515 -0
  47. package/dist/{doctor-B9ckYUoU.mjs → doctor-DPAqhVCK.mjs} +334 -721
  48. package/dist/doctor-watchman-CB4NnVZm.mjs +260 -0
  49. package/dist/{simslim-EHBQ2R4G.mjs → eas-build-Dw_ec2Km.mjs} +6 -152
  50. package/dist/{stim-installations-aR_WsNOA.mjs → eas-session-ledger-DZlC-Fq3.mjs} +57 -4
  51. package/dist/{error-diagnostics-BRXOqDzG.mjs → error-diagnostics-Du7I_GuY.mjs} +17 -12
  52. package/dist/errors-DzkMYh8Q.mjs +205 -0
  53. package/dist/{exec-DWhCZDGm.mjs → exec-B81Qodjn.mjs} +69 -15
  54. package/dist/{gc-Dh6mXOKC.mjs → gc-PWp34zGb.mjs} +23 -22
  55. package/dist/{gradle-C6GKnunQ.mjs → gradle-C2OEKCCI.mjs} +50 -125
  56. package/dist/{guide-tm6igAaa.mjs → guide-Dai9lphv.mjs} +862 -538
  57. package/dist/{hosted-android-Bv285t_t.mjs → hosted-android-DQ-Agsd8.mjs} +5 -5
  58. package/dist/{hosted-client-C-rMtppF.mjs → hosted-client-Io01s5By.mjs} +9 -3
  59. package/dist/{hosted-ios-D5mNmtj4.mjs → hosted-ios-DIxFnYp-.mjs} +3 -3
  60. package/dist/{hosted-logs-DN8Iz4mJ.mjs → hosted-logs-DP-ABU_M.mjs} +8 -8
  61. package/dist/{hosted-macos-B-wEjlwG.mjs → hosted-macos-Aziw9T_9.mjs} +7 -6
  62. package/dist/{hosted-native-DeeOG8xZ.mjs → hosted-native-BdAQi21M.mjs} +23 -16
  63. package/dist/{idle-shutdown-jjtxAn5C.mjs → idle-shutdown-DdMqoSUB.mjs} +7 -7
  64. package/dist/ios-BZ9dg2Fr.mjs +9 -0
  65. package/dist/{ios-B3SbOWdw.mjs → ios-CXLIUxXJ.mjs} +180 -24
  66. package/dist/ios-CZuv6gRv.mjs +1112 -0
  67. package/dist/{ios-Bvbf0SGY.mjs → ios-VGvNdsv2.mjs} +1 -1
  68. package/dist/{ios-device-Bn7jWDB1.mjs → ios-device-B1B14xND.mjs} +1 -1
  69. package/dist/{ios-device-vOaByMhw.mjs → ios-device-BreZOpM0.mjs} +3 -3
  70. package/dist/ios-project-B9drt_aY.mjs +197 -0
  71. package/dist/{ios-state-B2pES6Vs.mjs → ios-state-CjrL2qv-.mjs} +1 -1
  72. package/dist/js-swap-C8icNYIY.mjs +432 -0
  73. package/dist/launch-DxH0THLP.mjs +745 -0
  74. package/dist/launch-YrbqaIFj.mjs +1530 -0
  75. package/dist/{launch-verify-hskNcWkZ.mjs → launch-verify-BzRdbuO1.mjs} +26 -7
  76. package/dist/{logs-1Ylc1Ri6.mjs → logs-DHaRNfg7.mjs} +11 -11
  77. package/dist/macos-C0iljhsN.mjs +2 -0
  78. package/dist/{macos-DF6C7wUU.mjs → macos-V_3Ui7g4.mjs} +125 -155
  79. package/dist/macos-run.mjs +1 -1
  80. package/dist/maintenance-run.mjs +13 -10
  81. package/dist/{metro-Dh8T4Ytd.mjs → metro-D_j9sN-B.mjs} +209 -43
  82. package/dist/{metro-gateway-CEUw9RMH.mjs → metro-gateway-BBJldOdi.mjs} +2 -2
  83. package/dist/miss-reason-BPAzZS0r.mjs +238 -0
  84. package/dist/{named-ports-BFIz1Shz.mjs → named-ports-C2lvoPsb.mjs} +12 -5
  85. package/dist/native-android-lHzaew7r.mjs +226 -0
  86. package/dist/native-gradle-inputs-BQnIdAlL.mjs +356 -0
  87. package/dist/{native-runtime-D_603JiC.mjs → native-runtime-BRmWL7ru.mjs} +9 -8
  88. package/dist/native-xcode-inputs-eutS19jU.mjs +307 -0
  89. package/dist/native-xcode-ios-DP3cHLwu.mjs +434 -0
  90. package/dist/{ndjson-LGZAMsqa.mjs → ndjson-GOnbyvso.mjs} +6 -6
  91. package/dist/offload-worker.d.mts +12 -6
  92. package/dist/offload-worker.mjs +409 -73
  93. package/dist/{ownership-D6Se7gg_.mjs → ownership-DXzMMSx6.mjs} +2 -2
  94. package/dist/{placement-log-BcPhRwR4.mjs → placement-log-reFbjqEr.mjs} +7 -3
  95. package/dist/plan-BDk2AeY_.mjs +260 -0
  96. package/dist/plan-placement-D_YYlE6N.mjs +73 -0
  97. package/dist/{ports-BLTtfBq-.mjs → ports-jzvAJXXL.mjs} +3 -3
  98. package/dist/{prebuild-juCyreaR.mjs → prebuild-Bw44QxVh.mjs} +4 -4
  99. package/dist/{preview-D5V07Nrl.mjs → preview-BaBJ72Al.mjs} +51 -34
  100. package/dist/project-B8_duAzW.mjs +1067 -0
  101. package/dist/project-plan-BPwbLwQu.mjs +10 -0
  102. package/dist/{pull-request-u4vp7zmK.mjs → pull-request-vOhLNmIb.mjs} +1 -1
  103. package/dist/pull-requests.mjs +1 -1
  104. package/dist/react-native-android-chm1DMlA.mjs +921 -0
  105. package/dist/react-native-build-oAw6gQeW.mjs +239 -0
  106. package/dist/react-native-doctor-DYXoh-6I.mjs +78 -0
  107. package/dist/react-native-ios-SCTK_CT5.mjs +595 -0
  108. package/dist/{recordings-BAWltsVt.mjs → recordings-Bk-VivUV.mjs} +1 -1
  109. package/dist/{reload-DvyWzVjJ.mjs → reload-D34GkY78.mjs} +17 -14
  110. package/dist/remote-BU9UyhCJ.mjs +192 -0
  111. package/dist/{remote-cache-BGg7aGIZ.mjs → remote-cache-Bsts6f6Y.mjs} +5 -5
  112. package/dist/result-BDoRk1k3.mjs +186 -0
  113. package/dist/{run-vhtDgpHO.mjs → run-qvff78hv.mjs} +3 -3
  114. package/dist/{server-bare-B9E7S1aR.mjs → server-bare-dWjAarki.mjs} +6 -6
  115. package/dist/server-command-7XIKlWtO.mjs +69 -0
  116. package/dist/{server-expo-DoBHhuzK.mjs → server-expo-CsYu2aKL.mjs} +107 -82
  117. package/dist/server-expo-DV43vep4.mjs +2 -0
  118. package/dist/settings-Bbztm4oV.mjs +1503 -0
  119. package/dist/{settings-Byp2AQ_E.mjs → settings-CT3E71-i.mjs} +6 -7
  120. package/dist/{state-lKh8shBp.d.mts → settings-vk3mJGSm.d.mts} +4 -2
  121. package/dist/settings.schema.json +156 -6
  122. package/dist/simslim-ChMfe8rg.mjs +149 -0
  123. package/dist/{slot-launch-C1nqh4mu.mjs → slot-launch-B9hn96wd.mjs} +6 -6
  124. package/dist/{stage-g_WqWeaE.mjs → stage-BNKKdDNc.mjs} +6 -3
  125. package/dist/start-CbQCSnXQ.mjs +2 -0
  126. package/dist/{start-D-IeOyoK.mjs → start-CdvcTaXr.mjs} +113 -77
  127. package/dist/{state-xnqpx3AD.mjs → state-Boo0IMJq.mjs} +4 -2
  128. package/dist/{state-BG-aAk4k.mjs → state-DI84A-uu.mjs} +29 -10
  129. package/dist/{stats-DY6ekz8F.mjs → stats-BIIDrjA8.mjs} +6 -6
  130. package/dist/{status-BEeL6IKz.mjs → status-BDGHwkCs.mjs} +3 -1
  131. package/dist/{status-DCjZeE-K.mjs → status-DARMkc9q.mjs} +80 -294
  132. package/dist/status-watch-DvmI3SmN.mjs +296 -0
  133. package/dist/{stim-desktop-Behws_fi.mjs → stim-desktop-8GQX2OV8.mjs} +1 -1
  134. package/dist/{stop-DIJtD2GL.mjs → stop-CCZS-C9m.mjs} +2 -2
  135. package/dist/stop-WQPQHLuy.mjs +72 -0
  136. package/dist/{stop-BTjs3xbc.mjs → stop-iNemHsJC.mjs} +81 -33
  137. package/dist/supervisor-run.d.mts +7 -2
  138. package/dist/supervisor-run.mjs +32 -20
  139. package/dist/{support-Bwvbrq5-.mjs → support-CnE_SIjt.mjs} +17 -7
  140. package/dist/{support-BaayeYCf.mjs → support-DCco5fut.mjs} +61 -8
  141. package/dist/swiftpm-macos-afeNUExW.mjs +119 -0
  142. package/dist/{native-run-B06FkmZx.mjs → tailnet-CoJUb2tB.mjs} +113 -6
  143. package/dist/toolchain-BIupIro1.mjs +2 -0
  144. package/dist/{toolchain-Bnyd_K3A.mjs → toolchain-DqyDXs30.mjs} +225 -315
  145. package/dist/{trigger-CJMhwXe8.mjs → trigger-C0MWBi8O.mjs} +3 -3
  146. package/dist/trigger-SHLOWJ4O.mjs +2 -0
  147. package/dist/{warm-progress-CjB-MP6H.mjs → warm-progress-C3ASD6z5.mjs} +134 -37
  148. package/dist/{status-Dcr0Qexd.mjs → watchman-CQl51YZu.mjs} +75 -7
  149. package/dist/web-B3eldHFH.mjs +123 -0
  150. package/dist/web-BLXpOe6b.mjs +2 -0
  151. package/dist/web-run.mjs +10 -10
  152. package/dist/{workspace-state-C59dqG-Z.mjs → workspace-state-CSIFuuDa.mjs} +33 -33
  153. package/dist/{workspaces-xXItD8Dj.mjs → workspaces-D2xSHBDY.mjs} +1297 -1211
  154. package/dist/{worktree-BSYlofkq.mjs → worktree-BxDqaTev.mjs} +102 -63
  155. package/dist/worktree-q7_OZNY0.mjs +2 -0
  156. package/package.json +8 -6
  157. package/shim/bundle-response.cjs +2 -1
  158. package/shim/bundle-response.d.cts +1 -1
  159. package/shim/expo-metro-config.cjs +1 -1
  160. package/shim/native-android.gradle +101 -0
  161. package/dist/android-BKA16DTf.mjs +0 -4
  162. package/dist/android-BvwuMUJ9.mjs +0 -3572
  163. package/dist/guide-status-Bhf9SJXc.mjs +0 -125
  164. package/dist/ios-CPdkoKaa.mjs +0 -3953
  165. package/dist/ios-DNpdygiv.mjs +0 -7
  166. package/dist/macos-Ck7OBMNY.mjs +0 -2
  167. package/dist/metro-store-BAHFcJm6.mjs +0 -106
  168. package/dist/plan-placement-CtxkCO54.mjs +0 -1859
  169. package/dist/project-C3ChRLZy.mjs +0 -212
  170. package/dist/projects-DD7dBTCj.mjs +0 -92
  171. package/dist/server-expo-Db9HBUoa.mjs +0 -2
  172. package/dist/settings-CQyMElEP.mjs +0 -701
  173. package/dist/start-NTc1Ifqs.mjs +0 -2
  174. package/dist/stop-BGhKMOuH.mjs +0 -48
  175. package/dist/trigger-DWAq9hkw.mjs +0 -2
  176. package/dist/web-B6_bFDn7.mjs +0 -2
  177. package/dist/worktree-BJfnU6r5.mjs +0 -2
  178. package/dist/worktree-CsKYexJM.mjs +0 -753
@@ -1,12 +1,198 @@
1
1
  import { t as process_identity_exports } from "./process-identity-Biv3arCO.mjs";
2
- import { t as getExecutor } from "./exec-DWhCZDGm.mjs";
3
- import { M as isOnMountedVolume, N as listMountedVolumes, _ as loadConfig, n as allMetroPorts, r as claimMetroPort, x as releaseMetroPort } from "./config-CgYMahz7.mjs";
4
- import { r as readWorkspaceState } from "./workspace-state-C59dqG-Z.mjs";
2
+ import { t as getExecutor } from "./exec-B81Qodjn.mjs";
3
+ import { M as isOnMountedVolume, N as listMountedVolumes, _ as loadConfig, n as allMetroPorts, r as claimMetroPort, x as releaseMetroPort } from "./config-D0LhTY3S.mjs";
4
+ import { r as readWorkspaceState } from "./workspace-state-CSIFuuDa.mjs";
5
5
  import { sep } from "path";
6
6
  import { existsSync, readlinkSync, realpathSync } from "fs";
7
+ import { readFile } from "node:fs/promises";
7
8
  import { request } from "http";
8
- import { connect } from "net";
9
+ import { connect } from "node:net";
10
+ //#region src/listening-ports.ts
11
+ /**
12
+ * Returns occupied TCP ports across local interfaces. Rejects if listener inspection fails.
13
+ */
14
+ async function readListeningPorts(platform = process.platform) {
15
+ switch (platform) {
16
+ case "darwin": return parseDarwinPorts(await readNetstat("/usr/sbin/netstat", [
17
+ "-anL",
18
+ "-p",
19
+ "tcp"
20
+ ]));
21
+ case "linux": return parseLinuxPorts(await Promise.all([readFile("/proc/net/tcp", "utf8"), readFile("/proc/net/tcp6", "utf8").catch((error) => {
22
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
23
+ throw error;
24
+ })]));
25
+ case "win32": return parseWindowsPorts(await readNetstat("netstat", ["-an"]));
26
+ default: throw new Error(`TCP listener inspection is not supported on ${platform}.`);
27
+ }
28
+ }
29
+ const DARWIN_TITLE = "Current listen queue sizes (qlen/incqlen/maxqlen)";
30
+ const DARWIN_COLUMNS = /^Listen\s+Local Address$/;
31
+ const DARWIN_TABLE_CHANGE = /^Some tcp sockets may have been (?:created|deleted|created or deleted)\.?$/;
32
+ const DARWIN_LISTENER = /^\d+\/\d+\/\d+\s+\S+\.(?<port>\d+)$/;
33
+ function parseDarwinPorts(output) {
34
+ const ports = /* @__PURE__ */ new Set();
35
+ let title = false;
36
+ let columns = false;
37
+ for (const raw of output.split("\n")) {
38
+ const line = raw.trim();
39
+ if (line === DARWIN_TITLE) title = true;
40
+ else if (DARWIN_COLUMNS.test(line)) columns = true;
41
+ if (!line || line === DARWIN_TITLE || DARWIN_COLUMNS.test(line) || DARWIN_TABLE_CHANGE.test(line)) continue;
42
+ const listener = DARWIN_LISTENER.exec(line)?.groups;
43
+ if (!listener) throw new Error("Cannot parse the TCP listen table from netstat.");
44
+ addPort(ports, Number(listener.port));
45
+ }
46
+ if (!title || !columns) throw new Error("netstat printed no TCP listen table.");
47
+ return ports;
48
+ }
49
+ const LINUX_COLUMNS = /^\s*sl\s+local_address\s+rem(?:ote)?_address\s+st\s/;
50
+ const LINUX_TCP_STATE = /^[0-9A-F]{2}$/i;
51
+ const LINUX_LOCAL_ADDRESS = /^(?:[0-9A-F]{8}|[0-9A-F]{32}):(?<port>[0-9A-F]{4})$/i;
52
+ const LINUX_LISTEN_STATE = "0A";
53
+ function parseLinuxPorts(tables) {
54
+ const ports = /* @__PURE__ */ new Set();
55
+ for (const table of tables) {
56
+ if (table === null) continue;
57
+ const [header, ...rows] = table.trim().split("\n");
58
+ if (!header || !LINUX_COLUMNS.test(header)) throw new Error("Cannot parse the TCP socket table from /proc/net.");
59
+ for (const raw of rows) {
60
+ const line = raw.trim();
61
+ if (!line) continue;
62
+ const [, localAddress, , state] = line.split(/\s+/);
63
+ if (!state || !LINUX_TCP_STATE.test(state)) throw new Error("Invalid TCP state in /proc/net.");
64
+ if (state.toUpperCase() !== LINUX_LISTEN_STATE) continue;
65
+ const listener = localAddress && LINUX_LOCAL_ADDRESS.exec(localAddress)?.groups;
66
+ if (!listener) throw new Error("Invalid TCP listener in /proc/net.");
67
+ addPort(ports, Number(`0x${listener.port}`));
68
+ }
69
+ }
70
+ return ports;
71
+ }
72
+ const WINDOWS_COLUMNS = /^Proto\s/;
73
+ const WINDOWS_TCP_ROW = /^TCP\s+\S*:(?<localPort>\d+)\s+\S*:(?<remotePort>\d+)\s+\S/;
74
+ function parseWindowsPorts(output) {
75
+ const ports = /* @__PURE__ */ new Set();
76
+ let table = false;
77
+ for (const raw of output.split("\n")) {
78
+ const line = raw.trim();
79
+ if (WINDOWS_COLUMNS.test(line)) table = true;
80
+ const [protocol] = line.split(/\s+/, 1);
81
+ if (protocol !== "TCP") continue;
82
+ table = true;
83
+ const connection = WINDOWS_TCP_ROW.exec(line)?.groups;
84
+ if (!connection) throw new Error("Cannot parse the TCP connection table from netstat.");
85
+ if (Number(connection.remotePort) === 0) addPort(ports, Number(connection.localPort));
86
+ }
87
+ if (!table) throw new Error("netstat printed no TCP connection table.");
88
+ return ports;
89
+ }
90
+ function readNetstat(file, args) {
91
+ return getExecutor().runFileAsync(file, args, {
92
+ timeoutMs: 5e3,
93
+ rejectStderr: true,
94
+ env: { LC_ALL: "C" }
95
+ });
96
+ }
97
+ function addPort(ports, port) {
98
+ if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error("Invalid TCP listener port.");
99
+ ports.add(port);
100
+ }
101
+ function addressPort(address) {
102
+ const colon = String(address ?? "").lastIndexOf(":");
103
+ return colon < 0 ? NaN : Number(String(address).slice(colon + 1));
104
+ }
105
+ function parseLsofListeners(out) {
106
+ const byPort = /* @__PURE__ */ new Map();
107
+ let pid = null;
108
+ for (const line of String(out ?? "").split("\n")) if (line.startsWith("p")) pid = parseInt(line.slice(1), 10);
109
+ else if (line.startsWith("n") && pid !== null && Number.isFinite(pid)) {
110
+ const port = addressPort(line.slice(1));
111
+ const pids = byPort.get(port) ?? [];
112
+ if (!pids.includes(pid)) pids.push(pid);
113
+ byPort.set(port, pids);
114
+ }
115
+ return byPort;
116
+ }
117
+ const PORT_INSPECTION_REMEDY = "Allow Stim to run netstat or lsof, or to connect to 127.0.0.1 and ::1, then retry. A metro.port pin skips the scan for Metro.";
118
+ var PortInspectionError = class extends Error {
119
+ code = "STIM_PORT_INSPECTION_FAILED";
120
+ reason;
121
+ constructor(reason) {
122
+ super(`${reason} ${PORT_INSPECTION_REMEDY}`);
123
+ this.reason = reason;
124
+ }
125
+ };
126
+ /**
127
+ * Returns the TCP ports lsof lists as listening, or null when lsof cannot list them.
128
+ */
129
+ async function readLsofListeningPorts() {
130
+ try {
131
+ const out = await getExecutor().runFileAsync("lsof", [
132
+ "-nP",
133
+ "-iTCP",
134
+ "-sTCP:LISTEN",
135
+ "-Fpn"
136
+ ], { timeoutMs: 5e3 });
137
+ return new Set([...parseLsofListeners(out).keys()].filter(Number.isInteger));
138
+ } catch (error) {
139
+ const result = error;
140
+ if (result.status === 1 && !String(result.stdout ?? "").trim() && !String(result.stderr ?? "").trim()) return /* @__PURE__ */ new Set();
141
+ return null;
142
+ }
143
+ }
144
+ const IPV6_UNAVAILABLE = /* @__PURE__ */ new Set([
145
+ "EADDRNOTAVAIL",
146
+ "EAFNOSUPPORT",
147
+ "ENETUNREACH"
148
+ ]);
149
+ /**
150
+ * Connects to a loopback port: `taken` when something accepts or the connect hangs, `free` when
151
+ * it is refused or IPv6 is unavailable, `unknown` for any other connect error.
152
+ */
153
+ function probeLoopback(port, host, timeoutMs = 400) {
154
+ return new Promise((resolve) => {
155
+ const socket = connect({
156
+ port,
157
+ host
158
+ });
159
+ const done = (answer) => {
160
+ socket.removeAllListeners();
161
+ socket.destroy();
162
+ resolve(answer);
163
+ };
164
+ socket.setTimeout(timeoutMs);
165
+ socket.once("connect", () => done("taken"));
166
+ socket.once("timeout", () => done("taken"));
167
+ socket.once("error", (error) => {
168
+ const refused = error.code === "ECONNREFUSED" || host === "::1" && IPV6_UNAVAILABLE.has(error.code ?? "");
169
+ done(refused ? "free" : "unknown");
170
+ });
171
+ });
172
+ }
173
+ //#endregion
9
174
  //#region src/ports.ts
175
+ /**
176
+ * Creates one lazy listener snapshot for an allocation attempt; discard the probe before retrying.
177
+ * When the native TCP table cannot be read, each port is checked with an lsof listener scan and
178
+ * loopback connects instead. Rejects with `PortInspectionError` only when none of them can answer.
179
+ */
180
+ function createPortProbe() {
181
+ let snapshot;
182
+ let lsof;
183
+ return async (port) => {
184
+ const listening = await (snapshot ??= readListeningPorts().catch((error) => error instanceof Error ? error : new Error(String(error))));
185
+ if (!(listening instanceof Error)) return !listening.has(port);
186
+ const [listed, ipv4, ipv6] = await Promise.all([
187
+ lsof ??= readLsofListeningPorts(),
188
+ probeLoopback(port, "127.0.0.1"),
189
+ probeLoopback(port, "::1")
190
+ ]);
191
+ if (listed?.has(port) || ipv4 === "taken" || ipv6 === "taken") return false;
192
+ if (listed || ipv4 === "free" && ipv6 === "free") return true;
193
+ throw new PortInspectionError(`Cannot tell whether TCP port ${port} is free: the TCP listener table failed (${listening.message.replace(/\s+/g, " ").trim()}), lsof could not list listeners, and a loopback connect failed.`);
194
+ };
195
+ }
10
196
  function isMetroRunning(port) {
11
197
  return new Promise((resolve) => {
12
198
  const req = request({
@@ -29,25 +215,8 @@ function isMetroRunning(port) {
29
215
  req.end();
30
216
  });
31
217
  }
32
- function isPortFree(port, { timeoutMs = 400 } = {}) {
33
- return new Promise((resolve) => {
34
- const sock = connect({
35
- port,
36
- host: "127.0.0.1"
37
- });
38
- const done = (free) => {
39
- sock.removeAllListeners();
40
- sock.destroy();
41
- resolve(free);
42
- };
43
- sock.setTimeout(timeoutMs);
44
- sock.once("connect", () => done(false));
45
- sock.once("error", () => done(true));
46
- sock.once("timeout", () => done(false));
47
- });
48
- }
49
218
  const FIRST_PORT = 8082;
50
- async function computeNextPort(isFree = isPortFree) {
219
+ async function computeNextPort(isFree = createPortProbe()) {
51
220
  const taken = new Set(allMetroPorts());
52
221
  for (let port = FIRST_PORT; port < 8282; port++) {
53
222
  if (taken.has(port)) continue;
@@ -73,7 +242,7 @@ async function findReclaimablePort(excludeProjectPath, probe = isMetroRunning, {
73
242
  for (const c of candidates) if (!await probe(c.port)) return c;
74
243
  return null;
75
244
  }
76
- async function allocatePort(projectPath, probe = isMetroRunning, isFree = isPortFree) {
245
+ async function allocatePort(projectPath, probe = isMetroRunning, isFree = createPortProbe()) {
77
246
  const reclaim = await findReclaimablePort(projectPath, probe);
78
247
  if (reclaim && await isFree(reclaim.port)) {
79
248
  releaseMetroPort(reclaim.ownerPath, reclaim.port);
@@ -82,7 +251,7 @@ async function allocatePort(projectPath, probe = isMetroRunning, isFree = isPort
82
251
  return computeNextPort(isFree);
83
252
  }
84
253
  const RESERVE_ATTEMPTS = 5;
85
- async function reserveMetroPort(projectPath, probe = isMetroRunning, isFree = isPortFree, pinned = null) {
254
+ async function reserveMetroPort(projectPath, probe = isMetroRunning, isFree, pinned = null) {
86
255
  if (pinned !== null) {
87
256
  if (claimMetroPort(projectPath, pinned) !== null) return pinned;
88
257
  throw new Error(`metro.port ${pinned} is already reserved by another workspace's Metro or named port.`);
@@ -116,10 +285,6 @@ function parseLsofPids(out) {
116
285
  if (!out) return [];
117
286
  return String(out).split("\n").map((s) => parseInt(s.trim(), 10)).filter((n) => Number.isFinite(n));
118
287
  }
119
- function addressPort(address) {
120
- const colon = String(address ?? "").lastIndexOf(":");
121
- return colon < 0 ? NaN : Number(String(address).slice(colon + 1));
122
- }
123
288
  function parseNetstatPids(out, port) {
124
289
  const pids = [];
125
290
  for (const line of String(out ?? "").split("\n")) {
@@ -132,7 +297,12 @@ function parseNetstatPids(out, port) {
132
297
  return pids;
133
298
  }
134
299
  function listeningPids(port, platform = process.platform) {
135
- const pids = parseLsofPids(getExecutor().runQuiet(`lsof -nP -iTCP:${port} -sTCP:LISTEN -t`));
300
+ const pids = parseLsofPids(getExecutor().runFileQuiet("lsof", [
301
+ "-nP",
302
+ `-iTCP:${port}`,
303
+ "-sTCP:LISTEN",
304
+ "-t"
305
+ ]));
136
306
  if (pids.length > 0 || platform !== "win32") return pids;
137
307
  return parseNetstatPids(getExecutor().runQuiet("netstat -ano"), port);
138
308
  }
@@ -147,18 +317,6 @@ function parseLsofCwd(out) {
147
317
  function lsofOutput(args) {
148
318
  return getExecutor().runFileAsync("lsof", args).catch((error) => typeof error?.stdout === "string" ? error.stdout : null);
149
319
  }
150
- function parseLsofListeners(out) {
151
- const byPort = /* @__PURE__ */ new Map();
152
- let pid = null;
153
- for (const line of String(out ?? "").split("\n")) if (line.startsWith("p")) pid = parseInt(line.slice(1), 10);
154
- else if (line.startsWith("n") && pid !== null && Number.isFinite(pid)) {
155
- const port = addressPort(line.slice(1));
156
- const pids = byPort.get(port) ?? [];
157
- if (!pids.includes(pid)) pids.push(pid);
158
- byPort.set(port, pids);
159
- }
160
- return byPort;
161
- }
162
320
  /**
163
321
  * `listeningPids` for several ports with one lsof call. A port lsof could not be asked about is
164
322
  * missing from the map.
@@ -218,7 +376,14 @@ function processCwd(pid) {
218
376
  if (process.platform === "linux") try {
219
377
  return readlinkSync(`/proc/${pid}/cwd`);
220
378
  } catch {}
221
- return parseLsofCwd(getExecutor().runQuiet(`lsof -a -p ${pid} -d cwd -Fn`));
379
+ return parseLsofCwd(getExecutor().runFileQuiet("lsof", [
380
+ "-a",
381
+ "-p",
382
+ String(pid),
383
+ "-d",
384
+ "cwd",
385
+ "-Fn"
386
+ ]));
222
387
  }
223
388
  function canonicalPath(path) {
224
389
  try {
@@ -286,6 +451,7 @@ async function resolveProjectMetro(port, projectPath, { probe = isMetroRunning,
286
451
  * always forceful.
287
452
  */
288
453
  function signalProcessTree(pid, signal = "SIGTERM", { group = false, platform = process.platform } = {}) {
454
+ if (!Number.isSafeInteger(pid) || pid <= 1) throw new Error(`Refusing to signal pid ${pid}.`);
289
455
  if (platform !== "win32") return process.kill(group ? -pid : pid, signal);
290
456
  return getExecutor().runFileQuiet("taskkill", [
291
457
  "/PID",
@@ -306,4 +472,4 @@ function killMetroTree(leader, processToken) {
306
472
  }
307
473
  }
308
474
  //#endregion
309
- export { listeningPidsByPort as a, processCwds as c, isMetroRunning as d, isPortFree as f, listeningPids as i, resolveProjectMetro as l, isInsideProject as n, pidExists as o, reserveMetroPort as p, killMetroTree as r, processCwd as s, NOT_OURS_FOREIGN_CWD as t, signalProcessTree as u };
475
+ export { listeningPidsByPort as a, processCwd as c, signalProcessTree as d, createPortProbe as f, PortInspectionError as g, PORT_INSPECTION_REMEDY as h, listeningPids as i, processCwds as l, reserveMetroPort as m, isInsideProject as n, parseNetstatPids as o, isMetroRunning as p, killMetroTree as r, pidExists as s, NOT_OURS_FOREIGN_CWD as t, resolveProjectMetro as u };
@@ -1,6 +1,6 @@
1
1
  import { t as process_identity_exports } from "./process-identity-Biv3arCO.mjs";
2
- import { a as updateWorkspaceState, r as readWorkspaceState$1 } from "./workspace-state-C59dqG-Z.mjs";
3
- import { w as realIo } from "./client-DzhapoMB.mjs";
2
+ import { a as updateWorkspaceState, r as readWorkspaceState$1 } from "./workspace-state-CSIFuuDa.mjs";
3
+ import { l as realIo } from "./tailnet-CoJUb2tB.mjs";
4
4
  import { HOSTED_METRO_REQUESTS_KEY, findPeer, hostedMetroGateways, hostedMetroRequests, isJsonObject, parseMachine } from "@stim-cli/core/state";
5
5
  import { randomBytes, randomUUID } from "node:crypto";
6
6
  import { isIP } from "node:net";
@@ -0,0 +1,238 @@
1
+ import { ht as gitCommonDirOnDisk } from "./settings-Bbztm4oV.mjs";
2
+ import { C as diffFingerprintSources, I as storedSources } from "./device-lease-DHOBXGgS.mjs";
3
+ import { m as sharedBuildCache } from "./workspace-state-CSIFuuDa.mjs";
4
+ import { existsSync, realpathSync } from "node:fs";
5
+ import { dirname, join, relative } from "node:path";
6
+ import { LAST_BUILD_KEYS, loadConfig, readWorkspaceState } from "@stim-cli/core/state";
7
+ import * as expoFingerprint from "@expo/fingerprint";
8
+ //#region src/cache/miss-reason.ts
9
+ const MISS_CHANGE_CAP = 20;
10
+ function recordedBuilds(state, platform) {
11
+ const builds = [];
12
+ for (const value of [state?.[LAST_BUILD_KEYS[platform]], state?.lastBuild]) {
13
+ const record = value;
14
+ if (record?.platform !== platform) continue;
15
+ const startedAt = typeof record.startedAt === "string" ? record.startedAt : "";
16
+ if (typeof record.fingerprint === "string" && typeof record.cacheKey === "string") builds.push({
17
+ fingerprint: record.fingerprint,
18
+ cacheKey: record.cacheKey,
19
+ startedAt
20
+ });
21
+ const carried = record.missReason?.baseline;
22
+ if (typeof carried?.fingerprint === "string" && typeof carried.cacheKey === "string") builds.push({
23
+ fingerprint: carried.fingerprint,
24
+ cacheKey: carried.cacheKey,
25
+ startedAt
26
+ });
27
+ }
28
+ return builds;
29
+ }
30
+ function projectIdentity(root) {
31
+ let real;
32
+ try {
33
+ real = realpathSync.native(root);
34
+ } catch {
35
+ return null;
36
+ }
37
+ for (let dir = real;; dir = dirname(dir)) {
38
+ if (existsSync(join(dir, ".git"))) {
39
+ const common = gitCommonDirOnDisk(dir);
40
+ return common ? `${common}\n${relative(dir, real)}` : null;
41
+ }
42
+ if (dirname(dir) === dir) return null;
43
+ }
44
+ }
45
+ function findMissBaseline(root, platform, { readState = readWorkspaceState, projectRoots = () => Object.keys(loadConfig()?.projects ?? {}), identity = projectIdentity, sourcesOf = (p, key) => storedSources(p, key, sharedBuildCache()) } = {}) {
46
+ for (const build of recordedBuilds(readState(root), platform)) {
47
+ const sources = sourcesOf(platform, build.cacheKey);
48
+ if (sources) return {
49
+ fingerprint: build.fingerprint,
50
+ cacheKey: build.cacheKey,
51
+ sources,
52
+ from: "workspace"
53
+ };
54
+ }
55
+ const project = identity(root);
56
+ if (!project) return null;
57
+ const others = [];
58
+ for (const other of projectRoots()) {
59
+ if (other === root || identity(other) !== project) continue;
60
+ others.push(...recordedBuilds(readState(other), platform));
61
+ }
62
+ for (const build of others.toSorted((a, b) => (Date.parse(b.startedAt) || 0) - (Date.parse(a.startedAt) || 0))) {
63
+ const sources = sourcesOf(platform, build.cacheKey);
64
+ if (sources) return {
65
+ fingerprint: build.fingerprint,
66
+ cacheKey: build.cacheKey,
67
+ sources,
68
+ from: "project"
69
+ };
70
+ }
71
+ return null;
72
+ }
73
+ const CATEGORY_RANK = {
74
+ "native-dependency": 0,
75
+ "app-config": 1,
76
+ "config-plugin": 2,
77
+ package: 3,
78
+ "native-dir": 4,
79
+ file: 5,
80
+ "app-asset": 6,
81
+ other: 7,
82
+ "package-scripts": 8,
83
+ autolinking: 9
84
+ };
85
+ function packageIn(path) {
86
+ return [...path.matchAll(/(?:^|\/)node_modules\/((?:@[^/]+\/)?[^/]+)/g)].at(-1)?.[1] ?? null;
87
+ }
88
+ function describeChange({ name, change, reasons }) {
89
+ const pkg = packageIn(name);
90
+ if (name === "expoConfig" || reasons.includes("expoConfig")) return {
91
+ category: "app-config",
92
+ label: "app config changed"
93
+ };
94
+ if (pkg && reasons.some((reason) => /^(expo|rncore)Autolinking(Ios|Android)?$/.test(reason))) return {
95
+ category: "native-dependency",
96
+ label: `native dependency ${change}: ${pkg}`
97
+ };
98
+ if (pkg && reasons.includes("expoConfigPlugins")) return {
99
+ category: "config-plugin",
100
+ label: `config plugin ${change}: ${pkg}`
101
+ };
102
+ if (/^(expoAutolinkingConfig|rncoreAutolinkingConfig)(:|$)/.test(name)) return {
103
+ category: "autolinking",
104
+ label: "autolinking config changed"
105
+ };
106
+ if (name.startsWith("package:")) return {
107
+ category: "package",
108
+ label: `${name.slice(8)} ${change}`
109
+ };
110
+ if (name === "packageJson:scripts") return {
111
+ category: "package-scripts",
112
+ label: "package.json scripts changed"
113
+ };
114
+ if (reasons.includes("expoConfigExternalFile")) return {
115
+ category: "app-asset",
116
+ label: `app asset ${change}: ${name}`
117
+ };
118
+ if (reasons.includes("bareNativeDir")) return {
119
+ category: "native-dir",
120
+ label: `${name}/ ${change}`
121
+ };
122
+ if (reasons.length || name.includes("/") || name.includes(".")) return {
123
+ category: "file",
124
+ label: `${name} ${change}`
125
+ };
126
+ return {
127
+ category: "other",
128
+ label: `${name} ${change}`
129
+ };
130
+ }
131
+ function rekeyPrefix(rekeyedBy) {
132
+ return rekeyedBy.length ? `${rekeyedBy.join(" and ")} changed native inputs; ` : "";
133
+ }
134
+ function missReasonFromChanges({ changes, baseline, rekeyedBy = [] }) {
135
+ const prefix = rekeyPrefix(rekeyedBy);
136
+ if (!baseline) return {
137
+ kind: "no-baseline",
138
+ summary: `${prefix}no earlier build of this project in the cache to compare with`,
139
+ changes: [],
140
+ changeCount: 0,
141
+ baseline: null,
142
+ rekeyedBy
143
+ };
144
+ if (!changes.length) return {
145
+ kind: "same-sources",
146
+ summary: `${prefix}native inputs match the last build; no cached app for this configuration or target`,
147
+ changes: [],
148
+ changeCount: 0,
149
+ baseline,
150
+ rekeyedBy
151
+ };
152
+ const described = changes.map((change, index) => ({
153
+ change,
154
+ index,
155
+ ...describeChange(change)
156
+ })).toSorted((a, b) => CATEGORY_RANK[a.category] - CATEGORY_RANK[b.category] || a.index - b.index);
157
+ const dependencyMoved = described.some((entry) => entry.category === "native-dependency");
158
+ const labels = [...new Set(described.filter((entry) => !(dependencyMoved && entry.category === "autolinking")).map((entry) => entry.label))];
159
+ const more = labels.length > 1 ? ` (+${labels.length - 1} more)` : "";
160
+ return {
161
+ kind: "changed",
162
+ summary: `${prefix}${labels[0]}${more}`,
163
+ changes: described.slice(0, MISS_CHANGE_CAP).map(({ change, category }) => ({
164
+ source: change.name,
165
+ change: change.change,
166
+ category
167
+ })),
168
+ changeCount: changes.length,
169
+ baseline,
170
+ rekeyedBy
171
+ };
172
+ }
173
+ function explainBuildMiss({ root, platform, current, rekeyedBy = [], baselineDeps, differ = expoFingerprint.diffFingerprints }) {
174
+ const baseline = findMissBaseline(root, platform, baselineDeps);
175
+ const changes = baseline ? diffFingerprintSources({
176
+ previous: baseline.sources,
177
+ previousHash: baseline.fingerprint,
178
+ current,
179
+ differ
180
+ }) : [];
181
+ return {
182
+ reason: missReasonFromChanges({
183
+ changes,
184
+ baseline: baseline ? {
185
+ fingerprint: baseline.fingerprint,
186
+ cacheKey: baseline.cacheKey,
187
+ from: baseline.from
188
+ } : null,
189
+ rekeyedBy
190
+ }),
191
+ changedNames: changes.map((change) => change.name),
192
+ previousHash: baseline?.fingerprint ?? null
193
+ };
194
+ }
195
+ function predictBuildMiss({ root, platform, current, prebuild }) {
196
+ const { reason } = explainBuildMiss({
197
+ root,
198
+ platform,
199
+ current
200
+ });
201
+ const baseline = reason.baseline && {
202
+ fingerprint: reason.baseline.fingerprint,
203
+ from: reason.baseline.from
204
+ };
205
+ if (!prebuild || !baseline) return {
206
+ ...reason,
207
+ baseline
208
+ };
209
+ const dir = platform === "ios" ? "ios/" : "android/";
210
+ return {
211
+ ...reason,
212
+ kind: "prebuild-pending",
213
+ summary: `${reason.summary} (before prebuild ${prebuild}s ${dir})`,
214
+ baseline
215
+ };
216
+ }
217
+ function skippedMissReason(summary) {
218
+ return {
219
+ kind: "cache-skipped",
220
+ summary,
221
+ changes: [],
222
+ changeCount: 0,
223
+ baseline: null,
224
+ rekeyedBy: []
225
+ };
226
+ }
227
+ function fingerprintErrorMissReason(message) {
228
+ return {
229
+ kind: "fingerprint-error",
230
+ summary: `fingerprinting failed: ${message}`,
231
+ changes: [],
232
+ changeCount: 0,
233
+ baseline: null,
234
+ rekeyedBy: []
235
+ };
236
+ }
237
+ //#endregion
238
+ export { skippedMissReason as i, fingerprintErrorMissReason as n, predictBuildMiss as r, explainBuildMiss as t };
@@ -1,7 +1,7 @@
1
1
  import { n as ownership_claim_exports, t as process_identity_exports } from "./process-identity-Biv3arCO.mjs";
2
- import { t as getExecutor } from "./exec-DWhCZDGm.mjs";
3
- import { C as saveConfig, D as withConfigLock, _ as loadConfig, c as ensureConfig, d as getConfigDir, p as getProject } from "./config-CgYMahz7.mjs";
4
- import { f as isPortFree, i as listeningPids, u as signalProcessTree } from "./metro-Dh8T4Ytd.mjs";
2
+ import { t as getExecutor } from "./exec-B81Qodjn.mjs";
3
+ import { C as saveConfig, D as withConfigLock, _ as loadConfig, c as ensureConfig, d as getConfigDir, p as getProject } from "./config-D0LhTY3S.mjs";
4
+ import { d as signalProcessTree, f as createPortProbe, o as parseNetstatPids } from "./metro-D_j9sN-B.mjs";
5
5
  import { n as declareSpawnsOn, o as stopDeclaringSpawnsOn } from "./spawn-claims-Kqiifsk2.mjs";
6
6
  import { realpathSync } from "node:fs";
7
7
  import { join } from "node:path";
@@ -69,7 +69,14 @@ function validatePortLabel(label) {
69
69
  if (!/^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/.test(label)) throw new Error("A port label must start with a letter and contain at most 64 letters, digits, underscores or hyphens.");
70
70
  }
71
71
  function portListeners(port, platform) {
72
- if (platform === "win32") return listeningPids(port, platform);
72
+ if (platform === "win32") {
73
+ const out = getExecutor().runFile("netstat", ["-ano"], {
74
+ timeoutMs: 5e3,
75
+ rejectStderr: true
76
+ });
77
+ if (!/^\s*(?:Proto|TCP)\s/m.test(out)) throw new Error("netstat printed no TCP connection table.");
78
+ return parseNetstatPids(out, port);
79
+ }
73
80
  let out;
74
81
  try {
75
82
  out = getExecutor().runFile("lsof", [
@@ -126,7 +133,7 @@ function releaseBrowserPort(projectPath, port) {
126
133
  saveConfig(cfg);
127
134
  }));
128
135
  }
129
- async function allocateNamedPort(projectPath, label, { isFree = async (port) => await isPortFree(port) && portListeners(port, process.platform).length === 0 }) {
136
+ async function allocateNamedPort(projectPath, label, { isFree = createPortProbe() }) {
130
137
  const root = realpathSync(projectPath);
131
138
  return withPortsLock(async () => {
132
139
  const existing = getProject(root)?.ports;