stim 1.6.0 → 1.7.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 (71) hide show
  1. package/README.md +1 -1
  2. package/dist/{android-BkYmndPp.mjs → android-DWssqHQ8.mjs} +165 -661
  3. package/dist/{android-DLJB8ljN.mjs → android-Da-G_o3n.mjs} +646 -548
  4. package/dist/{android-BW8i_YxU.mjs → android-DeYdjjBP.mjs} +6 -2
  5. package/dist/{android-cas-CrzI_Zha.mjs → android-cas-SWIjzeuL.mjs} +54 -18
  6. package/dist/android-cas-compiler.mjs +1 -1
  7. package/dist/{app-install--PAocWvq.mjs → app-install-DJg7bVv-.mjs} +36 -10
  8. package/dist/build-lock-CgtsB6TV.mjs +604 -0
  9. package/dist/{build-slots-CuPszJDu.mjs → build-slots-C-oAHRT1.mjs} +2 -2
  10. package/dist/{cache-manifest-uoOKhgUD.mjs → cache-manifest-CYJi8-Hx.mjs} +2 -2
  11. package/dist/cache-manifest.d.mts +1 -1
  12. package/dist/cache-manifest.mjs +1 -1
  13. package/dist/cli.mjs +14 -14
  14. package/dist/collector-run.d.mts +2 -0
  15. package/dist/collector-run.mjs +7 -7
  16. package/dist/{command-output-BuIb_ZOI.mjs → command-output-B3ycpsYv.mjs} +2 -2
  17. package/dist/{config-DMP7L7H6.mjs → config-B7iyKWkL.mjs} +56 -16
  18. package/dist/{dependency-state-CTL8A9p3.mjs → dependency-state-D0M7oKA7.mjs} +2 -2
  19. package/dist/{deps-C1zGnr61.mjs → deps-oCXVNEqQ.mjs} +6 -6
  20. package/dist/detached-entry-Bpo6ZY_I.mjs +65 -0
  21. package/dist/{dev-client-BaTXAAnC.mjs → dev-client-DDUkWgJL.mjs} +12 -12
  22. package/dist/{device-xoQxw8hR.mjs → device-CBSaC6BU.mjs} +7 -7
  23. package/dist/{device-XY_5eGdN.mjs → device-CLeDp-KH.mjs} +17 -15
  24. package/dist/{device-lease-DBW-IL4p.mjs → device-lease-Ffz_M5t0.mjs} +3 -3
  25. package/dist/{device-pool-C0s_83Og.mjs → device-pool-DNSHL5z8.mjs} +4 -4
  26. package/dist/{device-remote-D9wN3CdE.mjs → device-remote-BprxZp-m.mjs} +11 -11
  27. package/dist/{doctor-BRO8UKlK.mjs → doctor-CTAx9gMu.mjs} +90 -46
  28. package/dist/{error-diagnostics-B5STzxqN.mjs → error-diagnostics-D1Kaf-eT.mjs} +23 -17
  29. package/dist/{exec-CyylIdq9.mjs → exec-B29XNuhP.mjs} +14 -2
  30. package/dist/{gc-C-6grAQI.mjs → gc-CX0KRfLG.mjs} +17 -16
  31. package/dist/{guide-CYtd7xbA.mjs → guide-CPmAG9me.mjs} +38 -15
  32. package/dist/{guide-status-BF6RsJJp.mjs → guide-status-Craq9z0t.mjs} +3 -3
  33. package/dist/{ios-Ccm6c8Hj.mjs → ios-BRXYSxSc.mjs} +34 -37
  34. package/dist/{ios-device-DJOi9FR5.mjs → ios-device-CDbvn5H2.mjs} +2 -2
  35. package/dist/{ios-device-D3pzTKQE.mjs → ios-device-CFXA46ez.mjs} +1 -1
  36. package/dist/{ios-Deh5js5V.mjs → ios-lEJ5pKsq.mjs} +3 -3
  37. package/dist/{logs-D4Poojxy.mjs → logs-CakdBTtm.mjs} +6 -6
  38. package/dist/{logs-query-Cdp5aaCH.mjs → logs-query-BGL-Pl12.mjs} +1 -1
  39. package/dist/metro-C7bcJ0uo.mjs +233 -0
  40. package/dist/{named-ports-DH9SXd0f.mjs → named-ports-DzMOlmaX.mjs} +57 -42
  41. package/dist/{ownership-CD_XnpDu.mjs → ownership-Cp52QAK1.mjs} +3 -3
  42. package/dist/{ownership-CVY9cqCr.mjs → ownership-D5vkdm8H.mjs} +13 -13
  43. package/dist/{ports-A6uTJSPj.mjs → ports-BIbJaKNW.mjs} +3 -3
  44. package/dist/{process-output-C7IlIzd7.mjs → process-output-Dl5uKE03.mjs} +1 -1
  45. package/dist/{project-B_SDt4ip.mjs → project-DDZWf_16.mjs} +3 -3
  46. package/dist/{reclaim-WXTbXPX4.mjs → reclaim-D614jI6_.mjs} +11 -11
  47. package/dist/{reload-xlQvHKfN.mjs → reload-ChW46VDb.mjs} +9 -9
  48. package/dist/{server-bare-CCLb90FS.mjs → server-bare-BQkyxa_b.mjs} +1 -1
  49. package/dist/{server-expo-CX-zkgWl.mjs → server-expo-BVAA1OqJ.mjs} +23 -11
  50. package/dist/server-expo-YDDG3eP7.mjs +2 -0
  51. package/dist/{settings-CGHTt5vb.mjs → settings-BXXorVM2.mjs} +3 -3
  52. package/dist/{slot-launch-BYjnljAs.mjs → slot-launch-DbLvyrup.mjs} +4 -4
  53. package/dist/{start-DHYAyGzm.mjs → start-TTVILlUi.mjs} +111 -38
  54. package/dist/{state-CnWzvCWP.mjs → state-DZobzxLW.mjs} +2 -2
  55. package/dist/{stats-Fo9ACIv7.mjs → stats-BYcZ6n1h.mjs} +1 -1
  56. package/dist/{stats-DHrO0frH.mjs → stats-CLg5RuDS.mjs} +4 -4
  57. package/dist/{status-QeLZb8SM.mjs → status-DUNy_HZs.mjs} +14 -15
  58. package/dist/{stop-CHTaw65F.mjs → stop-CnUpnFT5.mjs} +2 -2
  59. package/dist/{stop-DsdOx9Hj.mjs → stop-ZrfODZ51.mjs} +13 -13
  60. package/dist/supervisor-run.mjs +7 -5
  61. package/dist/{teardown-sm1FwF6V.mjs → teardown-5J5UXz2P.mjs} +14 -17
  62. package/dist/{workspace-process-lock-Bz19_R9R.mjs → workspace-process-lock-BYfZz_S-.mjs} +1 -1
  63. package/dist/{workspace-state-WVwWw_hB.mjs → workspace-state-BT5E3SA2.mjs} +3 -3
  64. package/dist/{worktree-CCNPg_f0.mjs → worktree-BfE4lzHK.mjs} +21 -16
  65. package/dist/{worktree-DJnBbQZ9.mjs → worktree-FG_ZKKdb.mjs} +13 -9
  66. package/dist/{xcode-DUF7jrXj.mjs → xcode-B6YaWeg0.mjs} +8 -8
  67. package/package.json +12 -8
  68. package/shim/android-optimizations.gradle +6 -0
  69. package/dist/metro-ato6ef3J.mjs +0 -99
  70. package/dist/ports-BZprTJRa.mjs +0 -90
  71. package/dist/server-expo-CjeTJ273.mjs +0 -2
