stim 1.20.2 → 1.22.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.
- package/README.md +141 -8
- package/dist/acquire-artifact-Co2k4f7O.mjs +171 -0
- package/dist/act-DsuqEkiM.mjs +2801 -0
- package/dist/activity-CMLmzp7o.mjs +1397 -0
- package/dist/{agent-device-usage-output-Dzgv6lgR.mjs → agent-device-usage-output-DUFaTMdd.mjs} +23 -67
- package/dist/{android-DCKZrNvV.mjs → android-ByRMUoiJ.mjs} +157 -93
- package/dist/{android-Mz_Inn3w.mjs → android-C7sVmyI9.mjs} +299 -116
- package/dist/{android-Dlzxshgp.mjs → android-DGkNqa_c.mjs} +5 -5
- package/dist/{android-HqP1_vB_.mjs → android-DR5lZ2Az.mjs} +2 -2
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/{android-cas-Bl-cLJBk.mjs → android-cas-ngferVq2.mjs} +7 -6
- package/dist/android-project-BCAAmMwz.mjs +12 -0
- package/dist/android-project-DVWyCbWU.d.mts +391 -0
- package/dist/api.d.mts +810 -64
- package/dist/api.mjs +782 -1
- package/dist/{artifact-HSwkIWEQ.mjs → artifact-8EY8SAPw.mjs} +240 -213
- package/dist/{artifact-D3czlUvT.mjs → artifact-DJPy518Y.mjs} +203 -164
- package/dist/artifact-platform-CXcmX_j3.mjs +27 -0
- package/dist/{browser-web-CA-F1GQo.mjs → browser-web-Bcv99PBM.mjs} +194 -64
- package/dist/budget-Cvwn54qV.mjs +9285 -0
- package/dist/{build-DYDbfkk5.mjs → build-CabC3G23.mjs} +153 -98
- package/dist/build-cache-Dv7RN200.mjs +602 -0
- package/dist/build-plan-DUC8Ka9y.mjs +248 -0
- package/dist/{cache-manifest-XH-fvDUj.mjs → cache-manifest-DSeQjt-Q.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{ccache-zdKiYQ3E.mjs → ccache-Btwc36-I.mjs} +10 -9
- package/dist/{cdp-Dq_ypAlJ.mjs → cdp-3fwICjzi.mjs} +18 -5
- package/dist/{chrome-BnzZMqsR.mjs → chrome-Bbw689-6.mjs} +7 -4
- package/dist/{cli-CLP3diBm.mjs → cli-CHZa9Lvk.mjs} +20 -20
- package/dist/cli.mjs +1 -1
- package/dist/client-OyRoEuSr.mjs +1577 -0
- package/dist/collector-run.d.mts +4 -40
- package/dist/collector-run.mjs +9 -8
- package/dist/{command-output-BTy8hZ0F.mjs → command-output-gwpm28fu.mjs} +1 -1
- package/dist/config-DlrsduwG.mjs +955 -0
- package/dist/{created-devices-Dzd5C2LF.mjs → created-devices-x9rN6bhs.mjs} +14 -4
- package/dist/{dependency-state-DG0p31-y.mjs → dependency-state-1I9KZT1Y.mjs} +20 -5
- package/dist/{detached-entry-B9C_zeQC.mjs → detached-entry-BwNEtIrP.mjs} +1 -1
- package/dist/{dev-client-DAWVIGBu.mjs → dev-client-BSDR5h3F.mjs} +2 -2
- package/dist/{device-BECOiufl.mjs → device-B-L3Fbxy.mjs} +91 -34
- package/dist/device-DklWZLqb.mjs +1068 -0
- package/dist/{device-capacity-CBUAcRyQ.mjs → device-capacity-DK5_EwRS.mjs} +42 -13
- package/dist/device-host-worker.mjs +76 -79
- package/dist/{device-ios-COSi6oS1.mjs → device-ios-DoB6KFCO.mjs} +77 -51
- package/dist/{device-lease-run-Cw2M4z51.mjs → device-lease-run-xJPQ_QQG.mjs} +14 -26
- package/dist/{device-pool-Bf-pOhSw.mjs → device-pool-BMkdsefq.mjs} +5 -5
- package/dist/{doctor-kfMBnUw6.mjs → doctor-Bo7BXLux.mjs} +271 -320
- package/dist/{doctor-CB8oZKCk.mjs → doctor-D5_EzwfA.mjs} +186 -91
- package/dist/doctor-fingerprint-DZLoYFVb.mjs +16 -0
- package/dist/doctor-fingerprint.mjs +19 -0
- package/dist/doctor-thjWAV0z.mjs +2 -0
- package/dist/{doctor-watchman-BRyV2adA.mjs → doctor-watchman-N2wDotwQ.mjs} +40 -21
- package/dist/{eas-build-JyfqyHfC.mjs → eas-build-CwknaQYj.mjs} +53 -20
- package/dist/{build-plan-B7SZsc2N.mjs → eas-fallback-CSgAulr5.mjs} +41 -214
- package/dist/{watchman-CPQFZLXY.mjs → eas-session-ledger-DlY_serQ.mjs} +230 -11
- package/dist/{error-diagnostics-BR677IDM.mjs → error-diagnostics-DBWO2rxE.mjs} +172 -82
- package/dist/exec-DEoAhcze.mjs +858 -0
- package/dist/exec-DnYc4HGH.d.mts +78 -0
- package/dist/{gc-qWOMdnER.mjs → gc-CXxN-4SE.mjs} +1091 -1029
- package/dist/gc-DnNDQyaP.mjs +3 -0
- package/dist/{guide-G9cAbVAc.mjs → guide-Br0LEfKy.mjs} +1165 -297
- package/dist/{hosted-android-C3MHGld1.mjs → hosted-android-B-9f-jkd.mjs} +10 -8
- package/dist/{hosted-client-ZEKpS9vw.mjs → hosted-client-DZAPrcro.mjs} +34 -19
- package/dist/{hosted-ios-l-o3iM9J.mjs → hosted-ios-CsCS21hn.mjs} +1 -1
- package/dist/{hosted-logs-EzQM7-3Z.mjs → hosted-logs-FIRx9X2f.mjs} +77 -43
- package/dist/{hosted-macos-BACVAVqE.mjs → hosted-macos-_awYSpfv.mjs} +35 -22
- package/dist/{hosted-native-A4s1QPXe.mjs → hosted-native-BdzE668-.mjs} +49 -27
- package/dist/idle-C4cZ757K.d.mts +24 -0
- package/dist/{idle-shutdown-DtxhPTKe.mjs → idle-shutdown-DDg3SdvY.mjs} +11 -10
- package/dist/ios-CDvlmUs7.d.mts +24 -0
- package/dist/{ios-BwVCF7oA.mjs → ios-Ds1clbIt.mjs} +8 -8
- package/dist/{ios-device-D7pi2Sca.mjs → ios-device--h_-8ENa.mjs} +51 -26
- package/dist/{ios-device-B5veSDDQ.mjs → ios-device-BXZ0c7Kb.mjs} +1 -1
- package/dist/{ios-RkW4pnQf.mjs → ios-hcBpnEGk.mjs} +200 -93
- package/dist/{ios-Ce7bc7br.mjs → ios-pm4nL6By.mjs} +160 -108
- package/dist/{ios-project-K9hY_Jab.mjs → ios-project-CZhKODF3.mjs} +13 -13
- package/dist/ios-signing-BtAffm0A.mjs +489 -0
- package/dist/{ios-state-CBLe1xYw.mjs → ios-state-Dtw5K2aI.mjs} +1 -1
- package/dist/{ios-nOLJGVdy.mjs → ios-xtlRHaeB.mjs} +1 -1
- package/dist/{js-swap-Bb3nsS07.mjs → js-swap-Bwhh4S7V.mjs} +30 -25
- package/dist/{launch-C3qJ6Agb.mjs → launch-BjxemNpK.mjs} +85 -543
- package/dist/{launch-F4sOqAhB.mjs → launch-CCzXlc8r.mjs} +91 -90
- package/dist/logs-BfwwadsZ.mjs +479 -0
- package/dist/logs-jTS_lHSo.mjs +248 -0
- package/dist/{macos-D7uDvO-z.mjs → macos-DVlSSyUg.mjs} +1 -1
- package/dist/macos-NfV2D7dO.mjs +694 -0
- package/dist/macos-run.mjs +1 -1
- package/dist/maintenance-run.mjs +123 -248
- package/dist/{metro-DnCEjCxs.mjs → metro-DiXtA3Ty.mjs} +107 -30
- package/dist/metro-gateway-CKZqKqx7.mjs +209 -0
- package/dist/{named-ports-4WopRPg-.mjs → named-ports-DkEzOfzO.mjs} +42 -90
- package/dist/native-android-CqgnOgsT.mjs +645 -0
- package/dist/native-gradle-artifact-inputs-DHFnmrVf.mjs +270 -0
- package/dist/native-gradle-inputs-Dnh84Zbd.mjs +211 -0
- package/dist/native-inputs-TRTTTRxt.mjs +91 -0
- package/dist/native-run-NwUuxbkg.mjs +276 -0
- package/dist/{native-runtime-k8dTIrbK.mjs → native-runtime-B7KIafpM.mjs} +11 -9
- package/dist/native-source-Y0yLhFyQ.mjs +133 -0
- package/dist/{native-xcode-inputs-CykopJdM.mjs → native-xcode-inputs-D_VOfFL7.mjs} +26 -18
- package/dist/{native-xcode-ios-Bbf30Gm0.mjs → native-xcode-ios-BYDOHWLN.mjs} +104 -60
- package/dist/{ndjson-GOnbyvso.mjs → ndjson-CgE_o3Dl.mjs} +3 -3
- package/dist/offload-worker.d.mts +59 -41
- package/dist/offload-worker.mjs +478 -115
- package/dist/operation-exec-BW0em61G.mjs +171 -0
- package/dist/{ownership-DCtw_2IA.mjs → ownership-C5fo5Edb.mjs} +3 -3
- package/dist/{page-DRHy329N.mjs → page-DBL2PiUU.mjs} +12 -5
- package/dist/paths-BsJMoPP2.mjs +12 -0
- package/dist/paths-DQtTgsBf.mjs +42 -0
- package/dist/{placement-log-MOfPVgaM.mjs → placement-log-BnGgJRRZ.mjs} +5 -5
- package/dist/{plan-placement-V0TdzYXp.mjs → plan-placement-DIsMtN-d.mjs} +15 -11
- package/dist/{ports-DdSUicVo.mjs → ports-Cy1RkmuQ.mjs} +3 -3
- package/dist/prebuild-D4JoEAEs.mjs +484 -0
- package/dist/{project-_phNIJfs.mjs → project-wM_XFTvE.mjs} +65 -37
- package/dist/{pull-request-CQ8sPbZa.mjs → pull-request-W1IuLXDt.mjs} +102 -20
- package/dist/pull-request-eODyIqZF.d.mts +124 -0
- package/dist/pull-requests.d.mts +2 -120
- package/dist/pull-requests.mjs +2 -2
- package/dist/{react-native-android-CM1XRDwv.mjs → react-native-android-6OZ7MSM7.mjs} +121 -72
- package/dist/{gradle-DSrXb6lG.mjs → react-native-build-Dvd8hC21.mjs} +292 -15
- package/dist/react-native-doctor-DsoSF1Y9.mjs +234 -0
- package/dist/{react-native-ios-Dhq_eiYQ.mjs → react-native-ios-4u1Rn-GR.mjs} +71 -39
- package/dist/{recordings-D0d5aQt0.mjs → recordings-CQmfzAh7.mjs} +12 -9
- package/dist/registration-BcRgF6Ox.mjs +51 -0
- package/dist/{reload-C8JhxjSh.mjs → reload-BzkYabB-.mjs} +94 -47
- package/dist/reload-D7AVlb8N.mjs +2 -0
- package/dist/{remote-BiR6aYjy.mjs → remote-DjGDmlTZ.mjs} +26 -18
- package/dist/result-D2Ykz_iD.mjs +435 -0
- package/dist/{run-BtPgpKks.mjs → run-DidHghD1.mjs} +5 -5
- package/dist/selection-DSMbWEX2.mjs +620 -0
- package/dist/{server-bare-R2m_gsH8.mjs → server-bare-CafzuyFB.mjs} +3 -4
- package/dist/{server-command-CTtCkmcQ.mjs → server-command-C6QjeupS.mjs} +14 -7
- package/dist/{server-expo-DsguMGUF.mjs → server-expo-CrjB29pq.mjs} +54 -11
- package/dist/server-expo-D58c9L0M.mjs +2 -0
- package/dist/{settings-B717odTD.mjs → settings-C7O8A6ZI.mjs} +309 -119
- package/dist/settings-DpR-1Wnn.mjs +114 -0
- package/dist/settings-write-CzD60rOD.mjs +360 -0
- package/dist/settings.schema.json +176 -16
- package/dist/{simslim-CCr3rE-A.mjs → simslim-BNckWpXi.mjs} +14 -12
- package/dist/{slot-launch-DPt0m9Nd.mjs → slot-launch-CbfmNgM6.mjs} +15 -20
- package/dist/{spawn-entry-Dcb7sDUz.mjs → spawn-entry-jw19m8Nl.mjs} +0 -1
- package/dist/stage-Bps5oius.mjs +200 -0
- package/dist/{start-TXeMmmO2.mjs → start-Br-oxqMD.mjs} +1 -1
- package/dist/start-EojN4WFz.mjs +1364 -0
- package/dist/{state-D1Icyjlu.mjs → state--smjFSTL.mjs} +55 -13
- package/dist/{state-CG_OFoaA.mjs → state-08I3hw3_.mjs} +3 -2
- package/dist/{stats-BoY5sEPU.mjs → stats-D3GerlFW.mjs} +7 -13
- package/dist/stats-report-BzaPrRm6.mjs +16 -0
- package/dist/{status-BTA7kKiQ.mjs → status-BRROykC8.mjs} +7 -5
- package/dist/status-EvrK72L2.mjs +2 -0
- package/dist/{status-WgFbkRzj.mjs → status-WBDajew2.mjs} +554 -452
- package/dist/{status-watch-C23ILtf9.mjs → status-watch-Bg_u-pwR.mjs} +222 -25
- package/dist/status-worker.d.mts +1 -0
- package/dist/status-worker.mjs +19 -0
- package/dist/stim-desktop-D5OGpYZM.mjs +112 -0
- package/dist/{stop-DR6VoDIk.mjs → stop-DTZ9uTw3.mjs} +2 -2
- package/dist/{stop-DPEhunlw.mjs → stop-Dky10AEJ.mjs} +33 -23
- package/dist/{stop-BNmBMi1F.mjs → stop-Mv6W9FAk.mjs} +137 -98
- package/dist/supervisor-run.d.mts +8 -17
- package/dist/supervisor-run.mjs +56 -28
- package/dist/{support-CwjbFs9H.mjs → support-CNioewsd.mjs} +14 -14
- package/dist/{support-DMTqj3Hm.mjs → support-yUiOjnVY.mjs} +20 -13
- package/dist/swiftpm-artifact-inputs-DFcGsXDP.mjs +153 -0
- package/dist/swiftpm-macos-BIak-Rxv.mjs +532 -0
- package/dist/toolchain-BY8X8HXx.mjs +2 -0
- package/dist/toolchain-DpJMHq0n.mjs +876 -0
- package/dist/{trigger-CigAYjeL.mjs → trigger-Cbyc41d_.mjs} +21 -6
- package/dist/trigger-yb_nFzPF.mjs +2 -0
- package/dist/{warm-progress-BuY-Fvfn.mjs → warm-progress-CA3TgT3-.mjs} +263 -156
- package/dist/web-CZ07gd77.mjs +2 -0
- package/dist/{web-BDBMxDpx.mjs → web-DKx6yoFA.mjs} +20 -14
- package/dist/web-run.mjs +13 -12
- package/dist/windows-report.d.mts +1 -0
- package/dist/windows-report.mjs +18 -0
- package/dist/windows-report.ps1 +43 -0
- package/dist/{workspace-state-DJt_MMvf.mjs → workspace-state-U08dEzhG.mjs} +62 -58
- package/dist/{workspaces-C36s2jb9.mjs → workspaces-CpANmBlq.mjs} +1723 -1286
- package/dist/{worktree-CgvJYJ-Q.mjs → worktree-CHecZ4Oq.mjs} +1097 -547
- package/dist/worktree-ZqZVjD9K.mjs +2 -0
- package/dist/xcode-BXn9-xYx.mjs +896 -0
- package/package.json +7 -5
- package/shim/android-audio/Main.java +24 -0
- package/shim/android-audio/README.md +17 -0
- package/shim/android-audio.jar +0 -0
- package/shim/android-optimizations.gradle +7 -5
- package/dist/activity-BSV745Kz.mjs +0 -629
- package/dist/api-qvOzWyfO.mjs +0 -154
- package/dist/api-run.mjs +0 -294
- package/dist/app-install-BIKtMfPq.mjs +0 -896
- package/dist/artifact-lifecycle-DzPpBYvK.mjs +0 -30
- package/dist/budget-f7516Zk0.mjs +0 -4106
- package/dist/build-facts-DcLv2HSd.d.mts +0 -111
- package/dist/cancellation-C_22VQTH.mjs +0 -24
- package/dist/client-qQAaXSUs.mjs +0 -1771
- package/dist/config-BYDxlMY3.mjs +0 -562
- package/dist/device-DuiPJQSn.mjs +0 -290
- package/dist/device-lease-BX1sJQ2E.mjs +0 -1224
- package/dist/device-remote-xRT0Vmo9.mjs +0 -1272
- package/dist/eas-session-ledger-Cm0Gwq2F.mjs +0 -207
- package/dist/exec-DqKo9GKJ.mjs +0 -268
- package/dist/git-DDfFzkMm.mjs +0 -45
- package/dist/launch-verify-B6Q8hZP-.mjs +0 -428
- package/dist/logs-BGrEUaEs.mjs +0 -383
- package/dist/macos-Ch5VJmcS.mjs +0 -584
- package/dist/metro-gateway-CX9Xlkq2.mjs +0 -103
- package/dist/miss-reason-mKzZ4VvN.mjs +0 -238
- package/dist/native-android-D5qc3-_v.mjs +0 -226
- package/dist/native-gradle-inputs-BsLRrl7u.mjs +0 -349
- package/dist/ndjson-Du3j0AQ9.d.mts +0 -17
- package/dist/plan-C4TjdPsR.mjs +0 -260
- package/dist/prebuild-DYbucPXy.mjs +0 -212
- package/dist/preview-78a9kR_-.mjs +0 -1011
- package/dist/react-native-build-CtCccJtb.mjs +0 -239
- package/dist/react-native-doctor-CfRnFfjs.mjs +0 -78
- package/dist/remote-cache-OyOyVIS3.mjs +0 -526
- package/dist/result-CaJg5Iat.mjs +0 -186
- package/dist/server-expo-Bigriskl.mjs +0 -2
- package/dist/settings-BORNaaCO.mjs +0 -402
- package/dist/settings-vk3mJGSm.d.mts +0 -13
- package/dist/spawn-claims-DVS5RkMS.mjs +0 -72
- package/dist/stage-KNTQopqv.mjs +0 -157
- package/dist/start-BWqzODpb.mjs +0 -1116
- package/dist/stim-desktop-BzBN2Em2.mjs +0 -63
- package/dist/swiftpm-macos-DRNVJWlb.mjs +0 -119
- package/dist/tailnet-Dh-qiYzG.mjs +0 -265
- package/dist/toolchain-BFkAcstd.mjs +0 -2
- package/dist/toolchain-DxSsNssu.mjs +0 -1657
- package/dist/trigger-9_USgmxz.mjs +0 -2
- package/dist/web-Dq-i3wzJ.mjs +0 -2
- package/dist/worktree-DpLdCHga.mjs +0 -2
- package/dist/{api-run.d.mts → doctor-fingerprint.d.mts} +0 -0
- package/dist/{errors-DzkMYh8Q.mjs → errors-ccaPc4ox.mjs} +2 -2
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { g as process_identity_exports, r as WorkspaceProcessCleanupError, t as getExecutor } from "./exec-DEoAhcze.mjs";
|
|
2
|
+
import { F as isOnMountedVolume, I as listMountedVolumes, O as withConfigLockAsync, _ as loadConfig, n as allMetroPorts, r as claimMetroPort, x as releaseMetroPort } from "./config-DlrsduwG.mjs";
|
|
3
|
+
import { t as runOperationFile } from "./operation-exec-BW0em61G.mjs";
|
|
4
|
+
import { r as readWorkspaceState } from "./workspace-state-U08dEzhG.mjs";
|
|
4
5
|
import { sep } from "path";
|
|
5
6
|
import { existsSync, readlinkSync, realpathSync } from "fs";
|
|
6
7
|
import { readFile } from "node:fs/promises";
|
|
@@ -192,38 +193,51 @@ function createPortProbe() {
|
|
|
192
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.`);
|
|
193
194
|
};
|
|
194
195
|
}
|
|
195
|
-
function isMetroRunning(port) {
|
|
196
|
+
function isMetroRunning(port, signal) {
|
|
196
197
|
return new Promise((resolve) => {
|
|
197
198
|
const req = request({
|
|
198
199
|
hostname: "localhost",
|
|
199
200
|
port,
|
|
200
201
|
path: "/status",
|
|
201
|
-
timeout: 2e3
|
|
202
|
+
timeout: 2e3,
|
|
203
|
+
signal
|
|
202
204
|
}, (res) => {
|
|
203
205
|
let data = "";
|
|
206
|
+
let bytes = 0;
|
|
204
207
|
res.on("data", (chunk) => {
|
|
208
|
+
bytes += Buffer.byteLength(chunk);
|
|
209
|
+
if (bytes > 65536) return finish(false);
|
|
205
210
|
data += chunk;
|
|
206
211
|
});
|
|
207
|
-
res.on("end", () =>
|
|
212
|
+
res.on("end", () => finish(data.includes("packager-status:running")));
|
|
213
|
+
res.on("error", () => finish(false));
|
|
214
|
+
res.on("aborted", () => finish(false));
|
|
208
215
|
});
|
|
209
|
-
|
|
210
|
-
|
|
216
|
+
const deadline = setTimeout(() => finish(false), 2e3);
|
|
217
|
+
const finish = (running) => {
|
|
218
|
+
clearTimeout(deadline);
|
|
211
219
|
req.destroy();
|
|
212
|
-
resolve(
|
|
213
|
-
}
|
|
220
|
+
resolve(running);
|
|
221
|
+
};
|
|
222
|
+
req.on("error", () => finish(false));
|
|
223
|
+
req.on("timeout", () => finish(false));
|
|
214
224
|
req.end();
|
|
215
225
|
});
|
|
216
226
|
}
|
|
217
227
|
const FIRST_PORT = 8082;
|
|
218
|
-
async function computeNextPort(isFree = createPortProbe()) {
|
|
228
|
+
async function computeNextPort(isFree = createPortProbe(), signal) {
|
|
229
|
+
signal?.throwIfAborted();
|
|
219
230
|
const taken = new Set(allMetroPorts());
|
|
220
231
|
for (let port = FIRST_PORT; port < 8282; port++) {
|
|
221
232
|
if (taken.has(port)) continue;
|
|
222
|
-
|
|
233
|
+
const free = await isFree(port);
|
|
234
|
+
signal?.throwIfAborted();
|
|
235
|
+
if (free) return port;
|
|
223
236
|
}
|
|
224
237
|
throw new Error(`Found no free Metro port between ${FIRST_PORT} and 8281. Free one up, or stop a stale bundler (\`stim status\`, \`stim stop <port>\`).`);
|
|
225
238
|
}
|
|
226
|
-
async function findReclaimablePort(excludeProjectPath, probe = isMetroRunning, { isMounted = isOnMountedVolume, mountedVolumes } = {}) {
|
|
239
|
+
async function findReclaimablePort(excludeProjectPath, probe = isMetroRunning, { isMounted = isOnMountedVolume, mountedVolumes, signal } = {}) {
|
|
240
|
+
signal?.throwIfAborted();
|
|
227
241
|
const cfg = loadConfig();
|
|
228
242
|
if (!cfg?.projects) return null;
|
|
229
243
|
const mounted = mountedVolumes || listMountedVolumes();
|
|
@@ -238,26 +252,31 @@ async function findReclaimablePort(excludeProjectPath, probe = isMetroRunning, {
|
|
|
238
252
|
ownerPath: path
|
|
239
253
|
});
|
|
240
254
|
}
|
|
241
|
-
for (const c of candidates)
|
|
255
|
+
for (const c of candidates) {
|
|
256
|
+
const alive = await probe(c.port);
|
|
257
|
+
signal?.throwIfAborted();
|
|
258
|
+
if (!alive) return c;
|
|
259
|
+
}
|
|
242
260
|
return null;
|
|
243
261
|
}
|
|
244
|
-
async function allocatePort(projectPath, probe = isMetroRunning, isFree = createPortProbe()) {
|
|
245
|
-
const reclaim = await findReclaimablePort(projectPath, probe);
|
|
262
|
+
async function allocatePort(projectPath, probe = isMetroRunning, isFree = createPortProbe(), signal) {
|
|
263
|
+
const reclaim = await findReclaimablePort(projectPath, probe, { signal });
|
|
246
264
|
if (reclaim && await isFree(reclaim.port)) {
|
|
247
|
-
releaseMetroPort(reclaim.ownerPath, reclaim.port);
|
|
265
|
+
await withConfigLockAsync(() => releaseMetroPort(reclaim.ownerPath, reclaim.port), signal);
|
|
248
266
|
return reclaim.port;
|
|
249
267
|
}
|
|
250
|
-
return computeNextPort(isFree);
|
|
268
|
+
return computeNextPort(isFree, signal);
|
|
251
269
|
}
|
|
252
270
|
const RESERVE_ATTEMPTS = 5;
|
|
253
|
-
async function reserveMetroPort(projectPath, probe = isMetroRunning, isFree, pinned = null) {
|
|
271
|
+
async function reserveMetroPort(projectPath, probe = (port) => isMetroRunning(port, signal), isFree, pinned = null, signal) {
|
|
272
|
+
signal?.throwIfAborted();
|
|
254
273
|
if (pinned !== null) {
|
|
255
|
-
if (claimMetroPort(projectPath, pinned) !== null) return pinned;
|
|
274
|
+
if (await withConfigLockAsync(() => claimMetroPort(projectPath, pinned), signal) !== null) return pinned;
|
|
256
275
|
throw new Error(`metro.port ${pinned} is already reserved by another workspace's Metro or named port.`);
|
|
257
276
|
}
|
|
258
277
|
for (let attempt = 0; attempt < RESERVE_ATTEMPTS; attempt++) {
|
|
259
|
-
const port = await allocatePort(projectPath, probe, isFree);
|
|
260
|
-
const claimed = claimMetroPort(projectPath, port);
|
|
278
|
+
const port = await allocatePort(projectPath, probe, isFree, signal);
|
|
279
|
+
const claimed = await withConfigLockAsync(() => claimMetroPort(projectPath, port), signal);
|
|
261
280
|
if (claimed !== null) return claimed;
|
|
262
281
|
}
|
|
263
282
|
throw new Error(`Could not reserve a Metro port after ${RESERVE_ATTEMPTS} attempts: another Stim run claimed each one first. Retry.`);
|
|
@@ -305,6 +324,25 @@ function listeningPids(port, platform = process.platform) {
|
|
|
305
324
|
if (pids.length > 0 || platform !== "win32") return pids;
|
|
306
325
|
return parseNetstatPids(getExecutor().runQuiet("netstat -ano", { timeoutMs: 1e4 }), port);
|
|
307
326
|
}
|
|
327
|
+
/** Reads listener pids without blocking on lsof or netstat. */
|
|
328
|
+
async function listeningPidsAsync(port, platform = process.platform, signal) {
|
|
329
|
+
const pids = parseLsofPids(await getExecutor().runFileQuietAsync("lsof", [
|
|
330
|
+
"-nP",
|
|
331
|
+
`-iTCP:${port}`,
|
|
332
|
+
"-sTCP:LISTEN",
|
|
333
|
+
"-t"
|
|
334
|
+
], {
|
|
335
|
+
timeoutMs: 1e4,
|
|
336
|
+
killSignal: "SIGKILL",
|
|
337
|
+
signal
|
|
338
|
+
}));
|
|
339
|
+
if (pids.length > 0 || platform !== "win32") return pids;
|
|
340
|
+
return parseNetstatPids(await getExecutor().runFileQuietAsync("netstat", ["-ano"], {
|
|
341
|
+
timeoutMs: 1e4,
|
|
342
|
+
killSignal: "SIGKILL",
|
|
343
|
+
signal
|
|
344
|
+
}), port);
|
|
345
|
+
}
|
|
308
346
|
function parseLsofCwd(out) {
|
|
309
347
|
if (!out) return null;
|
|
310
348
|
const lines = String(out).split("\n");
|
|
@@ -313,8 +351,12 @@ function parseLsofCwd(out) {
|
|
|
313
351
|
const nLine = lines.slice(idx + 1).find((l) => l.startsWith("n"));
|
|
314
352
|
return nLine ? nLine.slice(1) : null;
|
|
315
353
|
}
|
|
316
|
-
function lsofOutput(args) {
|
|
317
|
-
return getExecutor().runFileAsync("lsof", args, {
|
|
354
|
+
function lsofOutput(args, signal) {
|
|
355
|
+
return getExecutor().runFileAsync("lsof", args, {
|
|
356
|
+
timeoutMs: 1e4,
|
|
357
|
+
killSignal: "SIGKILL",
|
|
358
|
+
signal
|
|
359
|
+
}).catch((error) => typeof error?.stdout === "string" ? error.stdout : null);
|
|
318
360
|
}
|
|
319
361
|
/**
|
|
320
362
|
* `listeningPids` for several ports with one lsof call. A port lsof could not be asked about is
|
|
@@ -346,7 +388,7 @@ function parseLsofCwds(out) {
|
|
|
346
388
|
return cwds;
|
|
347
389
|
}
|
|
348
390
|
/** `processCwd` for several pids with at most one lsof call. A pid lsof could not be asked about is missing from the map. */
|
|
349
|
-
async function processCwds(pids) {
|
|
391
|
+
async function processCwds(pids, signal) {
|
|
350
392
|
const cwds = /* @__PURE__ */ new Map();
|
|
351
393
|
const viaLsof = [];
|
|
352
394
|
for (const pid of new Set(pids)) {
|
|
@@ -365,7 +407,7 @@ async function processCwds(pids) {
|
|
|
365
407
|
"-d",
|
|
366
408
|
"cwd",
|
|
367
409
|
"-Fn"
|
|
368
|
-
]);
|
|
410
|
+
], signal);
|
|
369
411
|
if (out === null) return cwds;
|
|
370
412
|
const found = parseLsofCwds(out);
|
|
371
413
|
for (const pid of viaLsof) cwds.set(pid, found.get(pid) ?? null);
|
|
@@ -459,16 +501,51 @@ function signalProcessTree(pid, signal = "SIGTERM", { group = false, platform =
|
|
|
459
501
|
"/F"
|
|
460
502
|
], { timeoutMs: 3e4 }) !== null;
|
|
461
503
|
}
|
|
462
|
-
|
|
504
|
+
/** Signals a process tree without blocking on Windows taskkill. */
|
|
505
|
+
async function signalProcessTreeAsync(pid, signal = "SIGTERM", options = {}) {
|
|
506
|
+
options.operation?.signal.throwIfAborted();
|
|
507
|
+
if (!Number.isSafeInteger(pid) || pid <= 1) throw new Error(`Refusing to signal pid ${pid}.`);
|
|
508
|
+
if ((options.platform ?? process.platform) !== "win32") return signalProcessTree(pid, signal, options);
|
|
509
|
+
if (options.operation) {
|
|
510
|
+
await runOperationFile(options.operation, getExecutor(), "taskkill", [
|
|
511
|
+
"/PID",
|
|
512
|
+
String(pid),
|
|
513
|
+
"/T",
|
|
514
|
+
"/F"
|
|
515
|
+
], {
|
|
516
|
+
cwd: options.operation.cwd,
|
|
517
|
+
timeoutMs: 3e4,
|
|
518
|
+
killSignal: "SIGKILL"
|
|
519
|
+
});
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
return await getExecutor().runFileQuietAsync("taskkill", [
|
|
523
|
+
"/PID",
|
|
524
|
+
String(pid),
|
|
525
|
+
"/T",
|
|
526
|
+
"/F"
|
|
527
|
+
], {
|
|
528
|
+
timeoutMs: 3e4,
|
|
529
|
+
killSignal: "SIGKILL"
|
|
530
|
+
}) !== null;
|
|
531
|
+
}
|
|
532
|
+
/** Stops the verified Metro process tree without blocking on Windows taskkill. */
|
|
533
|
+
async function killMetroTreeAsync(leader, processToken, operation) {
|
|
534
|
+
operation?.signal.throwIfAborted();
|
|
463
535
|
if (!leader || leader === process.pid || (0, process_identity_exports.inspectProcessIdentity)({
|
|
464
536
|
pid: leader,
|
|
465
537
|
processToken
|
|
466
538
|
}) !== "same") return false;
|
|
467
539
|
try {
|
|
468
|
-
return
|
|
469
|
-
|
|
540
|
+
return await signalProcessTreeAsync(leader, "SIGTERM", {
|
|
541
|
+
group: true,
|
|
542
|
+
operation
|
|
543
|
+
});
|
|
544
|
+
} catch (error) {
|
|
545
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
546
|
+
operation?.signal.throwIfAborted();
|
|
470
547
|
return false;
|
|
471
548
|
}
|
|
472
549
|
}
|
|
473
550
|
//#endregion
|
|
474
|
-
export {
|
|
551
|
+
export { PORT_INSPECTION_REMEDY as _, listeningPidsAsync as a, pidExists as c, resolveProjectMetro as d, signalProcessTree as f, reserveMetroPort as g, isMetroRunning as h, listeningPids as i, processCwd as l, createPortProbe as m, isInsideProject as n, listeningPidsByPort as o, signalProcessTreeAsync as p, killMetroTreeAsync as r, parseNetstatPids as s, NOT_OURS_FOREIGN_CWD as t, processCwds as u, PortInspectionError as v };
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { g as process_identity_exports } from "./exec-DEoAhcze.mjs";
|
|
2
|
+
import { g as workspaceStateFile$1 } from "./paths-DQtTgsBf.mjs";
|
|
3
|
+
import { ot as tailscaleStatusAsync } from "./config-DlrsduwG.mjs";
|
|
4
|
+
import { d as cancelledError, f as throwIfCancelled } from "./operation-exec-BW0em61G.mjs";
|
|
5
|
+
import { l as withWorkspaceStateLockAsync, o as updateWorkspaceState, r as readWorkspaceState$1, s as updateWorkspaceStateAsync } from "./workspace-state-U08dEzhG.mjs";
|
|
6
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
7
|
+
import { statSync, watch } from "node:fs";
|
|
8
|
+
import { HOSTED_METRO_REQUESTS_KEY, findPeer, hostedMetroGateways, hostedMetroRequests, isJsonObject, parseMachine } from "@stim-cli/core/state";
|
|
9
|
+
import { isIP } from "node:net";
|
|
10
|
+
//#region src/workspace/workspace-state-watch.ts
|
|
11
|
+
/** Filesystem events are hints; callers retain their own bounded repair and state verification. */
|
|
12
|
+
function watchWorkspaceState(root, changed) {
|
|
13
|
+
let stopped = false;
|
|
14
|
+
let current;
|
|
15
|
+
let startup;
|
|
16
|
+
const clear = () => {
|
|
17
|
+
clearTimeout(startup);
|
|
18
|
+
const previous = current;
|
|
19
|
+
current = void 0;
|
|
20
|
+
previous?.watcher.close();
|
|
21
|
+
};
|
|
22
|
+
const refresh = () => {
|
|
23
|
+
if (stopped) return;
|
|
24
|
+
try {
|
|
25
|
+
const file = workspaceStateFile$1(root);
|
|
26
|
+
const stat = statSync(file);
|
|
27
|
+
if (current?.dev === stat.dev && current.ino === stat.ino) return;
|
|
28
|
+
clear();
|
|
29
|
+
const watcher = watch(file, { persistent: false }, () => {
|
|
30
|
+
if (stopped || current?.watcher !== watcher) return;
|
|
31
|
+
refresh();
|
|
32
|
+
changed();
|
|
33
|
+
});
|
|
34
|
+
current = {
|
|
35
|
+
watcher,
|
|
36
|
+
dev: stat.dev,
|
|
37
|
+
ino: stat.ino
|
|
38
|
+
};
|
|
39
|
+
const lost = () => {
|
|
40
|
+
if (current?.watcher === watcher) clear();
|
|
41
|
+
};
|
|
42
|
+
watcher.once("error", lost);
|
|
43
|
+
watcher.once("close", lost);
|
|
44
|
+
startup = setTimeout(() => {
|
|
45
|
+
if (stopped) return;
|
|
46
|
+
refresh();
|
|
47
|
+
changed();
|
|
48
|
+
}, 500);
|
|
49
|
+
} catch {
|
|
50
|
+
clear();
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
refresh();
|
|
54
|
+
return {
|
|
55
|
+
refresh,
|
|
56
|
+
close: () => {
|
|
57
|
+
stopped = true;
|
|
58
|
+
clear();
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
//#region src/device-host/metro-gateway.ts
|
|
64
|
+
async function waitForGateway(root, read, { waitMs, signal, what }) {
|
|
65
|
+
throwIfCancelled(signal, what);
|
|
66
|
+
const startedAt = Date.now();
|
|
67
|
+
const deadline = startedAt + waitMs;
|
|
68
|
+
let observer;
|
|
69
|
+
let timer;
|
|
70
|
+
let queued;
|
|
71
|
+
let onAbort;
|
|
72
|
+
try {
|
|
73
|
+
return await new Promise((resolve, reject) => {
|
|
74
|
+
const check = () => {
|
|
75
|
+
clearImmediate(queued);
|
|
76
|
+
queued = void 0;
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
try {
|
|
79
|
+
throwIfCancelled(signal, what);
|
|
80
|
+
observer?.refresh();
|
|
81
|
+
const result = read();
|
|
82
|
+
const remaining = deadline - Date.now();
|
|
83
|
+
if (result !== null || remaining <= 0) resolve(result);
|
|
84
|
+
else {
|
|
85
|
+
const repairMs = Date.now() - startedAt < 500 ? 100 : 1e3;
|
|
86
|
+
timer = setTimeout(check, Math.min(remaining, repairMs));
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
reject(error);
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
onAbort = () => reject(cancelledError(what));
|
|
93
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
94
|
+
observer = watchWorkspaceState(root, () => {
|
|
95
|
+
queued ??= setImmediate(check);
|
|
96
|
+
});
|
|
97
|
+
check();
|
|
98
|
+
});
|
|
99
|
+
} finally {
|
|
100
|
+
observer?.close();
|
|
101
|
+
clearTimeout(timer);
|
|
102
|
+
clearImmediate(queued);
|
|
103
|
+
if (onAbort) signal?.removeEventListener("abort", onAbort);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function gatewayAddresses(credential, status) {
|
|
107
|
+
const machine = parseMachine(credential.machine);
|
|
108
|
+
const peer = machine ? findPeer(status, machine.name) : "missing";
|
|
109
|
+
if (typeof peer === "string") throw new Error(`${credential.machine} no longer resolves to one tailnet peer. Run stim doctor.`);
|
|
110
|
+
const self = isJsonObject(status) && isJsonObject(status.Self) ? status.Self : null;
|
|
111
|
+
const address = (Array.isArray(self?.TailscaleIPs) ? self.TailscaleIPs : []).find((ip) => typeof ip === "string" && isIP(ip) === isIP(peer.address) && isIP(ip) !== 0);
|
|
112
|
+
if (!address) throw new Error("This machine has no Tailscale address compatible with the hosting Mac.");
|
|
113
|
+
return {
|
|
114
|
+
address,
|
|
115
|
+
peer: peer.address
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function requireHostedMetro(root) {
|
|
119
|
+
const supervisor = readWorkspaceState$1(root)?.supervisor;
|
|
120
|
+
if (supervisor?.hostedMetro === true && (0, process_identity_exports.inspectProcessIdentity)(supervisor) === "same") return;
|
|
121
|
+
throw Object.assign(/* @__PURE__ */ new Error("Hosted Debug runs require a running Metro supervisor with private gateway support. Run stim stop; stim start, then retry."), {
|
|
122
|
+
code: "STIM_HOSTING_REFUSED",
|
|
123
|
+
remedy: "Run stim stop; stim start, then retry."
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
async function requestHostedMetro(root, session, credential, signal) {
|
|
127
|
+
throwIfCancelled(signal, "hosted Metro gateway");
|
|
128
|
+
requireHostedMetro(root);
|
|
129
|
+
const addresses = gatewayAddresses(credential, await tailscaleStatusAsync(signal));
|
|
130
|
+
throwIfCancelled(signal, "hosted Metro gateway");
|
|
131
|
+
requireHostedMetro(root);
|
|
132
|
+
const previous = hostedMetroRequests(readWorkspaceState$1(root))[session];
|
|
133
|
+
const request = previous && previous.address === addresses.address && previous.peer === addresses.peer ? previous : {
|
|
134
|
+
id: randomUUID(),
|
|
135
|
+
machine: credential.machine,
|
|
136
|
+
...addresses,
|
|
137
|
+
secret: randomBytes(32).toString("hex")
|
|
138
|
+
};
|
|
139
|
+
await updateWorkspaceStateAsync(root, (state) => ({
|
|
140
|
+
...state,
|
|
141
|
+
[HOSTED_METRO_REQUESTS_KEY]: {
|
|
142
|
+
...hostedMetroRequests(state),
|
|
143
|
+
[session]: request
|
|
144
|
+
}
|
|
145
|
+
}), signal);
|
|
146
|
+
const opened = await waitForGateway(root, () => {
|
|
147
|
+
const state = readWorkspaceState$1(root);
|
|
148
|
+
const gateway = hostedMetroGateways(state)[session];
|
|
149
|
+
if (gateway?.id === request.id && gateway.processToken === state?.supervisor?.processToken) {
|
|
150
|
+
if (gateway.error) throw new Error(gateway.error);
|
|
151
|
+
if (gateway.port) return {
|
|
152
|
+
gatewayPort: gateway.port,
|
|
153
|
+
secret: request.secret
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}, {
|
|
158
|
+
waitMs: 15e3,
|
|
159
|
+
signal,
|
|
160
|
+
what: "hosted Metro gateway"
|
|
161
|
+
});
|
|
162
|
+
if (opened) return opened;
|
|
163
|
+
throw new Error(`The Metro supervisor did not open a private gateway for ${credential.machine}. Run stim stop; stim start, then retry.`);
|
|
164
|
+
}
|
|
165
|
+
function clearHostedMetro(root, session) {
|
|
166
|
+
updateWorkspaceState(root, (state) => {
|
|
167
|
+
const requests = { ...hostedMetroRequests(state) };
|
|
168
|
+
delete requests[session];
|
|
169
|
+
return {
|
|
170
|
+
...state,
|
|
171
|
+
[HOSTED_METRO_REQUESTS_KEY]: requests
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async function closeHostedMetro(root, session, signal) {
|
|
176
|
+
await withWorkspaceStateLockAsync(root, () => clearHostedMetro(root, session), signal);
|
|
177
|
+
if (!await waitForGateway(root, () => {
|
|
178
|
+
const state = readWorkspaceState$1(root);
|
|
179
|
+
const gateway = hostedMetroGateways(state)[session];
|
|
180
|
+
const supervisor = state?.supervisor;
|
|
181
|
+
if (!gateway?.port || !supervisor || gateway.processToken !== supervisor.processToken) return true;
|
|
182
|
+
return (0, process_identity_exports.inspectProcessIdentity)(supervisor) === "gone" ? true : null;
|
|
183
|
+
}, {
|
|
184
|
+
waitMs: 5e3,
|
|
185
|
+
signal,
|
|
186
|
+
what: "close hosted Metro"
|
|
187
|
+
})) throw new Error("The Metro supervisor did not close its private gateway. Restart Metro, then retry stim stop.");
|
|
188
|
+
}
|
|
189
|
+
function hostedMetroSettings(settings, hosted) {
|
|
190
|
+
return hosted ? {
|
|
191
|
+
...settings,
|
|
192
|
+
ios: {
|
|
193
|
+
...isJsonObject(settings.ios) ? settings.ios : {},
|
|
194
|
+
remote: void 0
|
|
195
|
+
},
|
|
196
|
+
android: {
|
|
197
|
+
...isJsonObject(settings.android) ? settings.android : {},
|
|
198
|
+
remote: void 0
|
|
199
|
+
},
|
|
200
|
+
metro: {
|
|
201
|
+
...isJsonObject(settings.metro) ? settings.metro : {},
|
|
202
|
+
tunnel: void 0,
|
|
203
|
+
publicUrl: void 0,
|
|
204
|
+
ngrokUrl: void 0
|
|
205
|
+
}
|
|
206
|
+
} : settings;
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
209
|
+
export { requireHostedMetro as a, requestHostedMetro as i, closeHostedMetro as n, watchWorkspaceState as o, hostedMetroSettings as r, clearHostedMetro as t };
|
|
@@ -1,63 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { n as declareSpawnsOn, o as stopDeclaringSpawnsOn } from "./spawn-claims-DVS5RkMS.mjs";
|
|
1
|
+
import { g as process_identity_exports, i as withWorkspaceProcessLock, t as getExecutor } from "./exec-DEoAhcze.mjs";
|
|
2
|
+
import { C as saveConfig, O as withConfigLockAsync, _ as loadConfig, c as ensureConfig, d as getConfigDir, p as getProject } from "./config-DlrsduwG.mjs";
|
|
3
|
+
import { f as signalProcessTree, m as createPortProbe, s as parseNetstatPids } from "./metro-DiXtA3Ty.mjs";
|
|
5
4
|
import { realpathSync } from "node:fs";
|
|
6
|
-
import { join } from "node:path";
|
|
7
|
-
import { setTimeout } from "node:timers/promises";
|
|
8
|
-
//#region src/engine/workspace-process-lock.ts
|
|
9
|
-
const DEFAULT_WAIT_MS = 6e4;
|
|
10
|
-
const POLL_MS = 25;
|
|
11
|
-
function workspaceProcessLockError(err) {
|
|
12
|
-
const code = err?.code;
|
|
13
|
-
if (code === "STIM_LOCK_REFUSED") return "refused";
|
|
14
|
-
if (code === "STIM_LOCK_TIMEOUT") return "timeout";
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
function workspaceProcessLockPath(root, name, external) {
|
|
18
|
-
return external ? join(root, `${name}.lock`) : join(root, ".stim", `${name}.lock`);
|
|
19
|
-
}
|
|
20
|
-
async function withWorkspaceProcessLock(root, name, fn, { now = Date.now, signal, sleep = (ms) => setTimeout(ms, void 0, { signal }), waitMs = DEFAULT_WAIT_MS, ownerPurpose, details = {}, onHeld, rejectOwnerPurposes = [], external = false, declareSpawns = false } = {}) {
|
|
21
|
-
const path = workspaceProcessLockPath(root, name, external);
|
|
22
|
-
const deadline = now() + waitMs;
|
|
23
|
-
for (;;) {
|
|
24
|
-
signal?.throwIfAborted();
|
|
25
|
-
const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
|
|
26
|
-
root: path,
|
|
27
|
-
mode: "exclusive",
|
|
28
|
-
label: `${name} lock`,
|
|
29
|
-
details: ownerPurpose ? {
|
|
30
|
-
...details,
|
|
31
|
-
purpose: ownerPurpose
|
|
32
|
-
} : details
|
|
33
|
-
});
|
|
34
|
-
if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
|
|
35
|
-
if (attempt.acquired) {
|
|
36
|
-
if (declareSpawns) declareSpawnsOn(attempt.acquired);
|
|
37
|
-
try {
|
|
38
|
-
return await fn(attempt.acquired);
|
|
39
|
-
} finally {
|
|
40
|
-
stopDeclaringSpawnsOn(attempt.acquired);
|
|
41
|
-
(0, ownership_claim_exports.releaseClaim)(attempt.acquired);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
const holder = attempt.held ?? attempt.waitingFor?.[0];
|
|
45
|
-
const purpose = holder?.details.purpose;
|
|
46
|
-
if (typeof purpose === "string" && rejectOwnerPurposes.includes(purpose)) {
|
|
47
|
-
const error = /* @__PURE__ */ new Error(`The ${name} lock at ${path} is held for ${purpose}.`);
|
|
48
|
-
error.code = "STIM_LOCK_REFUSED";
|
|
49
|
-
throw error;
|
|
50
|
-
}
|
|
51
|
-
if (holder && onHeld) await onHeld(holder);
|
|
52
|
-
if (now() >= deadline) {
|
|
53
|
-
const error = /* @__PURE__ */ new Error(`Timed out waiting for the ${name} lock at ${path}.`);
|
|
54
|
-
error.code = "STIM_LOCK_TIMEOUT";
|
|
55
|
-
throw error;
|
|
56
|
-
}
|
|
57
|
-
await sleep(POLL_MS);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
//#endregion
|
|
61
5
|
//#region src/named-ports.ts
|
|
62
6
|
const FIRST_PORT = 8900;
|
|
63
7
|
const LAST_PORT = 8999;
|
|
@@ -112,8 +56,11 @@ function processCommand(pid, platform) {
|
|
|
112
56
|
"args="
|
|
113
57
|
], { timeoutMs: 5e3 }) ?? "").trim();
|
|
114
58
|
}
|
|
115
|
-
function withPortsLock(fn) {
|
|
116
|
-
return withWorkspaceProcessLock(getConfigDir(), "ports", fn, {
|
|
59
|
+
function withPortsLock(fn, signal) {
|
|
60
|
+
return withWorkspaceProcessLock(getConfigDir(), "ports", fn, {
|
|
61
|
+
external: true,
|
|
62
|
+
signal
|
|
63
|
+
});
|
|
117
64
|
}
|
|
118
65
|
async function getNamedPort(projectPath, label, options = {}) {
|
|
119
66
|
validatePortLabel(label);
|
|
@@ -123,14 +70,14 @@ async function getNamedPort(projectPath, label, options = {}) {
|
|
|
123
70
|
function reserveBrowserPort(projectPath, options = {}) {
|
|
124
71
|
return allocateNamedPort(projectPath, BROWSER_LABEL, options);
|
|
125
72
|
}
|
|
126
|
-
function releaseBrowserPort(projectPath, port) {
|
|
127
|
-
return withPortsLock(
|
|
73
|
+
function releaseBrowserPort(projectPath, port, signal) {
|
|
74
|
+
return withPortsLock(() => withConfigLockAsync(() => {
|
|
128
75
|
const cfg = loadConfig();
|
|
129
76
|
const ports = cfg?.projects?.[projectPath]?.ports;
|
|
130
77
|
if (!cfg || ports?.[BROWSER_LABEL] !== port) return;
|
|
131
78
|
delete ports[BROWSER_LABEL];
|
|
132
79
|
saveConfig(cfg);
|
|
133
|
-
}));
|
|
80
|
+
}, signal), signal);
|
|
134
81
|
}
|
|
135
82
|
async function allocateNamedPort(projectPath, label, { isFree = createPortProbe() }) {
|
|
136
83
|
const root = realpathSync(projectPath);
|
|
@@ -140,7 +87,7 @@ async function allocateNamedPort(projectPath, label, { isFree = createPortProbe(
|
|
|
140
87
|
for (let port = FIRST_PORT; port <= LAST_PORT; port++) {
|
|
141
88
|
if (Object.values(loadConfig()?.projects ?? {}).some((project) => project.metroPort === port || Object.values(project.ports ?? {}).includes(port))) continue;
|
|
142
89
|
if (!await isFree(port)) continue;
|
|
143
|
-
if (
|
|
90
|
+
if (await withConfigLockAsync(() => {
|
|
144
91
|
const cfg = ensureConfig();
|
|
145
92
|
if (Object.values(cfg.projects).some((project) => project.metroPort === port || Object.values(project.ports ?? {}).includes(port))) return false;
|
|
146
93
|
const project = cfg.projects[root] ??= {
|
|
@@ -158,9 +105,10 @@ async function allocateNamedPort(projectPath, label, { isFree = createPortProbe(
|
|
|
158
105
|
throw new Error(`No free named port between ${FIRST_PORT} and ${LAST_PORT}. Use stim ports stop or stim ports release in a workspace that no longer needs its ports.`);
|
|
159
106
|
});
|
|
160
107
|
}
|
|
161
|
-
async function stopListeners(port, label, dryRun, log, platform) {
|
|
108
|
+
async function stopListeners(port, label, dryRun, log, platform, signal) {
|
|
162
109
|
const listeners = () => portListeners(port, platform);
|
|
163
110
|
for (const pid of listeners()) {
|
|
111
|
+
signal?.throwIfAborted();
|
|
164
112
|
if (pid === process.pid) throw new Error(`Refusing to stop Stim itself on ${label} (${port}).`);
|
|
165
113
|
const identity = (0, process_identity_exports.captureProcessIdentity)(pid);
|
|
166
114
|
if (!identity.ok) {
|
|
@@ -182,10 +130,10 @@ async function stopListeners(port, label, dryRun, log, platform) {
|
|
|
182
130
|
if (status !== "same") throw new Error(`Cannot verify pid ${pid} on ${label} (${port}); allocation kept.`);
|
|
183
131
|
try {
|
|
184
132
|
signalProcessTree(pid, "SIGTERM", { platform });
|
|
185
|
-
if (!await (0, process_identity_exports.waitForProcessExit)(record, 2e3)) {
|
|
133
|
+
if (!await (0, process_identity_exports.waitForProcessExit)(record, 2e3, signal)) {
|
|
186
134
|
if ((0, process_identity_exports.inspectProcessIdentity)(record) !== "same") throw new Error(`Cannot verify pid ${pid} before SIGKILL.`);
|
|
187
135
|
signalProcessTree(pid, "SIGKILL", { platform });
|
|
188
|
-
if (!await (0, process_identity_exports.waitForProcessExit)(record, 2e3)) throw new Error(`Pid ${pid} did not exit.`);
|
|
136
|
+
if (!await (0, process_identity_exports.waitForProcessExit)(record, 2e3, signal)) throw new Error(`Pid ${pid} did not exit.`);
|
|
189
137
|
}
|
|
190
138
|
} catch (error) {
|
|
191
139
|
if (error.code !== "ESRCH") throw error;
|
|
@@ -194,36 +142,40 @@ async function stopListeners(port, label, dryRun, log, platform) {
|
|
|
194
142
|
}
|
|
195
143
|
if (!dryRun && listeners().length) throw new Error(`Port ${port} still has a listener; allocation kept.`);
|
|
196
144
|
}
|
|
197
|
-
async function clearNamedPorts(projectPath, { label, stop = false, dryRun = false, log = console.error, platform = process.platform } = {}) {
|
|
145
|
+
async function clearNamedPorts(projectPath, { label, stop = false, dryRun = false, log = console.error, platform = process.platform, signal } = {}) {
|
|
198
146
|
if (label !== void 0) validatePortLabel(label);
|
|
199
147
|
await withPortsLock(async () => {
|
|
200
148
|
const ports = getProject(projectPath)?.ports ?? {};
|
|
201
149
|
const selected = Object.entries(ports).filter(([name]) => name !== BROWSER_LABEL && (label === void 0 || name === label));
|
|
202
150
|
if (stop && selected.length > 0 && platform !== "win32" && !getExecutor().findExecutable("lsof")) throw new Error("Cannot stop named ports: lsof is not installed.");
|
|
203
151
|
const failures = [];
|
|
204
|
-
for (const [name, port] of selected)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
152
|
+
for (const [name, port] of selected) {
|
|
153
|
+
signal?.throwIfAborted();
|
|
154
|
+
try {
|
|
155
|
+
if (!Number.isInteger(port) || port < FIRST_PORT || port > LAST_PORT) throw new Error(`Invalid named port ${name} (${port}); repair its config record before cleanup.`);
|
|
156
|
+
if (stop) {
|
|
157
|
+
if (Object.values(loadConfig()?.projects ?? {}).some((project) => project.metroPort === port)) throw new Error(`Port ${port} is reserved for managed Metro; allocation kept.`);
|
|
158
|
+
await stopListeners(port, name, dryRun, log, platform, signal);
|
|
159
|
+
}
|
|
160
|
+
if (dryRun) {
|
|
161
|
+
log(`would release ${name} (${port})`);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
await withConfigLockAsync(() => {
|
|
165
|
+
const cfg = loadConfig();
|
|
166
|
+
const current = cfg?.projects?.[projectPath]?.ports;
|
|
167
|
+
if (!cfg || current?.[name] !== port) return;
|
|
168
|
+
delete current[name];
|
|
169
|
+
saveConfig(cfg);
|
|
170
|
+
}, signal);
|
|
171
|
+
log(`released ${name} (${port})`);
|
|
172
|
+
} catch (error) {
|
|
173
|
+
signal?.throwIfAborted();
|
|
174
|
+
failures.push(`${name} (${port}): ${error.message}`);
|
|
213
175
|
}
|
|
214
|
-
withConfigLock(() => {
|
|
215
|
-
const cfg = loadConfig();
|
|
216
|
-
const current = cfg?.projects?.[projectPath]?.ports;
|
|
217
|
-
if (!cfg || current?.[name] !== port) return;
|
|
218
|
-
delete current[name];
|
|
219
|
-
saveConfig(cfg);
|
|
220
|
-
});
|
|
221
|
-
log(`released ${name} (${port})`);
|
|
222
|
-
} catch (error) {
|
|
223
|
-
failures.push(`${name} (${port}): ${error.message}`);
|
|
224
176
|
}
|
|
225
177
|
if (failures.length) throw new Error(failures.join("\n"));
|
|
226
|
-
});
|
|
178
|
+
}, signal);
|
|
227
179
|
}
|
|
228
180
|
//#endregion
|
|
229
|
-
export {
|
|
181
|
+
export { reserveBrowserPort as i, getNamedPort as n, releaseBrowserPort as r, clearNamedPorts as t };
|