@@ -1,31 +1,31 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { E as ownership_claim_exports, P as deviceSlotKey, S as upsertProject, f as getProject, l as getConcurrencyLimits, z as validateDeviceSlot } from "./config-DMP7L7H6.mjs";
1
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
+ import { B as validateDeviceSlot, E as ownership_claim_exports, F as deviceSlotKey, P as deviceSlotFileKey, S as upsertProject, f as getProject, l as getConcurrencyLimits } from "./config-B7iyKWkL.mjs";
3
3
  import { r as createNdjsonWriter } from "./ndjson-BZwI1NBv.mjs";
4
- import { _ as workspaceLogsDir, h as workspaceDir, o as writeWorkspaceState, r as readWorkspaceState } from "./workspace-state-WVwWw_hB.mjs";
5
- import { T as removeTemporaryEntry, g as repoRoot, s as gitCommonDir, w as makeTemporaryDirectory } from "./worktree-DJnBbQZ9.mjs";
6
- import { a as findProjectRoot, c as projectShortcut, i as detectIsExpo, r as detectBundleId, t as appProjectProblem } from "./project-B_SDt4ip.mjs";
7
- import { n as MODE_EXPO, p as writeWorkspaceLaunch, t as MODE_BARE } from "./state-CnWzvCWP.mjs";
8
- import { i as resolveProjectMetro, r as pidExists } from "./metro-ato6ef3J.mjs";
9
- import { n as verifyCollectorOwnership } from "./ownership-CD_XnpDu.mjs";
10
- import { _ as launchErrorPreview, c as launchErrorReport, g as stepTimer, h as stepClock, i as formatDuration, l as phaseLine, m as shortUdid, n as appReadinessMessage, p as shortHash } from "./command-output-BuIb_ZOI.mjs";
11
- import { $ as providerDownloadPath, G as buildCacheKey, J as filesystemBuildCapability, K as describeFingerprintMiss, Q as prepareProviderDownloadDir, R as WAIT_CEILING_MS, U as takeoverLine, V as releaseBuildLock, W as waitForBuild, X as fingerprintDiffSuffix, Y as fingerprintDiffRecord, Z as fingerprintProject, at as untrackedMissLine, et as providerUploadOutcome, nt as resolveBuild, ot as untrackedNativeFiles, rt as storeBuild, tt as refingerprintAfterMutation, z as acquireBuildLock } from "./android-BkYmndPp.mjs";
12
- import { A as resolveSettings, B as artifactCachePolicy, D as remoteDeviceSettingError, F as tunnelModeSetting, H as optimizationBuildProfile, I as unknownSettingKeys, N as settingShapeErrors, O as remoteIosSetting, R as detectProviders, T as publicUrlSetting, U as resolveOptimizations, _ as iosSigningIdentitySha1SettingError, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, f as iosLanHostSetting, g as iosSigningIdentitySha1Setting, h as iosSigningIdentitySettingError, k as resolveCacheProviderConfig, m as iosSigningIdentitySetting, p as iosLanHostSettingError, r as REMOTE_DEVICE_BACKENDS, x as metroWarmupUrlSetting } from "./settings-CGHTt5vb.mjs";
13
- import { C as resolveRemote, S as resolveEasCliBin, _ as exitAfterFlush, a as POOL_SETTING_REMEDY, f as RESOLVE_TIMEOUT_MS, g as easAuthNote, h as checkEasAuth, l as parkedMaxSetting, m as cacheLevel, p as UPLOAD_TIMEOUT_MS, v as isEasAuthFailureText, w as uploadRemote, y as loadProjectProvider } from "./teardown-sm1FwF6V.mjs";
14
- import { _ as easDeviceBuildRemedy, a as swapJsBundle, c as ensureWorkspaceStorageSafely, d as launchOutcomeRecord, f as noMetroMessage, g as warmMetro, h as sleep, l as gateShouldRetry, m as resolveMetroWithRetry, n as pickDevClientScheme, o as needsPrebuild, p as noMetroRemedy, r as schemesFromInfoPlist, s as runPrebuild, t as devClientScheme, u as isPhysicalDeviceRequest, v as isEasBuildFailure, y as resolveEasDevelopmentBuild } from "./dev-client-BaTXAAnC.mjs";
15
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-Bz19_R9R.mjs";
16
- import { C as launchIosApp, E as unverifiedLaunchLines, O as verifyLaunch, T as readMetroRecords, b as isBundleProof, c as RELEASE_VERIFY_WAIT_MS, f as clearOtherUserApps, i as LAUNCH_BUNDLING, k as verifyReleaseLaunch, m as devClientUrl, n as DEFAULT_METRO_PORT, o as LAUNCH_FATAL, s as LAUNCH_UNVERIFIED, v as installIosApp, w as readCollectorRecords, y as iosAppProcess } from "./app-install--PAocWvq.mjs";
17
- import { f as listIosRuntimes } from "./ios-Deh5js5V.mjs";
18
- import { a as lostLine, c as releaseLeaseOnSignal, i as leaseExpiryText, l as runLease, n as DEBUG_VERIFY_STEP_MS, o as lostRefusal, r as acquireRunLease, s as parseDeviceWait, t as selectFromPool, u as waitFlagConflict } from "./device-pool-C0s_83Og.mjs";
19
- import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-CuPszJDu.mjs";
20
- import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-Fo9ACIv7.mjs";
21
- import { c as unknownIosRuntimeRefusal, i as ensureOwnedDevice, n as clearIosAdoptionPending, r as ensureBooted, s as unknownIosDeviceTypeRefusal, t as checkDeviceCapacity } from "./device-XY_5eGdN.mjs";
22
- import { a as ensureMetroReachable, b as ownedSessionName, c as remoteIosDeps, l as resolveRemoteContext, n as binOnPath, o as ensureRemoteBootOwned, r as bundleEntryPoint, w as gateMetroOrigin } from "./device-remote-D9wN3CdE.mjs";
23
- import { a as compilationCacheActivityLine, c as readBundleExecutable, l as readBundleId, p as describeDiagnostic, r as buildIos, s as discoverXcodeProject, t as COMPILATION_CACHE_NOT_RUN, u as resolveScheme } from "./xcode-DUF7jrXj.mjs";
24
- import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-BYjnljAs.mjs";
4
+ import { _ as workspaceLogsDir, h as workspaceDir, o as writeWorkspaceState, r as readWorkspaceState } from "./workspace-state-BT5E3SA2.mjs";
5
+ import { T as removeTemporaryEntry, g as repoRoot, s as gitCommonDir, w as makeTemporaryDirectory } from "./worktree-FG_ZKKdb.mjs";
6
+ import { a as findProjectRoot, c as projectShortcut, i as detectIsExpo, r as detectBundleId, t as appProjectProblem } from "./project-DDZWf_16.mjs";
7
+ import { n as MODE_EXPO, p as writeWorkspaceLaunch, t as MODE_BARE } from "./state-DZobzxLW.mjs";
8
+ import { a as resolveProjectMetro, i as pidExists, o as signalProcessTree } from "./metro-C7bcJ0uo.mjs";
9
+ import { n as verifyCollectorOwnership } from "./ownership-Cp52QAK1.mjs";
10
+ import { A as resolveSettings, B as artifactCachePolicy, D as remoteDeviceSettingError, F as tunnelModeSetting, H as optimizationBuildProfile, I as unknownSettingKeys, N as settingShapeErrors, O as remoteIosSetting, R as detectProviders, T as publicUrlSetting, U as resolveOptimizations, _ as iosSigningIdentitySha1SettingError, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, f as iosLanHostSetting, g as iosSigningIdentitySha1Setting, h as iosSigningIdentitySettingError, k as resolveCacheProviderConfig, m as iosSigningIdentitySetting, p as iosLanHostSettingError, r as REMOTE_DEVICE_BACKENDS, x as metroWarmupUrlSetting } from "./settings-BXXorVM2.mjs";
11
+ import { _ as launchErrorPreview, c as launchErrorReport, g as stepTimer, h as stepClock, i as formatDuration, l as phaseLine, m as shortUdid, n as appReadinessMessage, p as shortHash } from "./command-output-B3ycpsYv.mjs";
12
+ import { C as untrackedNativeFiles, S as untrackedMissLine, _ as providerUploadOutcome, b as storeBuild, c as buildCacheKey, d as filesystemBuildCapability, f as fingerprintDiffRecord, g as providerDownloadPath, h as prepareProviderDownloadDir, i as releaseBuildLock, l as describeFingerprintMiss, m as fingerprintProject, n as acquireBuildLock, o as takeoverLine, p as fingerprintDiffSuffix, s as waitForBuild, t as WAIT_CEILING_MS, v as refingerprintAfterMutation, y as resolveBuild } from "./build-lock-CgtsB6TV.mjs";
13
+ import { C as resolveRemote, S as resolveEasCliBin, _ as exitAfterFlush, a as POOL_SETTING_REMEDY, f as RESOLVE_TIMEOUT_MS, g as easAuthNote, h as checkEasAuth, l as parkedMaxSetting, m as cacheLevel, p as UPLOAD_TIMEOUT_MS, v as isEasAuthFailureText, w as uploadRemote, y as loadProjectProvider } from "./teardown-5J5UXz2P.mjs";
14
+ import { _ as easDeviceBuildRemedy, a as swapJsBundle, c as ensureWorkspaceStorageSafely, d as launchOutcomeRecord, f as noMetroMessage, g as warmMetro, h as sleep, l as gateShouldRetry, m as resolveMetroWithRetry, n as pickDevClientScheme, o as needsPrebuild, p as noMetroRemedy, r as schemesFromInfoPlist, s as runPrebuild, t as devClientScheme, u as isPhysicalDeviceRequest, v as isEasBuildFailure, y as resolveEasDevelopmentBuild } from "./dev-client-DDUkWgJL.mjs";
15
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BYfZz_S-.mjs";
16
+ import { C as launchIosApp, E as unverifiedLaunchLines, O as verifyLaunch, T as readMetroRecords, b as isBundleProof, c as RELEASE_VERIFY_WAIT_MS, f as clearOtherUserApps, i as LAUNCH_BUNDLING, k as verifyReleaseLaunch, m as devClientUrl, n as DEFAULT_METRO_PORT, o as LAUNCH_FATAL, s as LAUNCH_UNVERIFIED, v as installIosApp, w as readCollectorRecords, y as iosAppProcess } from "./app-install-DJg7bVv-.mjs";
17
+ import { f as listIosRuntimes } from "./ios-lEJ5pKsq.mjs";
18
+ import { a as lostLine, c as releaseLeaseOnSignal, i as leaseExpiryText, l as runLease, n as DEBUG_VERIFY_STEP_MS, o as lostRefusal, r as acquireRunLease, s as parseDeviceWait, t as selectFromPool, u as waitFlagConflict } from "./device-pool-DNSHL5z8.mjs";
19
+ import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-C-oAHRT1.mjs";
20
+ import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-BYcZ6n1h.mjs";
21
+ import { c as unknownIosRuntimeRefusal, i as ensureOwnedDevice, n as clearIosAdoptionPending, r as ensureBooted, s as unknownIosDeviceTypeRefusal, t as checkDeviceCapacity } from "./device-CLeDp-KH.mjs";
22
+ import { a as ensureMetroReachable, b as ownedSessionName, c as remoteIosDeps, l as resolveRemoteContext, n as binOnPath, o as ensureRemoteBootOwned, r as bundleEntryPoint, w as gateMetroOrigin } from "./device-remote-BprxZp-m.mjs";
23
+ import { a as compilationCacheActivityLine, c as readBundleExecutable, l as readBundleId, p as describeDiagnostic, r as buildIos, s as discoverXcodeProject, t as COMPILATION_CACHE_NOT_RUN, u as resolveScheme } from "./xcode-B6YaWeg0.mjs";
24
+ import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-DbLvyrup.mjs";
25
25
  import { t as spawnEntry } from "./spawn-entry-nCE6m770.mjs";
26
- import { a as printNativeCrashReport, n as IOS_CRASH_REPORT_RETRY, o as simulatorConsolePaths, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-B5STzxqN.mjs";
27
- import { a as iosDeviceProcess, c as listIosDevices, d as verifyIosDeviceReleaseLaunch, i as installIosDeviceApp, l as localNetworkPending, n as LAUNCH_PROBE_TIMEOUT_MS, o as iosPoolCandidates, r as awaitIosDeviceLaunch, s as iosPoolNoCandidatesRefusal, t as DEVICECTL_INSTALL_TIMEOUT_MS, u as resolveIosPhysicalDevice } from "./ios-device-DJOi9FR5.mjs";
28
- import { a as runPodInstall, n as podsAreStale, r as readPodState } from "./deps-C1zGnr61.mjs";
26
+ import { a as printNativeCrashReport, n as IOS_CRASH_REPORT_RETRY, o as simulatorConsolePaths, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-D1Kaf-eT.mjs";
27
+ import { a as iosDeviceProcess, c as listIosDevices, d as verifyIosDeviceReleaseLaunch, i as installIosDeviceApp, l as localNetworkPending, n as LAUNCH_PROBE_TIMEOUT_MS, o as iosPoolCandidates, r as awaitIosDeviceLaunch, s as iosPoolNoCandidatesRefusal, t as DEVICECTL_INSTALL_TIMEOUT_MS, u as resolveIosPhysicalDevice } from "./ios-device-CDbvn5H2.mjs";
28
+ import { a as runPodInstall, n as podsAreStale, r as readPodState } from "./deps-oCXVNEqQ.mjs";
29
29
  import { existsSync, mkdirSync, mkdtempSync, openSync, rmSync, writeFileSync } from "node:fs";
30
30
  import { InvalidArgumentError } from "commander";
31
31
  import { X509Certificate } from "node:crypto";
@@ -1503,14 +1503,14 @@ function hostLanCandidates(interfaces = networkInterfaces) {
1503
1503
  //#endregion
1504
1504
  //#region src/commands/ios/collector.ts
1505
1505
  function collectorLogFile(root, slot) {
1506
- return join(workspaceLogsDir(root), `collector-${deviceSlotKey("ios", slot)}.log`);
1506
+ return join(workspaceLogsDir(root), `collector-${deviceSlotFileKey("ios", slot)}.log`);
1507
1507
  }
1508
1508
  function collectorEntry() {
1509
1509
  return spawnEntry("collector-run");
1510
1510
  }
1511
1511
  const COLLECTOR_EXIT_WAIT_MS = 2e3;
1512
1512
  const COLLECTOR_POLL_MS = 25;
1513
- async function stopPreviousCollector({ root, slot = "default", kill = (pid, signal) => process.kill(pid, signal), alive = pidExists, readState = readWorkspaceState, verify = verifyCollectorOwnership, waitMs = COLLECTOR_EXIT_WAIT_MS, note = (_line) => {} }) {
1513
+ async function stopPreviousCollector({ root, slot = "default", kill = (pid, signal) => signalProcessTree(pid, signal), alive = pidExists, readState = readWorkspaceState, verify = verifyCollectorOwnership, waitMs = COLLECTOR_EXIT_WAIT_MS, note = (_line) => {} }) {
1514
1514
  const previous = (readState(root)?.collectors)?.[deviceSlotKey("ios", slot)] || null;
1515
1515
  const previousPid = Number(previous?.pid) || null;
1516
1516
  let killed = null;
@@ -2138,14 +2138,11 @@ async function finishIosRun({ artifact, d, root, slot, json, release, configurat
2138
2138
  event: "launch",
2139
2139
  msg: release ? `launched ${bundleId} on ${udid} (${configuration}, embedded JS bundle, no Metro)` : `launched ${bundleId} on ${udid} against Metro ${lanOriginUrl ?? `port ${metroPort}`}` + (launched?.mode === "openurl" || launched?.mode === "payload-url" ? " (expo-dev-client)" : "")
2140
2140
  });
2141
- if (remoteDevice) await d.replaceCollector({
2142
- root,
2143
- slot,
2144
- udid,
2145
- bundleId,
2146
- appName,
2147
- appExecutable,
2148
- note
2141
+ if (remoteDevice) logWriter().write({
2142
+ src: "build",
2143
+ level: "info",
2144
+ event: "collector_skipped",
2145
+ msg: `remote session ${udid}: device logs come from agent-device/EAS, so no simctl log stream is attached`
2149
2146
  });
2150
2147
  if (physical) raiseLeaseFor(release ? RELEASE_VERIFY_WAIT_MS : DEBUG_VERIFY_STEP_MS, false);
2151
2148
  const { state: launchState, warning: launchWarning } = await verifyIosRun({
@@ -2272,7 +2269,7 @@ function explicitSchemeRefusal(root, scheme, isExpo, d) {
2272
2269
  return d.resolveScheme(project, { scheme }).error ?? null;
2273
2270
  }
2274
2271
  function iosSlotLogFile(root, slot) {
2275
- return slot === "default" ? buildLogFile(root) : join(workspaceLogsDir(root), `build-${deviceSlotKey("ios", slot)}.ndjson`);
2272
+ return slot === "default" ? buildLogFile(root) : join(workspaceLogsDir(root), `build-${deviceSlotFileKey("ios", slot)}.ndjson`);
2276
2273
  }
2277
2274
  function iosSlotDeps(d, slot) {
2278
2275
  if (slot !== "default") {
@@ -1,5 +1,5 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { r as INSTALL_ERROR } from "./app-install--PAocWvq.mjs";
1
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
+ import { r as INSTALL_ERROR } from "./app-install-DJg7bVv-.mjs";
3
3
  import { mkdtempSync, readFileSync, rmSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
@@ -1,4 +1,4 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
1
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
2
  //#region src/collector/ios-device.ts
3
3
  const CONSOLE_ENV = { OS_ACTIVITY_DT_MODE: "enable" };
4
4
  const DEV_MENU_LAUNCH_ARGS = [
@@ -1,5 +1,5 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
1
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
+ import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-Dl5uKE03.mjs";
3
3
  import { mkdtempSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
@@ -22,7 +22,7 @@ function hostMemoryPressureAdvice(pressure) {
22
22
  return `macOS reports ${pressure} host memory pressure. Simulator processes can stall even when the device is Booted. Free memory before retrying: use \`stim stop\` only in workspaces you own, and ask before closing other apps or devices. For parallel iOS work, consider a reviewed SimSlim profile (\`stim guide lifecycle simslim\`). This observation does not establish an OOM crash.`;
23
23
  }
24
24
  //#endregion
25
- //#region src/sim/ios.ts
25
+ //#region src/devices/ios.ts
26
26
  function parseSimctlList(jsonOutput, { includeUnavailable = false } = {}) {
27
27
  const parsed = JSON.parse(jsonOutput);
28
28
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Expected simctl list to return a JSON object with a devices object.");
@@ -1,10 +1,10 @@
1
- import { g as loadConfig, h as isPathPrefix, z as validateDeviceSlot } from "./config-DMP7L7H6.mjs";
1
+ import { B as validateDeviceSlot, g as loadConfig, h as isPathPrefix } from "./config-B7iyKWkL.mjs";
2
2
  import { n as SOURCES, t as LEVELS } from "./ndjson-BZwI1NBv.mjs";
3
- import { _ as workspaceLogsDir, r as readWorkspaceState } from "./workspace-state-WVwWw_hB.mjs";
4
- import { a as findProjectRoot } from "./project-B_SDt4ip.mjs";
5
- import { _ as launchErrorPreview } from "./command-output-BuIb_ZOI.mjs";
6
- import { i as captureWorkspaceCrashes, t as errorDiagnostics } from "./error-diagnostics-B5STzxqN.mjs";
7
- import { a as followLogs, c as queryLogs, i as fileSizes, n as buildCriteria, o as logFiles, r as compileGrep, s as parseSince } from "./logs-query-Cdp5aaCH.mjs";
3
+ import { _ as workspaceLogsDir, r as readWorkspaceState } from "./workspace-state-BT5E3SA2.mjs";
4
+ import { a as findProjectRoot } from "./project-DDZWf_16.mjs";
5
+ import { _ as launchErrorPreview } from "./command-output-B3ycpsYv.mjs";
6
+ import { i as captureWorkspaceCrashes, t as errorDiagnostics } from "./error-diagnostics-D1Kaf-eT.mjs";
7
+ import { a as followLogs, c as queryLogs, i as fileSizes, n as buildCriteria, o as logFiles, r as compileGrep, s as parseSince } from "./logs-query-BGL-Pl12.mjs";
8
8
  import { realpathSync } from "node:fs";
9
9
  import { relative, sep } from "node:path";
10
10
  import chalk from "chalk";
@@ -2,7 +2,7 @@ import { a as parseNdjsonLine, i as levelRank, o as parseNdjsonText } from "./nd
2
2
  import { closeSync, openSync, readFileSync, readSync, readdirSync, statSync } from "fs";
3
3
  import { join } from "path";
4
4
  import { StringDecoder } from "string_decoder";
5
- //#region src/logs-query.ts
5
+ //#region src/diagnostics/logs-query.ts
6
6
  const SINCE_UNITS = {
7
7
  s: 1e3,
8
8
  m: 6e4,
@@ -0,0 +1,233 @@
1
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
+ import { A as isOnMountedVolume, g as loadConfig, j as listMountedVolumes, n as allMetroPorts, r as claimMetroPort, v as removeProject, w as process_identity_exports } from "./config-B7iyKWkL.mjs";
3
+ import { r as readWorkspaceState } from "./workspace-state-BT5E3SA2.mjs";
4
+ import { existsSync, readlinkSync, realpathSync } from "fs";
5
+ import { sep } from "path";
6
+ import { request } from "http";
7
+ import { connect } from "net";
8
+ //#region src/ports.ts
9
+ function isMetroRunning(port) {
10
+ return new Promise((resolve) => {
11
+ const req = request({
12
+ hostname: "localhost",
13
+ port,
14
+ path: "/status",
15
+ timeout: 2e3
16
+ }, (res) => {
17
+ let data = "";
18
+ res.on("data", (chunk) => {
19
+ data += chunk;
20
+ });
21
+ res.on("end", () => resolve(data.includes("packager-status:running")));
22
+ });
23
+ req.on("error", () => resolve(false));
24
+ req.on("timeout", () => {
25
+ req.destroy();
26
+ resolve(false);
27
+ });
28
+ req.end();
29
+ });
30
+ }
31
+ function isPortFree(port, { timeoutMs = 400 } = {}) {
32
+ return new Promise((resolve) => {
33
+ const sock = connect({
34
+ port,
35
+ host: "127.0.0.1"
36
+ });
37
+ const done = (free) => {
38
+ sock.removeAllListeners();
39
+ sock.destroy();
40
+ resolve(free);
41
+ };
42
+ sock.setTimeout(timeoutMs);
43
+ sock.once("connect", () => done(false));
44
+ sock.once("error", () => done(true));
45
+ sock.once("timeout", () => done(false));
46
+ });
47
+ }
48
+ const FIRST_PORT = 8082;
49
+ async function computeNextPort(isFree = isPortFree) {
50
+ const taken = new Set(allMetroPorts());
51
+ for (let port = FIRST_PORT; port < 8282; port++) {
52
+ if (taken.has(port)) continue;
53
+ if (await isFree(port)) return port;
54
+ }
55
+ 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>\`).`);
56
+ }
57
+ async function findReclaimablePort(excludeProjectPath, probe = isMetroRunning, { isMounted = isOnMountedVolume, mountedVolumes } = {}) {
58
+ const cfg = loadConfig();
59
+ if (!cfg?.projects) return null;
60
+ const mounted = mountedVolumes || listMountedVolumes();
61
+ const candidates = [];
62
+ for (const [path, proj] of Object.entries(cfg.projects)) {
63
+ if (path === excludeProjectPath) continue;
64
+ if (Object.keys(proj.ports ?? {}).length) continue;
65
+ if (existsSync(path)) continue;
66
+ if (!isMounted(path, mounted)) continue;
67
+ if (typeof proj.metroPort === "number") candidates.push({
68
+ port: proj.metroPort,
69
+ ownerPath: path
70
+ });
71
+ }
72
+ for (const c of candidates) if (!await probe(c.port)) return c;
73
+ return null;
74
+ }
75
+ async function allocatePort(projectPath, probe = isMetroRunning, isFree = isPortFree) {
76
+ const reclaim = await findReclaimablePort(projectPath, probe);
77
+ if (reclaim && await isFree(reclaim.port)) {
78
+ removeProject(reclaim.ownerPath);
79
+ return reclaim.port;
80
+ }
81
+ return computeNextPort(isFree);
82
+ }
83
+ const RESERVE_ATTEMPTS = 5;
84
+ async function reserveMetroPort(projectPath, probe = isMetroRunning, isFree = isPortFree) {
85
+ for (let attempt = 0; attempt < RESERVE_ATTEMPTS; attempt++) {
86
+ const port = await allocatePort(projectPath, probe, isFree);
87
+ const claimed = claimMetroPort(projectPath, port);
88
+ if (claimed !== null) return claimed;
89
+ }
90
+ throw new Error(`Could not reserve a Metro port after ${RESERVE_ATTEMPTS} attempts: another Stim run claimed each one first. Retry.`);
91
+ }
92
+ //#endregion
93
+ //#region src/metro.ts
94
+ /**
95
+ * True when some process with this pid exists right now. It cannot tell a recycled pid from the
96
+ * process that first held it, so it is only valid for a pid this run spawned and still holds a
97
+ * handle on. For a pid read from a record on disk, decide with `inspectProcessIdentity` on that
98
+ * record's process token, or take an ownership claim (`ownership-claim.ts`): this answer would keep
99
+ * a dead holder's record looking live until some unrelated process exits.
100
+ */
101
+ function pidExists(pid) {
102
+ if (!Number.isSafeInteger(pid) || pid <= 0) return false;
103
+ try {
104
+ process.kill(pid, 0);
105
+ return true;
106
+ } catch (error) {
107
+ return error.code !== "ESRCH";
108
+ }
109
+ }
110
+ function parseLsofPids(out) {
111
+ if (!out) return [];
112
+ return String(out).split("\n").map((s) => parseInt(s.trim(), 10)).filter((n) => Number.isFinite(n));
113
+ }
114
+ function addressPort(address) {
115
+ const colon = String(address ?? "").lastIndexOf(":");
116
+ return colon < 0 ? NaN : Number(String(address).slice(colon + 1));
117
+ }
118
+ function parseNetstatPids(out, port) {
119
+ const pids = [];
120
+ for (const line of String(out ?? "").split("\n")) {
121
+ const cols = line.trim().split(/\s+/);
122
+ if (cols.length < 5 || cols[0]?.toUpperCase() !== "TCP") continue;
123
+ if (addressPort(cols[1]) !== port || addressPort(cols[2]) !== 0) continue;
124
+ const pid = Number(cols.at(-1));
125
+ if (Number.isSafeInteger(pid) && pid > 0 && !pids.includes(pid)) pids.push(pid);
126
+ }
127
+ return pids;
128
+ }
129
+ function listeningPids(port, platform = process.platform) {
130
+ const pids = parseLsofPids(getExecutor().runQuiet(`lsof -nP -iTCP:${port} -sTCP:LISTEN -t`));
131
+ if (pids.length > 0 || platform !== "win32") return pids;
132
+ return parseNetstatPids(getExecutor().runQuiet("netstat -ano"), port);
133
+ }
134
+ function parseLsofCwd(out) {
135
+ if (!out) return null;
136
+ const lines = String(out).split("\n");
137
+ const idx = lines.findIndex((l) => l === "fcwd");
138
+ if (idx === -1) return null;
139
+ const nLine = lines.slice(idx + 1).find((l) => l.startsWith("n"));
140
+ return nLine ? nLine.slice(1) : null;
141
+ }
142
+ function processCwd(pid) {
143
+ if (process.platform === "linux") try {
144
+ return readlinkSync(`/proc/${pid}/cwd`);
145
+ } catch {}
146
+ return parseLsofCwd(getExecutor().runQuiet(`lsof -a -p ${pid} -d cwd -Fn`));
147
+ }
148
+ function canonicalPath(path) {
149
+ try {
150
+ return realpathSync(path);
151
+ } catch {
152
+ return path;
153
+ }
154
+ }
155
+ function isInsideProject(cwd, projectPath) {
156
+ if (!cwd || !projectPath) return false;
157
+ const a = canonicalPath(cwd);
158
+ const b = canonicalPath(projectPath);
159
+ return a === b || a.startsWith(b.endsWith(sep) ? b : b + sep);
160
+ }
161
+ const NOT_OURS_UNRESPONSIVE = "unresponsive";
162
+ const NOT_OURS_UNREADABLE_CWD = "unreadable-cwd";
163
+ const NOT_OURS_FOREIGN_CWD = "foreign-cwd";
164
+ function ownedSupervisor(projectPath, port) {
165
+ const supervisor = readWorkspaceState(canonicalPath(projectPath))?.supervisor;
166
+ if (supervisor?.port !== port || (0, process_identity_exports.inspectProcessIdentity)(supervisor) !== "same") return null;
167
+ return supervisor;
168
+ }
169
+ async function resolveProjectMetro(port, projectPath, { probe = isMetroRunning, cwdOf = processCwd } = {}) {
170
+ const pid = listeningPids(port)[0];
171
+ if (pid === void 0) return { missing: true };
172
+ if (!await probe(port)) return {
173
+ notOurs: `pid ${pid} on port ${port} does not answer Metro's /status`,
174
+ kind: NOT_OURS_UNRESPONSIVE,
175
+ pid
176
+ };
177
+ const cwd = cwdOf(pid);
178
+ if (!cwd) {
179
+ const owner = ownedSupervisor(projectPath, port);
180
+ if (owner && (pid === owner.pid || pid === owner.serverPid)) return { metro: {
181
+ pid,
182
+ leader: owner.pid,
183
+ cwd: canonicalPath(projectPath),
184
+ processToken: owner.processToken
185
+ } };
186
+ return {
187
+ notOurs: `pid ${pid} on port ${port}: working directory could not be read`,
188
+ kind: NOT_OURS_UNREADABLE_CWD,
189
+ pid
190
+ };
191
+ }
192
+ if (!isInsideProject(cwd, projectPath)) return {
193
+ notOurs: `pid ${pid} on port ${port} runs from ${cwd}, outside ${projectPath}`,
194
+ kind: NOT_OURS_FOREIGN_CWD,
195
+ pid
196
+ };
197
+ const owner = ownedSupervisor(projectPath, port);
198
+ return { metro: {
199
+ pid,
200
+ leader: owner ? owner.pid : pid,
201
+ cwd,
202
+ ...owner ? { processToken: owner.processToken } : {}
203
+ } };
204
+ }
205
+ /**
206
+ * Terminate a process and everything it spawned. On POSIX `group` signals the process group, which
207
+ * is what a detached leader owns. Windows has no process groups, and its SIGTERM is
208
+ * TerminateProcess: no handler runs and no child is reached, so a spawned dev server or `adb
209
+ * logcat` would outlive its parent. taskkill /T is the only route to the tree there, and it is
210
+ * always forceful.
211
+ */
212
+ function signalProcessTree(pid, signal = "SIGTERM", { group = false, platform = process.platform } = {}) {
213
+ if (platform !== "win32") return process.kill(group ? -pid : pid, signal);
214
+ return getExecutor().runFileQuiet("taskkill", [
215
+ "/PID",
216
+ String(pid),
217
+ "/T",
218
+ "/F"
219
+ ]) !== null;
220
+ }
221
+ function killMetroTree(leader, processToken) {
222
+ if (!leader || leader === process.pid || (0, process_identity_exports.inspectProcessIdentity)({
223
+ pid: leader,
224
+ processToken
225
+ }) !== "same") return false;
226
+ try {
227
+ return signalProcessTree(leader, "SIGTERM", { group: true });
228
+ } catch {
229
+ return false;
230
+ }
231
+ }
232
+ //#endregion
233
+ export { resolveProjectMetro as a, isPortFree as c, pidExists as i, reserveMetroPort as l, killMetroTree as n, signalProcessTree as o, listeningPids as r, isMetroRunning as s, NOT_OURS_FOREIGN_CWD as t };
@@ -1,7 +1,7 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { C as withConfigLock, f as getProject, g as loadConfig, s as ensureConfig, u as getConfigDir, w as process_identity_exports, y as saveConfig } from "./config-DMP7L7H6.mjs";
3
- import { n as isPortFree } from "./ports-BZprTJRa.mjs";
4
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-Bz19_R9R.mjs";
1
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
+ import { C as withConfigLock, f as getProject, g as loadConfig, s as ensureConfig, u as getConfigDir, w as process_identity_exports, y as saveConfig } from "./config-B7iyKWkL.mjs";
3
+ import { c as isPortFree, o as signalProcessTree, r as listeningPids } from "./metro-C7bcJ0uo.mjs";
4
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BYfZz_S-.mjs";
5
5
  import { realpathSync } from "node:fs";
6
6
  //#region src/named-ports.ts
7
7
  const FIRST_PORT = 8900;
@@ -10,7 +10,8 @@ function validatePortLabel(label) {
10
10
  if (label === "metro") throw new Error("metro is managed by stim start and stim stop; choose another label.");
11
11
  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.");
12
12
  }
13
- function portListeners(port) {
13
+ function portListeners(port, platform) {
14
+ if (platform === "win32") return listeningPids(port, platform);
14
15
  let out;
15
16
  try {
16
17
  out = getExecutor().runFile("lsof", [
@@ -28,10 +29,29 @@ function portListeners(port) {
28
29
  if (pids.some((pid) => !Number.isSafeInteger(pid) || pid <= 0)) throw new Error(`lsof returned an invalid listener for TCP port ${port}.`);
29
30
  return [...new Set(pids)];
30
31
  }
32
+ function processCommand(pid, platform) {
33
+ const e = getExecutor();
34
+ if (platform === "win32") {
35
+ const csv = e.runFileQuiet("tasklist", [
36
+ "/FI",
37
+ `PID eq ${pid}`,
38
+ "/FO",
39
+ "CSV",
40
+ "/NH"
41
+ ], { timeoutMs: 5e3 });
42
+ return /^"([^"]*)"/.exec((csv ?? "").trim())?.[1] ?? "";
43
+ }
44
+ return (e.runFileQuiet("ps", [
45
+ "-p",
46
+ String(pid),
47
+ "-o",
48
+ "args="
49
+ ], { timeoutMs: 5e3 }) ?? "").trim();
50
+ }
31
51
  function withPortsLock(fn) {
32
52
  return withWorkspaceProcessLock(getConfigDir(), "ports", fn, { external: true });
33
53
  }
34
- async function getNamedPort(projectPath, label, { isFree = async (port) => await isPortFree(port) && portListeners(port).length === 0, log = console.error } = {}) {
54
+ async function getNamedPort(projectPath, label, { isFree = async (port) => await isPortFree(port) && portListeners(port, process.platform).length === 0, log = console.error } = {}) {
35
55
  validatePortLabel(label);
36
56
  const root = realpathSync(projectPath);
37
57
  return withPortsLock(async () => {
@@ -61,37 +81,33 @@ async function getNamedPort(projectPath, label, { isFree = async (port) => await
61
81
  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.`);
62
82
  });
63
83
  }
64
- async function stopListeners(port, label, dryRun, log) {
65
- for (const pid of portListeners(port)) {
84
+ async function stopListeners(port, label, dryRun, log, platform) {
85
+ const listeners = () => portListeners(port, platform);
86
+ for (const pid of listeners()) {
66
87
  if (pid === process.pid) throw new Error(`Refusing to stop Stim itself on ${label} (${port}).`);
67
88
  const identity = (0, process_identity_exports.captureProcessIdentity)(pid);
68
89
  if (!identity.ok) {
69
- if (!portListeners(port).includes(pid)) continue;
90
+ if (!listeners().includes(pid)) continue;
70
91
  throw new Error(`Cannot identify pid ${pid} on ${label} (${port}): ${identity.reason}`);
71
92
  }
72
93
  const record = {
73
94
  pid,
74
95
  processToken: identity.token
75
96
  };
76
- const command = getExecutor().runFile("ps", [
77
- "-p",
78
- String(pid),
79
- "-o",
80
- "args="
81
- ], { timeoutMs: 5e3 }).trim();
97
+ const command = processCommand(pid, platform);
82
98
  if (dryRun) {
83
99
  log(`would stop ${label} (${port}): pid ${pid} ${command}`);
84
100
  continue;
85
101
  }
86
- if (!portListeners(port).includes(pid)) continue;
102
+ if (!listeners().includes(pid)) continue;
87
103
  const status = (0, process_identity_exports.inspectProcessIdentity)(record);
88
104
  if (status === "gone" || status === "different") continue;
89
105
  if (status !== "same") throw new Error(`Cannot verify pid ${pid} on ${label} (${port}); allocation kept.`);
90
106
  try {
91
- process.kill(pid, "SIGTERM");
107
+ signalProcessTree(pid, "SIGTERM", { platform });
92
108
  if (!await (0, process_identity_exports.waitForProcessExit)(record, 2e3)) {
93
109
  if ((0, process_identity_exports.inspectProcessIdentity)(record) !== "same") throw new Error(`Cannot verify pid ${pid} before SIGKILL.`);
94
- process.kill(pid, "SIGKILL");
110
+ signalProcessTree(pid, "SIGKILL", { platform });
95
111
  if (!await (0, process_identity_exports.waitForProcessExit)(record, 2e3)) throw new Error(`Pid ${pid} did not exit.`);
96
112
  }
97
113
  } catch (error) {
@@ -99,36 +115,35 @@ async function stopListeners(port, label, dryRun, log) {
99
115
  }
100
116
  log(`stopped ${label} (${port}): pid ${pid} ${command}`);
101
117
  }
102
- if (!dryRun && portListeners(port).length) throw new Error(`Port ${port} still has a listener; allocation kept.`);
118
+ if (!dryRun && listeners().length) throw new Error(`Port ${port} still has a listener; allocation kept.`);
103
119
  }
104
- async function clearNamedPorts(projectPath, { label, stop = false, dryRun = false, log = console.error } = {}) {
120
+ async function clearNamedPorts(projectPath, { label, stop = false, dryRun = false, log = console.error, platform = process.platform } = {}) {
105
121
  if (label !== void 0) validatePortLabel(label);
106
122
  await withPortsLock(async () => {
107
123
  const ports = getProject(projectPath)?.ports ?? {};
124
+ const selected = Object.entries(ports).filter(([name]) => label === void 0 || name === label);
125
+ if (stop && selected.length > 0 && platform !== "win32" && !getExecutor().findExecutable("lsof")) throw new Error("Cannot stop named ports: lsof is not installed.");
108
126
  const failures = [];
109
- for (const [name, port] of Object.entries(ports)) {
110
- if (label !== void 0 && name !== label) continue;
111
- try {
112
- if (!Number.isInteger(port) || port < FIRST_PORT || port > LAST_PORT) throw new Error(`Invalid named port ${name} (${port}); repair its config record before cleanup.`);
113
- if (stop) {
114
- if (Object.values(loadConfig()?.projects ?? {}).some((project) => project.metroPort === port)) throw new Error(`Port ${port} is reserved for managed Metro; allocation kept.`);
115
- await stopListeners(port, name, dryRun, log);
116
- }
117
- if (dryRun) {
118
- log(`would release ${name} (${port})`);
119
- continue;
120
- }
121
- withConfigLock(() => {
122
- const cfg = loadConfig();
123
- const current = cfg?.projects?.[projectPath]?.ports;
124
- if (!cfg || current?.[name] !== port) return;
125
- delete current[name];
126
- saveConfig(cfg);
127
- });
128
- log(`released ${name} (${port})`);
129
- } catch (error) {
130
- failures.push(`${name} (${port}): ${error.message}`);
127
+ for (const [name, port] of selected) try {
128
+ if (!Number.isInteger(port) || port < FIRST_PORT || port > LAST_PORT) throw new Error(`Invalid named port ${name} (${port}); repair its config record before cleanup.`);
129
+ if (stop) {
130
+ if (Object.values(loadConfig()?.projects ?? {}).some((project) => project.metroPort === port)) throw new Error(`Port ${port} is reserved for managed Metro; allocation kept.`);
131
+ await stopListeners(port, name, dryRun, log, platform);
131
132
  }
133
+ if (dryRun) {
134
+ log(`would release ${name} (${port})`);
135
+ continue;
136
+ }
137
+ withConfigLock(() => {
138
+ const cfg = loadConfig();
139
+ const current = cfg?.projects?.[projectPath]?.ports;
140
+ if (!cfg || current?.[name] !== port) return;
141
+ delete current[name];
142
+ saveConfig(cfg);
143
+ });
144
+ log(`released ${name} (${port})`);
145
+ } catch (error) {
146
+ failures.push(`${name} (${port}): ${error.message}`);
132
147
  }
133
148
  if (failures.length) throw new Error(failures.join("\n"));
134
149
  });
@@ -1,6 +1,6 @@
1
- import { w as process_identity_exports } from "./config-DMP7L7H6.mjs";
2
- import { a as withWorkspaceStateLock, o as writeWorkspaceState, r as readWorkspaceState } from "./workspace-state-WVwWw_hB.mjs";
3
- import { r as pidExists } from "./metro-ato6ef3J.mjs";
1
+ import { w as process_identity_exports } from "./config-B7iyKWkL.mjs";
2
+ import { a as withWorkspaceStateLock, o as writeWorkspaceState, r as readWorkspaceState } from "./workspace-state-BT5E3SA2.mjs";
3
+ import { i as pidExists } from "./metro-C7bcJ0uo.mjs";
4
4
  import { realpathSync } from "node:fs";
5
5
  //#region src/collector/state.ts
6
6
  function collectorRoot(root) {