stim 1.13.0 → 1.15.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 (133) hide show
  1. package/README.md +2 -0
  2. package/dist/{activity-DfVKZTEB.mjs → activity-B2-kb7k0.mjs} +281 -41
  3. package/dist/{android-BHfAu2mf.mjs → android-CYmq0jNi.mjs} +2 -2
  4. package/dist/{android-CD8pr8Q2.mjs → android-Cu_L4RpL.mjs} +524 -412
  5. package/dist/{android-qx_31wJo.mjs → android-Db2eEMaf.mjs} +152 -65
  6. package/dist/android-cas-PnKfAdB2.mjs +167 -0
  7. package/dist/android-cas-compiler.mjs +2 -2
  8. package/dist/{app-install-CDFY2nUL.mjs → app-install-CAAplXAt.mjs} +162 -173
  9. package/dist/{budget-E5o91NR1.mjs → budget-CMiqghig.mjs} +1048 -77
  10. package/dist/{build-plan-BTa93o_r.mjs → build-plan-aeGMFX7e.mjs} +193 -650
  11. package/dist/build-progress-C2Jv1AtA.mjs +872 -0
  12. package/dist/build-slots-Df1ApSuH.mjs +127 -0
  13. package/dist/{cache-manifest-DWPF4RKJ.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
  14. package/dist/cache-manifest.mjs +1 -1
  15. package/dist/{chrome-B5hWgQJS.mjs → chrome-BrcVWQyD.mjs} +4 -4
  16. package/dist/cli-0cX5s4NM.mjs +45 -0
  17. package/dist/cli.mjs +6 -42
  18. package/dist/client-C2IS36Qf.mjs +1333 -0
  19. package/dist/collector-run.mjs +8 -7
  20. package/dist/{command-output-BIL7rlX7.mjs → command-output-CliHHn7a.mjs} +1 -1
  21. package/dist/{config-0jCcMM0E.mjs → config-D0DbpZtM.mjs} +29 -29
  22. package/dist/{created-devices-Cd558h31.mjs → created-devices-BwVb3liH.mjs} +2 -2
  23. package/dist/{dependency-state-DNN0yfx6.mjs → dependency-state-BmsjKZ2H.mjs} +2 -2
  24. package/dist/{deps-2Wo81np9.mjs → deps-BFlKbOIp.mjs} +3 -3
  25. package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
  26. package/dist/dev-client-CjzLoNs0.mjs +80 -0
  27. package/dist/{device-Cf7vl-so.mjs → device-BZ1IyS5A.mjs} +7 -7
  28. package/dist/device-capacity-DMuKC7nx.mjs +136 -0
  29. package/dist/device-host-worker.d.mts +1 -0
  30. package/dist/device-host-worker.mjs +769 -0
  31. package/dist/device-ios-DMQBUPus.mjs +381 -0
  32. package/dist/{device-lease-CL9dZf73.mjs → device-lease-W5Pi1q38.mjs} +43 -36
  33. package/dist/{device-pool-MuoiyRcu.mjs → device-pool-B2r38YjS.mjs} +4 -4
  34. package/dist/{device-remote-DT9ngZiD.mjs → device-remote-9ulPsdhY.mjs} +68 -162
  35. package/dist/{doctor-D2iysNNH.mjs → doctor-BcE5xsBn.mjs} +191 -99
  36. package/dist/{error-diagnostics-BoiOhck8.mjs → error-diagnostics-Cb0P1WLS.mjs} +12 -11
  37. package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
  38. package/dist/{gc-eA-PRBX8.mjs → gc-BuL1Genr.mjs} +430 -102
  39. package/dist/{android-cas-B9Kv9aIy.mjs → gradle-CmdOBq1d.mjs} +8 -294
  40. package/dist/{guide-BfZ0OmiP.mjs → guide-cCSGRFd3.mjs} +2052 -240
  41. package/dist/{guide-status-BaMYu55M.mjs → guide-status-CIoWK6mG.mjs} +3 -3
  42. package/dist/hosted-macos-DwowNU7W.mjs +447 -0
  43. package/dist/idle-shutdown-B5LPaNyY.mjs +122 -0
  44. package/dist/in-use-CndPMilN.mjs +110 -0
  45. package/dist/{ios-CmEzZiOj.mjs → ios-3CtCRhwr.mjs} +201 -10
  46. package/dist/{ios-CcfCeZ19.mjs → ios-Chob8ny7.mjs} +560 -259
  47. package/dist/{ios-device-BW-t_Lkv.mjs → ios-device-DERmMmk-.mjs} +12 -7
  48. package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
  49. package/dist/{launch-verify-CuJaBpEa.mjs → launch-verify-CLlofxRt.mjs} +42 -11
  50. package/dist/{logs-DcSRbC2t.mjs → logs-h9h6MUvE.mjs} +95 -13
  51. package/dist/machine-usage-D_YYMzfR.mjs +283 -0
  52. package/dist/machines-CYtOCqcj.mjs +259 -0
  53. package/dist/macos-BwtbUtTN.mjs +468 -0
  54. package/dist/macos-run.d.mts +5 -0
  55. package/dist/macos-run.mjs +2 -0
  56. package/dist/{merge-state-ytFQCeh4.mjs → merge-state-BnxLNQiM.mjs} +22 -12
  57. package/dist/{metro-BPeY1YMh.mjs → metro-Di8EN2Tb.mjs} +20 -120
  58. package/dist/{named-ports-n7tnJsI8.mjs → named-ports-Dj_XBNaL.mjs} +8 -64
  59. package/dist/native-run-DFDoIRbY.mjs +147 -0
  60. package/dist/{native-runtime-CiiFHt0e.mjs → native-runtime-i6OkVg-w.mjs} +11 -6
  61. package/dist/offload-worker.d.mts +121 -0
  62. package/dist/offload-worker.mjs +601 -0
  63. package/dist/{ownership-Bz2kETAS.mjs → ownership-BkYxUtuj.mjs} +3 -2
  64. package/dist/{page-BTu7I60K.mjs → page-QF_Ke10s.mjs} +7 -2
  65. package/dist/{ports-C5UutqpJ.mjs → ports-DddwttKM.mjs} +3 -3
  66. package/dist/prebuild-BvxDlD6C.mjs +213 -0
  67. package/dist/process-identity-BmRm1qV0.mjs +12 -0
  68. package/dist/{project-DGeclhvO.mjs → project-CV3tDFHl.mjs} +9 -18
  69. package/dist/pull-request-pxnDCQzn.mjs +322 -0
  70. package/dist/pull-requests.d.mts +120 -0
  71. package/dist/pull-requests.mjs +2 -0
  72. package/dist/recordings-GEXp4iBT.mjs +37 -0
  73. package/dist/{reload-C-cV5yy_.mjs → reload-ChPiwvot.mjs} +70 -22
  74. package/dist/{remote-cache--aehr4Pr.mjs → remote-cache-CO2elm_b.mjs} +4 -4
  75. package/dist/run-B-xqRrsg.mjs +119 -0
  76. package/dist/{server-bare-Bq8BpSIo.mjs → server-bare-X7PUgizL.mjs} +60 -13
  77. package/dist/{server-expo-DUCg52-K.mjs → server-expo-DhtX6bom.mjs} +6 -10
  78. package/dist/server-expo-KJeD109I.mjs +2 -0
  79. package/dist/{settings-xB0Frkg6.mjs → settings-CduVcoxj.mjs} +77 -26
  80. package/dist/{settings-ssVBToDR.mjs → settings-DbL6uhH8.mjs} +16 -10
  81. package/dist/settings.schema.json +357 -2
  82. package/dist/{simslim-Bbg9UWrt.mjs → simslim-Ciluxlq4.mjs} +8 -132
  83. package/dist/slot-launch-BY0BrOLh.mjs +65 -0
  84. package/dist/{spawn-claims-PT8aQpCa.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
  85. package/dist/spawn-entry-DncpJvWL.mjs +16 -0
  86. package/dist/stage-CzaiwFsI.mjs +143 -0
  87. package/dist/start-CPO4wRg7.mjs +2 -0
  88. package/dist/{start-BZhItkiL.mjs → start-CUD9nasA.mjs} +108 -61
  89. package/dist/state-B4PQFXMW.d.mts +12 -0
  90. package/dist/state-BxCgeTQu.mjs +41 -0
  91. package/dist/{state-DwAU2_-D.mjs → state-CaeaHbDA.mjs} +7 -4
  92. package/dist/{state-l1pYwOre.mjs → state-DFm77wRn.mjs} +67 -5
  93. package/dist/stats-DBLLFmKd.mjs +66 -0
  94. package/dist/{status-TJlL0HWo.mjs → status-DYXxNOT0.mjs} +425 -260
  95. package/dist/{status-sszP4XKU.mjs → status-l84Gbvp2.mjs} +122 -20
  96. package/dist/stdout-reader-C8yjre1x.mjs +23 -0
  97. package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
  98. package/dist/stim-footprint.swift +32 -0
  99. package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
  100. package/dist/{stop-BZ2qMiBP.mjs → stop-Bad61z_2.mjs} +128 -19
  101. package/dist/stop-Bvf1MflS.mjs +48 -0
  102. package/dist/{stop-DskDKNTU.mjs → stop-CceYh1yG.mjs} +2 -2
  103. package/dist/stop-cause-CZ_Mr-v9.mjs +665 -0
  104. package/dist/supervisor-run.d.mts +44 -12
  105. package/dist/supervisor-run.mjs +276 -33
  106. package/dist/support-CDQbDkL5.mjs +315 -0
  107. package/dist/support-WLOzVKUv.mjs +176 -0
  108. package/dist/teardown-BYuQafiA.mjs +718 -0
  109. package/dist/{xcode-LM6A8cNZ.mjs → toolchain-DmeYjXD9.mjs} +363 -13
  110. package/dist/warm-progress-DVHLN4uC.mjs +463 -0
  111. package/dist/{idle-DxLi5OkN.mjs → watchman-Bx0wceoF.mjs} +55 -8
  112. package/dist/{web-sWXd5iek.mjs → web-k6w8-HnE.mjs} +39 -22
  113. package/dist/web-run.mjs +326 -13
  114. package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
  115. package/dist/workspace-state-CL2V2Ons.mjs +371 -0
  116. package/dist/worktree-BT_uULFx.mjs +2 -0
  117. package/dist/{worktree-DeBQSVtR.mjs → worktree-C0c5Udwa.mjs} +55 -54
  118. package/dist/{worktree-D4Do_XiQ.mjs → worktree-qXKzADSC.mjs} +161 -255
  119. package/package.json +10 -5
  120. package/shim/bundle-response.cjs +55 -10
  121. package/shim/bundle-response.d.cts +1 -0
  122. package/shim/expo-metro-config.cjs +10 -2
  123. package/dist/build-progress-l7d-X-ZP.mjs +0 -247
  124. package/dist/in-use-wYEaKQM9.mjs +0 -249
  125. package/dist/ownership-PUP4Dwai.mjs +0 -1138
  126. package/dist/server-expo-BeYwP_Xy.mjs +0 -2
  127. package/dist/slot-launch-BIWEW-wI.mjs +0 -19
  128. package/dist/start-Rw2GD3nV.mjs +0 -2
  129. package/dist/stats-CaSwmsSZ.mjs +0 -62
  130. package/dist/stats-D8rUTPHd.mjs +0 -368
  131. package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
  132. package/dist/worktree-3cBa-bQt.mjs +0 -2
  133. package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
@@ -1,29 +1,37 @@
1
- import { A as ownership_claim_exports, O as process_identity_exports, T as upsertProject, U as projectDeviceSlots, _ as loadConfig, a as clearDevice, d as getConfigDir, g as isPathPrefix, k as acquireAvdClaim, p as getProject, x as removeProject } from "./config-0jCcMM0E.mjs";
2
- import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
3
- import { a as pidExists, c as resolveProjectMetro, g as recordWorkspaceUse, h as readWorkspaceState, k as workspaceDir, l as signalProcessTree, n as killMetroTree } from "./metro-BPeY1YMh.mjs";
4
- import { C as sourceCheckoutOf, S as restoreFile, T as warmWorktreePaths, _ as readWorktreeExclude, b as resolveFullRef, d as hasUncommittedWork, f as isPodInstallChurn, g as podsOutOfSync, h as locallyKnownUpstream, i as depsOutOfSync, l as hasPopulatedSubmodules, m as listWorktrees, n as cloneIgnoredEntries, o as dirtyPaths, r as deleteBranch, s as gitCommonDir, t as branchExists, u as hasRemote, v as removeWorktree, w as unpushedCommits } from "./worktree-DeBQSVtR.mjs";
5
- import { a as findProjectRoot } from "./project-DGeclhvO.mjs";
6
- import { a as clearRemoteSession, c as readMetroTunnel, d as readRemoteSessionId, i as clearManagedMetroTunnel, u as readRemoteSession } from "./state-DwAU2_-D.mjs";
7
- import { t as clearNamedPorts } from "./named-ports-n7tnJsI8.mjs";
8
- import { a as formatElapsed, h as shortUdid, i as formatDuration, l as phaseLine, p as releasedLeaseFact, u as plural } from "./command-output-BIL7rlX7.mjs";
9
- import { u as releaseWorkspaceLeases } from "./device-lease-CL9dZf73.mjs";
10
- import { ct as parkedMaxSetting, rt as POOL_SETTING_REMEDY } from "./android-qx_31wJo.mjs";
11
- import { L as unknownSettingKeys, P as settingShapeErrors, a as SETTING_SHAPE_REMEDY, j as resolveSettings } from "./settings-xB0Frkg6.mjs";
12
- import { f as teardownOwnedAvd, i as stopTunnel, l as withManagedTunnelRemovalLock, m as teardownOwnedIosSim, p as teardownOwnedBrowser, s as withManagedRemoteWorktreeRemovalLock, t as resolveSupervisorTarget } from "./ownership-PUP4Dwai.mjs";
13
- import { a as readWebRecord } from "./state-l1pYwOre.mjs";
14
- import { n as withIdleWorkspace, t as emptyWorkspaceDir } from "./in-use-wYEaKQM9.mjs";
15
- import { n as verifyCollectorOwnership } from "./ownership-Bz2kETAS.mjs";
16
- import { f as resolveEasCliBin } from "./remote-cache--aehr4Pr.mjs";
17
- import { i as endRecordedSession } from "./device-remote-DT9ngZiD.mjs";
18
- import { h as podInstallCommand, n as HEARTBEAT_INTERVAL_MS } from "./xcode-LM6A8cNZ.mjs";
19
- import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-DNN0yfx6.mjs";
20
- import { a as runPodInstall, i as runCaptured, n as podsAreStale, r as readPodState, t as DEPS_ERROR } from "./deps-2Wo81np9.mjs";
1
+ import { t as getExecutor } from "./exec-CaErVv7t.mjs";
2
+ import { E as upsertProject, H as projectDeviceSlots, S as removeProject, _ as loadConfig$1, a as clearDevice, d as getConfigDir, g as isPathPrefix, k as acquireAvdClaim, p as getProject } from "./config-D0DbpZtM.mjs";
3
+ import { n as ownership_claim_exports, t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
4
+ import { a as pullRequestLookup, r as endedPullRequest } from "./pull-request-pxnDCQzn.mjs";
5
+ import { b as workspaceDir, i as recordWorkspaceUse, r as readWorkspaceState$1 } from "./workspace-state-CL2V2Ons.mjs";
6
+ import { C as sourceCheckoutOf, S as restoreFile, T as warmWorktreePaths, _ as readWorktreeExclude, b as resolveFullRef, d as hasUncommittedWork, f as isPodInstallChurn, g as podsOutOfSync, h as locallyKnownUpstream, i as depsOutOfSync, l as hasPopulatedSubmodules, m as listWorktrees, n as cloneIgnoredEntries, o as dirtyPaths, r as deleteBranch, s as gitCommonDir, t as branchExists, u as hasRemote, v as removeWorktree, w as unpushedCommits } from "./worktree-C0c5Udwa.mjs";
7
+ import { a as findProjectRoot$1, n as appProjectProblem } from "./project-CV3tDFHl.mjs";
8
+ import { a as clearRemoteSession, c as readMetroTunnel, d as readRemoteSessionId, i as clearManagedMetroTunnel, u as readRemoteSession } from "./state-CaeaHbDA.mjs";
9
+ import { a as formatElapsed, h as shortUdid, i as formatDuration, l as phaseLine, p as releasedLeaseFact, u as plural } from "./command-output-CliHHn7a.mjs";
10
+ import { u as releaseWorkspaceLeases } from "./device-lease-W5Pi1q38.mjs";
11
+ import { a as workspaceLinks, i as workspaceLinkLine } from "./stim-desktop-Cnz8CPnl.mjs";
12
+ import { at as POOL_SETTING_REMEDY, dt as parkedMaxSetting } from "./android-Db2eEMaf.mjs";
13
+ import { l as resolveProjectMetro, o as pidExists, r as killMetroTree, u as signalProcessTree } from "./metro-Di8EN2Tb.mjs";
14
+ import { B as unknownSettingKeys, L as settingShapeErrors, N as resolveSettings, a as SETTING_SHAPE_REMEDY } from "./settings-CduVcoxj.mjs";
15
+ import { a as teardownOwnedBrowser, i as teardownOwnedAvd, o as teardownOwnedIosSim } from "./teardown-BYuQafiA.mjs";
16
+ import { t as clearNamedPorts } from "./named-ports-Dj_XBNaL.mjs";
17
+ import { o as readWebRecord } from "./state-DFm77wRn.mjs";
18
+ import { n as withIdleWorkspace, t as emptyWorkspaceDir } from "./in-use-CndPMilN.mjs";
19
+ import { g as withManagedRemoteWorktreeRemovalLock, l as withdrawDevServerStopRequest, p as stopTunnel, s as requestDevServerStop, u as resolveSupervisorTarget, v as withManagedTunnelRemovalLock } from "./stop-cause-CZ_Mr-v9.mjs";
20
+ import { n as verifyCollectorOwnership } from "./ownership-BkYxUtuj.mjs";
21
+ import { C as podInstallCommand, u as HEARTBEAT_INTERVAL_MS } from "./toolchain-DmeYjXD9.mjs";
22
+ import { t as stopMacosApp } from "./stop-Bvf1MflS.mjs";
23
+ import { a as runPodInstall, i as runCaptured, n as podsAreStale, r as readPodState, t as DEPS_ERROR } from "./deps-BFlKbOIp.mjs";
24
+ import { f as resolveEasCliBin } from "./remote-cache-CO2elm_b.mjs";
25
+ import { i as endRecordedSession } from "./device-remote-9ulPsdhY.mjs";
26
+ import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-BmsjKZ2H.mjs";
27
+ import { a as forgetStatusMeasures, t as startWarmProgress } from "./warm-progress-DVHLN4uC.mjs";
28
+ import { createHash, randomUUID } from "node:crypto";
21
29
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
22
- import { existsSync as existsSync$1, realpathSync as realpathSync$1 } from "fs";
23
- import { basename, dirname, isAbsolute, relative, resolve } from "path";
30
+ import { join, relative } from "node:path";
31
+ import { existsSync as existsSync$1, realpathSync as realpathSync$1, rmSync as rmSync$1 } from "fs";
32
+ import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1 } from "path";
24
33
  import { workspaceName } from "@stim-cli/core";
25
- import { join as join$1, relative as relative$1 } from "node:path";
26
- import { createHash, randomUUID } from "node:crypto";
34
+ import { LAST_BUILD_KEYS, readMacosRecord } from "@stim-cli/core/state";
27
35
  import chalk from "chalk";
28
36
  //#region src/devices/reclaim.ts
29
37
  async function reapCollectors(root, { verify = verifyCollectorOwnership, collectors }) {
@@ -166,14 +174,22 @@ function describeDereferenced(project) {
166
174
  }
167
175
  return devices;
168
176
  }
169
- function parkRequest(projectPath, slot, simslimManaged) {
177
+ function lastBuildCacheKey(projectPath, platform) {
178
+ const build = readWorkspaceState$1(projectPath)?.[LAST_BUILD_KEYS[platform]];
179
+ if (build === null || typeof build !== "object" || Array.isArray(build)) return null;
180
+ const { cacheKey } = build;
181
+ return typeof cacheKey === "string" ? cacheKey : null;
182
+ }
183
+ function parkRequest(project, projectPath, slot, simslimManaged) {
170
184
  const { max, error } = parkedMaxSetting("ios");
171
185
  if (error || max <= 0) return void 0;
172
186
  return {
173
187
  projectPath,
174
188
  max,
175
189
  simslimManaged,
176
- slot
190
+ slot,
191
+ appId: typeof project?.bundleId === "string" ? project.bundleId : null,
192
+ cacheKey: lastBuildCacheKey(projectPath, "ios")
177
193
  };
178
194
  }
179
195
  function reclaimOwnedDevices(project, projectPath, { park = false } = {}) {
@@ -191,7 +207,7 @@ function reclaimOwnedDevices(project, projectPath, { park = false } = {}) {
191
207
  const r = teardownOwnedIosSim(udid, {
192
208
  del: true,
193
209
  label,
194
- ...park ? { park: parkRequest(projectPath, slot, Boolean(ios.simslimManaged)) } : {}
210
+ ...park ? { park: parkRequest(project, projectPath, slot, Boolean(ios.simslimManaged)) } : {}
195
211
  });
196
212
  if (r.parkFallback) poolNotes.push(`could not park ${label}: ${r.parkFallback} -- deleted it instead`);
197
213
  for (const failure of r.evictionFailures ?? []) poolNotes.push(failure);
@@ -230,7 +246,9 @@ function reclaimOwnedDevices(project, projectPath, { park = false } = {}) {
230
246
  projectPath,
231
247
  slot,
232
248
  max: bound.max,
233
- configuration: typeof android.poolConfiguration === "string" ? android.poolConfiguration : void 0
249
+ configuration: typeof android.poolConfiguration === "string" ? android.poolConfiguration : void 0,
250
+ appId: typeof project?.androidPackage === "string" ? project.androidPackage : null,
251
+ cacheKey: lastBuildCacheKey(projectPath, "android")
234
252
  } } : {}
235
253
  });
236
254
  if (r.parkFallback) poolNotes.push(`could not park ${android.avdName}: ${r.parkFallback} -- deleted it instead`);
@@ -298,12 +316,12 @@ async function reclaimProject(path, options = {}) {
298
316
  failedWorkspaceDirs: []
299
317
  };
300
318
  }
301
- async function reclaimIdleProject(path, workspaceExisted, { deleteOwnedDevices = false, parkOwnedDevices = false, preserveProjectRecord = false, stopSession = defaultStopSession, stopMetroTunnel = defaultStopMetroTunnel, releaseLeases = releaseWorkspaceLeases, verifyCollector = verifyCollectorOwnership, teardownBrowser = teardownOwnedBrowser }) {
319
+ async function reclaimIdleProject(path, workspaceExisted, { deleteOwnedDevices = false, parkOwnedDevices = false, preserveProjectRecord = false, stopSession = defaultStopSession, stopMetroTunnel = defaultStopMetroTunnel, releaseLeases = releaseWorkspaceLeases, verifyCollector = verifyCollectorOwnership, teardownBrowser = teardownOwnedBrowser, stopRequester = { by: "stim gc --delete" } }) {
302
320
  await clearNamedPorts(path, { stop: true });
303
321
  const project = getProject(path);
304
322
  for (const { platforms } of projectDeviceSlots(project)) if (platforms.android?.owned && platforms.android.avdName) (0, ownership_claim_exports.releaseClaim)(acquireAvdClaim(platforms.android.avdName));
305
323
  const dereferenced = describeDereferenced(project);
306
- const initialState = readWorkspaceState(path);
324
+ const initialState = readWorkspaceState$1(path);
307
325
  let killedPid = null;
308
326
  let skippedMetro = null;
309
327
  let supervisorHeld = false;
@@ -318,7 +336,10 @@ async function reclaimIdleProject(path, workspaceExisted, { deleteOwnedDevices =
318
336
  };
319
337
  const orphanIdentity = supervisor.status === "stale" && typeof orphanServer.processToken === "string" ? (0, process_identity_exports.inspectProcessIdentity)(orphanServer) : null;
320
338
  if (supervisor.status === "ours") {
321
- if (killMetroTree(supervisor.pid, supervisor.processToken) && await (0, process_identity_exports.waitForProcessExit)(supervisor, 1e4)) killedPid = supervisor.pid;
339
+ requestDevServerStop(path, supervisor.processToken, stopRequester);
340
+ const signalled = killMetroTree(supervisor.pid, supervisor.processToken);
341
+ if (!signalled) withdrawDevServerStopRequest(path, supervisor.processToken);
342
+ if (signalled && await (0, process_identity_exports.waitForProcessExit)(supervisor, 1e4)) killedPid = supervisor.pid;
322
343
  else {
323
344
  skippedMetro = `could not confirm supervisor pid ${supervisor.pid} exited`;
324
345
  supervisorHeld = true;
@@ -356,7 +377,7 @@ async function reclaimIdleProject(path, workspaceExisted, { deleteOwnedDevices =
356
377
  failedCollectors.push(entry);
357
378
  }
358
379
  function retainReplacementProcesses() {
359
- const currentState = readWorkspaceState(path);
380
+ const currentState = readWorkspaceState$1(path);
360
381
  const currentProject = getProject(path);
361
382
  if (Boolean(currentState?.supervisor && !(0, process_identity_exports.sameProcessRecord)(currentState.supervisor, initialState?.supervisor) || currentProject?.supervisor && !(0, process_identity_exports.sameProcessRecord)(currentProject.supervisor, project?.supervisor) || readWebRecord(path) !== null || Object.entries(currentState?.collectors ?? {}).some(([platform, record]) => !(0, process_identity_exports.sameProcessRecord)(record, initialState?.collectors?.[platform])))) {
362
383
  supervisorHeld = true;
@@ -379,6 +400,17 @@ async function reclaimIdleProject(path, workspaceExisted, { deleteOwnedDevices =
379
400
  skippedDevices.push(remote.failed);
380
401
  failedDevices.push(remote.failed);
381
402
  }
403
+ const hosted = readMacosRecord(path)?.host;
404
+ if (hosted) try {
405
+ await stopMacosApp(path);
406
+ } catch (error) {
407
+ const failed = {
408
+ name: `hosted macOS session ${hosted.session} on ${hosted.machine}`,
409
+ reason: String(error?.message ?? error)
410
+ };
411
+ skippedDevices.push(failed);
412
+ failedDevices.push(failed);
413
+ }
382
414
  const tunnel = await reclaimMetroTunnel(path, { stopMetroTunnel });
383
415
  if (tunnel.failed) {
384
416
  skippedDevices.push(tunnel.failed);
@@ -425,192 +457,6 @@ async function reclaimIdleProject(path, workspaceExisted, { deleteOwnedDevices =
425
457
  };
426
458
  }
427
459
  //#endregion
428
- //#region src/workspace/pull-request.ts
429
- const GH_TIMEOUT_MS = 2e4;
430
- const GH_ENV = {
431
- GH_PROMPT_DISABLED: "1",
432
- GH_NO_UPDATE_NOTIFIER: "1",
433
- NO_COLOR: "1"
434
- };
435
- const GH_FIELDS = "number,state,url,headRefOid,mergedAt,closedAt,isCrossRepository";
436
- const SIGNED_OUT_EXIT = 4;
437
- function stateOf(state) {
438
- if (state === "OPEN") return "open";
439
- if (state === "MERGED") return "merged";
440
- if (state === "CLOSED") return "closed";
441
- return null;
442
- }
443
- function epoch(text) {
444
- const at = text ? Date.parse(text) : NaN;
445
- return Number.isFinite(at) ? at : null;
446
- }
447
- /**
448
- * Picks the pull request that describes `head` among those `gh pr list --head <branch>` returned. One whose head is
449
- * `head` wins, then one whose head contains `head`, then one whose head `head` contains. An open one wins a tier,
450
- * then the newest. A pull request unrelated to `head` is from an earlier use of the branch name, and one from a fork
451
- * only shares the branch name; both are ignored.
452
- * `isAncestor(a, b)` answers whether commit `a` is an ancestor of commit `b`, and false when git cannot tell.
453
- */
454
- function selectPullRequest(pulls, head, isAncestor) {
455
- const known = pulls.flatMap((pull) => {
456
- const state = stateOf(pull.state);
457
- return state && pull.headRefOid && !pull.isCrossRepository ? [{
458
- pull,
459
- state
460
- }] : [];
461
- });
462
- const tiers = [
463
- known.filter(({ pull }) => pull.headRefOid === head),
464
- known.filter(({ pull }) => pull.headRefOid !== head && isAncestor(head, pull.headRefOid)),
465
- known.filter(({ pull }) => pull.headRefOid !== head && isAncestor(pull.headRefOid, head))
466
- ];
467
- const tier = tiers.findIndex((entries) => entries.length > 0);
468
- if (tier < 0) return null;
469
- const { pull, state } = tiers[tier].toSorted((a, b) => Number(b.state === "open") - Number(a.state === "open") || b.pull.number - a.pull.number)[0];
470
- return {
471
- number: pull.number,
472
- state,
473
- url: pull.url,
474
- head: pull.headRefOid,
475
- containsHead: tier < 2,
476
- endedAt: state === "merged" ? epoch(pull.mergedAt) : state === "closed" ? epoch(pull.closedAt) : null
477
- };
478
- }
479
- function firstLine(error) {
480
- const { stderr, message } = error;
481
- const text = String(stderr ?? "").trim() || String(message ?? error);
482
- return text.split("\n")[0] ?? text;
483
- }
484
- function ghFailure(error, command) {
485
- if (error.status === SIGNED_OUT_EXIT) return {
486
- unavailable: "gh is not signed in; run `gh auth login`",
487
- sticky: true
488
- };
489
- if (error.code === "ETIMEDOUT") return {
490
- unavailable: `${command} did not answer within ${GH_TIMEOUT_MS / 1e3}s`,
491
- sticky: true
492
- };
493
- return {
494
- unavailable: `${command} failed: ${firstLine(error)}`,
495
- sticky: false
496
- };
497
- }
498
- function ancestry(cwd) {
499
- const exec = getExecutor();
500
- return (ancestor, descendant) => exec.runFileQuiet("git", [
501
- "-C",
502
- cwd,
503
- "merge-base",
504
- "--is-ancestor",
505
- ancestor,
506
- descendant
507
- ]) !== null;
508
- }
509
- /**
510
- * Asks GitHub, through `gh`, which pull requests have `branch` as their head, run from `cwd` so `gh` picks the
511
- * repository from its remotes. Each call is one fixed `gh pr list` invocation. Once `gh` is missing, signed out or
512
- * times out, every later lookup through the same function answers `unavailable` without running it again.
513
- */
514
- function pullRequestLookup() {
515
- let unavailable;
516
- return (cwd, branch, head) => {
517
- const exec = getExecutor();
518
- if (unavailable === void 0) unavailable = exec.findExecutable("gh") ? null : "gh is not installed";
519
- if (unavailable) return { unavailable };
520
- let out;
521
- try {
522
- out = exec.runFile("gh", [
523
- "pr",
524
- "list",
525
- "--head",
526
- branch,
527
- "--state",
528
- "all",
529
- "--limit",
530
- "20",
531
- "--json",
532
- GH_FIELDS
533
- ], {
534
- cwd,
535
- timeoutMs: GH_TIMEOUT_MS,
536
- env: GH_ENV
537
- });
538
- } catch (error) {
539
- const failure = ghFailure(error, "gh pr list");
540
- if (failure.sticky) unavailable = failure.unavailable;
541
- return { unavailable: failure.unavailable };
542
- }
543
- let pulls;
544
- try {
545
- pulls = JSON.parse(out);
546
- } catch {
547
- return { unavailable: "gh pr list printed no JSON" };
548
- }
549
- return { pullRequest: Array.isArray(pulls) ? selectPullRequest(pulls, head, ancestry(cwd)) : null };
550
- };
551
- }
552
- function branchesQuery(count) {
553
- return `query($owner: String!, $repo: String!${Array.from({ length: count }, (_, i) => `, $b${i}: String!`).join("")}) { repository(owner: $owner, name: $repo) {${Array.from({ length: count }, (_, i) => ` b${i}: pullRequests(headRefName: $b${i}, states: [OPEN, CLOSED, MERGED], first: 20, orderBy: {field: CREATED_AT, direction: DESC}) { nodes { ${GH_FIELDS.replaceAll(",", " ")} } }`).join("")} } }`;
554
- }
555
- /**
556
- * {@link pullRequestLookup} for every branch of one repository in one fixed `gh api graphql` call run from `repo`,
557
- * which asks for the same 20 newest pull requests of each head branch that `gh pr list --head` returns. `gh` fills
558
- * `{owner}` and `{repo}` from the remotes of `repo`; branch names travel as variables. Once `gh` is missing, signed
559
- * out or times out, every later call through the same function answers `unavailable` without running it again.
560
- */
561
- function pullRequestLookups() {
562
- let unavailable;
563
- return async (repo, queries) => {
564
- const exec = getExecutor();
565
- if (unavailable === void 0) unavailable = exec.findExecutable("gh") ? null : "gh is not installed";
566
- const all = (lookup) => queries.map(() => lookup);
567
- if (unavailable) return all({ unavailable });
568
- if (!queries.length) return [];
569
- const args = [
570
- "api",
571
- "graphql",
572
- "-F",
573
- "owner={owner}",
574
- "-F",
575
- "repo={repo}"
576
- ];
577
- queries.forEach(({ branch }, i) => args.push("-f", `b${i}=${branch}`));
578
- args.push("-f", `query=${branchesQuery(queries.length)}`);
579
- let out;
580
- try {
581
- out = await exec.runFileAsync("gh", args, {
582
- cwd: repo,
583
- timeoutMs: GH_TIMEOUT_MS,
584
- env: GH_ENV
585
- });
586
- } catch (error) {
587
- const failure = ghFailure(error, "gh api graphql");
588
- if (failure.sticky) unavailable = failure.unavailable;
589
- return all({ unavailable: failure.unavailable });
590
- }
591
- let found;
592
- try {
593
- found = JSON.parse(out).data?.repository;
594
- } catch {
595
- return all({ unavailable: "gh api graphql printed no JSON" });
596
- }
597
- if (!found) return all({ unavailable: "gh api graphql printed no repository" });
598
- return queries.map(({ cwd, head }, i) => {
599
- const pulls = found[`b${i}`]?.nodes;
600
- return { pullRequest: Array.isArray(pulls) ? selectPullRequest(pulls, head, ancestry(cwd)) : null };
601
- });
602
- };
603
- }
604
- /** The merged or closed pull request whose commits include HEAD, which makes the worktree removable. */
605
- function endedPullRequest(lookup) {
606
- const pr = lookup && "pullRequest" in lookup ? lookup.pullRequest : null;
607
- return pr && pr.state !== "open" && pr.containsHead ? pr : null;
608
- }
609
- /** "PR #123 merged", for reports. */
610
- function describePullRequest(pr) {
611
- return `PR #${pr.number} ${pr.state}`;
612
- }
613
- //#endregion
614
460
  //#region src/engine/warm-claim.ts
615
461
  const WARM_CLAIM_LABEL = "worktree warm";
616
462
  const WARM_CLAIM_POLL_MS = 250;
@@ -631,10 +477,10 @@ function defaultSleep(ms) {
631
477
  return new Promise((resolve) => setTimeout(resolve, ms));
632
478
  }
633
479
  function warmClaimsDir() {
634
- return join$1(getConfigDir(), "warm-locks");
480
+ return join(getConfigDir(), "warm-locks");
635
481
  }
636
482
  function warmClaimPath(repositoryRoot) {
637
- return join$1(warmClaimsDir(), `${workspaceName(repositoryRoot)}.lock`);
483
+ return join(warmClaimsDir(), `${workspaceName(repositoryRoot)}.lock`);
638
484
  }
639
485
  function warmCommand(phase) {
640
486
  return phase === "refresh" ? "stim worktree warm --refresh" : "stim worktree warm";
@@ -961,11 +807,11 @@ function stepLine(label, reason, action) {
961
807
  return phaseLine(label, `${reason} -> ${action}`);
962
808
  }
963
809
  function installRecordFile(depsRoot) {
964
- return join$1(getConfigDir(), "warm-installs", `${workspaceName(depsRoot)}.json`);
810
+ return join(getConfigDir(), "warm-installs", `${workspaceName(depsRoot)}.json`);
965
811
  }
966
812
  function lockfileHash(depsRoot, lockfile) {
967
813
  try {
968
- return createHash("sha256").update(readFileSync(join$1(depsRoot, lockfile))).digest("hex");
814
+ return createHash("sha256").update(readFileSync(join(depsRoot, lockfile))).digest("hex");
969
815
  } catch {
970
816
  return null;
971
817
  }
@@ -987,7 +833,7 @@ function readInstallEvidence(depsRoot) {
987
833
  function writeInstallEvidence(depsRoot, lockfile, hash, completed) {
988
834
  const file = installRecordFile(depsRoot);
989
835
  const tmp = `${file}.${process.pid}.${randomUUID()}.tmp`;
990
- mkdirSync(join$1(getConfigDir(), "warm-installs"), { recursive: true });
836
+ mkdirSync(join(getConfigDir(), "warm-installs"), { recursive: true });
991
837
  writeFileSync(tmp, JSON.stringify({
992
838
  lock: lockfile,
993
839
  hash: hash ?? "",
@@ -1005,9 +851,9 @@ function restoreInstallEvidence(depsRoot, prior) {
1005
851
  else rmSync(installRecordFile(depsRoot), { force: true });
1006
852
  }
1007
853
  function inProgressOperation(gitDir) {
1008
- if (existsSync(join$1(gitDir, "rebase-merge"))) return "rebase";
1009
- if (existsSync(join$1(gitDir, "rebase-apply"))) return existsSync(join$1(gitDir, "rebase-apply", "applying")) ? "am" : "rebase";
1010
- return existsSync(join$1(gitDir, "MERGE_HEAD")) ? "merge" : null;
854
+ if (existsSync(join(gitDir, "rebase-merge"))) return "rebase";
855
+ if (existsSync(join(gitDir, "rebase-apply"))) return existsSync(join(gitDir, "rebase-apply", "applying")) ? "am" : "rebase";
856
+ return existsSync(join(gitDir, "MERGE_HEAD")) ? "merge" : null;
1011
857
  }
1012
858
  function readMainCheckoutState(root) {
1013
859
  const exec = getExecutor();
@@ -1261,7 +1107,7 @@ async function refreshMainCheckout({ root, appDir, settings, emit, spawnFn = nul
1261
1107
  const consumed = dependencies ? lockfileHash(dependencies.root, dependencies.lock) : null;
1262
1108
  const deps = depsPlan({
1263
1109
  lockfile: dependencies?.lock ?? null,
1264
- lockfileChanged: dependencies ? pathChangedBetween(root, before, head, gitPath(relative$1(root, dependencies.root), dependencies.lock)) : false,
1110
+ lockfileChanged: dependencies ? pathChangedBetween(root, before, head, gitPath(relative(root, dependencies.root), dependencies.lock)) : false,
1265
1111
  installed,
1266
1112
  treeValid: dependencies?.lock === "package-lock.json" && installed ? installedNpmTreeIsValid(dependencies.root) : null,
1267
1113
  lastInstall: priorInstall,
@@ -1293,11 +1139,11 @@ async function refreshMainCheckout({ root, appDir, settings, emit, spawnFn = nul
1293
1139
  if (marked) restoreInstallEvidence(marked.root, priorInstall);
1294
1140
  emit(stepLine("deps", depsSubject, "skipped"));
1295
1141
  }
1296
- const app = relative$1(root, appDir) || ".";
1142
+ const app = relative(root, appDir) || ".";
1297
1143
  const where = `source ${appDir}: `;
1298
1144
  const podState = readPodState(appDir);
1299
1145
  const pods = podsPlan({
1300
- hasIos: existsSync(join$1(appDir, "ios")),
1146
+ hasIos: existsSync(join(appDir, "ios")),
1301
1147
  hasPodfile: podState.hasPodfile,
1302
1148
  podfileLockChanged: pathChangedBetween(root, before, head, gitPath(app, "ios", "Podfile.lock")),
1303
1149
  stale: podsAreStale(podState.lockText, podState.manifestText)
@@ -1328,12 +1174,12 @@ async function refreshMainCheckout({ root, appDir, settings, emit, spawnFn = nul
1328
1174
  //#endregion
1329
1175
  //#region src/commands/worktree.ts
1330
1176
  function dependencyInstallCommand(target, dir = ".") {
1331
- const installRoot = resolve(target, dir);
1177
+ const installRoot = resolve$1(target, dir);
1332
1178
  let command = "npm install";
1333
- if (existsSync$1(resolve(installRoot, "pnpm-lock.yaml"))) command = "pnpm install";
1334
- else if (existsSync$1(resolve(installRoot, "yarn.lock"))) command = "yarn install";
1335
- else if (existsSync$1(resolve(installRoot, "bun.lock")) || existsSync$1(resolve(installRoot, "bun.lockb"))) command = "bun install";
1336
- else if (existsSync$1(resolve(installRoot, "package-lock.json"))) command = "npm ci";
1179
+ if (existsSync$1(resolve$1(installRoot, "pnpm-lock.yaml"))) command = "pnpm install";
1180
+ else if (existsSync$1(resolve$1(installRoot, "yarn.lock"))) command = "yarn install";
1181
+ else if (existsSync$1(resolve$1(installRoot, "bun.lock")) || existsSync$1(resolve$1(installRoot, "bun.lockb"))) command = "bun install";
1182
+ else if (existsSync$1(resolve$1(installRoot, "package-lock.json"))) command = "npm ci";
1337
1183
  return `cd '${installRoot.replaceAll("'", "'\\''")}' && ${command}`;
1338
1184
  }
1339
1185
  function reportCarriedStateHealth(root, target, copied) {
@@ -1351,7 +1197,7 @@ function reportCarriedStateHealth(root, target, copied) {
1351
1197
  }
1352
1198
  for (const p of podsOutOfSync(target, copied)) {
1353
1199
  const where = p.dir === "." ? "Podfile.lock" : `${p.dir}/Podfile.lock`;
1354
- const pod = podInstallCommand(p.dir === "." ? target : resolve(target, p.dir, ".."));
1200
+ const pod = podInstallCommand(p.dir === "." ? target : resolve$1(target, p.dir, ".."));
1355
1201
  console.error(chalk.yellow(phaseLine("carry", p.reason === "missing" ? `carried ${p.dir === "." ? "Pods" : `${p.dir}/Pods`} but there is no ${where}. Run \`${pod}\` before building.` : `carried ${p.dir === "." ? "Pods" : `${p.dir}/Pods`} does not match the ${where} on disk here. Pods are gitignored and cloned; Podfile.lock is tracked, so the two can disagree. Run \`${pod}\` before building, or xcodebuild fails with "sandbox is not in sync" only after every pod has compiled.`)));
1356
1202
  }
1357
1203
  }
@@ -1363,18 +1209,51 @@ function reportRefreshFailure(failure) {
1363
1209
  process.exitCode = 1;
1364
1210
  }
1365
1211
  function mainCheckoutAppDir(root, target) {
1366
- const app = findProjectRoot(process.cwd());
1212
+ const app = findProjectRoot$1(process.cwd());
1367
1213
  if (!app) return root;
1368
- const rel = relative(target, app);
1369
- if (!rel || rel.startsWith("..") || isAbsolute(rel)) return root;
1370
- return resolve(root, rel);
1214
+ const rel = relative$1(target, app);
1215
+ if (!rel || rel.startsWith("..") || isAbsolute$1(rel)) return root;
1216
+ return resolve$1(root, rel);
1217
+ }
1218
+ /**
1219
+ * The app workspace a warm prepares: the nearest app below the worktree root, or else the one app registered in
1220
+ * the source checkout, at the same place in this worktree, or else the worktree root when it is the app. A monorepo
1221
+ * root can declare react-native without being the app.
1222
+ */
1223
+ function warmedWorkspace(root, target, cwd) {
1224
+ const nearest = findProjectRoot$1(cwd);
1225
+ const app = nearest && isPathPrefix(target, nearest) && appProjectProblem(nearest) === null ? nearest : null;
1226
+ if (app && app !== target) return app;
1227
+ const apps = /* @__PURE__ */ new Set();
1228
+ for (const path of Object.keys(loadConfig$1()?.projects ?? {})) {
1229
+ const rel = relative$1(root, path);
1230
+ if (rel.startsWith("..") || isAbsolute$1(rel)) continue;
1231
+ const mapped = resolve$1(target, rel);
1232
+ if (appProjectProblem(mapped) === null) apps.add(mapped);
1233
+ }
1234
+ return apps.size === 1 ? [...apps][0] : app;
1235
+ }
1236
+ function registerWarmedWorkspace(workspace) {
1237
+ try {
1238
+ if (!getProject(workspace)) upsertProject(workspace, {});
1239
+ } catch (error) {
1240
+ console.error(chalk.yellow(`Could not register ${workspace} for status: ${error?.message || String(error)}`));
1241
+ }
1371
1242
  }
1372
1243
  function registerWarm(worktree) {
1373
1244
  worktree.command("warm").description("Copy missing ignored paths from the source checkout into the current linked worktree.").option("--refresh", "Before copying, fast-forward the source checkout to its upstream and install what the new commits moved.").action(async (opts) => {
1245
+ let progress = null;
1246
+ let workspace = null;
1374
1247
  try {
1375
1248
  const { root, target, common } = warmWorktreePaths(process.cwd());
1376
- const app = findProjectRoot(process.cwd());
1249
+ const app = findProjectRoot$1(process.cwd());
1377
1250
  if (app) recordWorkspaceUse(app);
1251
+ workspace = warmedWorkspace(root, target, process.cwd());
1252
+ if (!workspace) console.error(chalk.dim(phaseLine("project", "no single app to register here; run warm from the app directory to list it in status while it warms")));
1253
+ else {
1254
+ registerWarmedWorkspace(workspace);
1255
+ progress = startWarmProgress(workspace, opts.refresh ? "refresh" : "copy", { note: (line) => console.error(chalk.yellow(line)) });
1256
+ }
1378
1257
  const readSettings = () => {
1379
1258
  const settings = resolveSettings({
1380
1259
  gitCommonDir: common,
@@ -1390,6 +1269,7 @@ function registerWarm(worktree) {
1390
1269
  return settings;
1391
1270
  };
1392
1271
  const copy = (wait) => {
1272
+ progress?.step("copy");
1393
1273
  if (wait?.holder) console.error(warmClaimAcquiredLine(wait));
1394
1274
  const incomplete = incompleteInstallRefusal(root, mainCheckoutAppDir(root, target));
1395
1275
  if (incomplete) {
@@ -1487,6 +1367,10 @@ function registerWarm(worktree) {
1487
1367
  if (claim) console.error(chalk.dim(claim.remedy));
1488
1368
  if (typeof code === "string" && code.startsWith("STIM_")) console.error(chalk.red(`failed: ${code}`));
1489
1369
  process.exitCode = 1;
1370
+ } finally {
1371
+ progress?.finish(!process.exitCode);
1372
+ const links = workspace && !process.exitCode ? workspaceLinks(workspace) : void 0;
1373
+ if (links) console.error(chalk.dim(workspaceLinkLine(links)));
1490
1374
  }
1491
1375
  });
1492
1376
  }
@@ -1501,6 +1385,20 @@ function porcelainPath(line) {
1501
1385
  }
1502
1386
  const SAFE_DIFF_PATH = /^[A-Za-z0-9._/-]+$/;
1503
1387
  const POD_CHURN_PATH = /(?:^|\/)ios\/(?:Podfile\.lock|[^/]+\.xcodeproj\/project\.pbxproj)$/;
1388
+ const WATCHMAN_COOKIE_PATH = /(?:^|\/)\.watchman-cookie-[A-Za-z0-9._-]+$/;
1389
+ function excludeWatchmanCookies(lines) {
1390
+ const kept = [];
1391
+ const cookies = [];
1392
+ for (const line of lines || []) {
1393
+ const path = String(line).startsWith("?? ") ? porcelainPath(line) : null;
1394
+ if (path && SAFE_DIFF_PATH.test(path) && WATCHMAN_COOKIE_PATH.test(path)) cookies.push(path);
1395
+ else kept.push(line);
1396
+ }
1397
+ return {
1398
+ lines: kept,
1399
+ cookies
1400
+ };
1401
+ }
1504
1402
  function excludePodChurn(lines) {
1505
1403
  const kept = [];
1506
1404
  const restore = [];
@@ -1523,11 +1421,11 @@ function excludePodChurn(lines) {
1523
1421
  }
1524
1422
  function nativeCanonicalPath(path) {
1525
1423
  const missing = [];
1526
- for (let existing = resolve(path);; existing = dirname(existing)) try {
1527
- return resolve(realpathSync$1.native(existing), ...missing);
1424
+ for (let existing = resolve$1(path);; existing = dirname$1(existing)) try {
1425
+ return resolve$1(realpathSync$1.native(existing), ...missing);
1528
1426
  } catch {
1529
- if (dirname(existing) === existing) return resolve(path);
1530
- missing.unshift(basename(existing));
1427
+ if (dirname$1(existing) === existing) return resolve$1(path);
1428
+ missing.unshift(basename$1(existing));
1531
1429
  }
1532
1430
  }
1533
1431
  function matchWorktreeEntry(entries, path) {
@@ -1581,7 +1479,7 @@ function describeKeptDevice(s) {
1581
1479
  return s.udid && s.udid !== s.name ? `${s.name} (${s.udid})` : s.name;
1582
1480
  }
1583
1481
  function reclaimKeys(rootPath) {
1584
- const cfg = loadConfig();
1482
+ const cfg = loadConfig$1();
1585
1483
  const keys = /* @__PURE__ */ new Set([rootPath]);
1586
1484
  const root = nativeCanonicalPath(rootPath);
1587
1485
  if (cfg?.projects) {
@@ -1615,7 +1513,8 @@ async function reclaimAll(rootPath, keys = reclaimKeys(rootPath), { preserveRoot
1615
1513
  r = await reclaimProject(key, {
1616
1514
  deleteOwnedDevices: true,
1617
1515
  parkOwnedDevices: true,
1618
- preserveProjectRecord: preserveRootProject && key === rootPath
1516
+ preserveProjectRecord: preserveRootProject && key === rootPath,
1517
+ stopRequester: { by: "stim worktree remove" }
1619
1518
  });
1620
1519
  } catch (error) {
1621
1520
  keptEntries.push(key);
@@ -1712,7 +1611,7 @@ function reportRetainedResources(root, result) {
1712
1611
  process.exitCode = 1;
1713
1612
  }
1714
1613
  function hasRegisteredProjectUnder(rootPath) {
1715
- const cfg = loadConfig();
1614
+ const cfg = loadConfig$1();
1716
1615
  return Object.keys(cfg?.projects ?? {}).some((key) => isPathPrefix(rootPath, key));
1717
1616
  }
1718
1617
  async function reclaimEnvironment(root, why) {
@@ -1739,20 +1638,21 @@ function removalPath(target) {
1739
1638
  return canonicalExistingPath(target ?? process.cwd());
1740
1639
  }
1741
1640
  function canonicalExistingPath(target) {
1742
- const resolved = resolve(target);
1641
+ const resolved = resolve$1(target);
1743
1642
  let existing = resolved;
1744
1643
  const missing = [];
1745
1644
  while (!existsSync$1(existing)) {
1746
- const parent = dirname(existing);
1645
+ const parent = dirname$1(existing);
1747
1646
  if (parent === existing) return resolved;
1748
- missing.unshift(basename(existing));
1647
+ missing.unshift(basename$1(existing));
1749
1648
  existing = parent;
1750
1649
  }
1751
- return resolve(realpathSync$1(existing), ...missing);
1650
+ return resolve$1(realpathSync$1(existing), ...missing);
1752
1651
  }
1753
1652
  function inspectRemoval(path, mergedHead) {
1754
1653
  const gitAnswered = hasUncommittedWork(path);
1755
- const { lines: dirtyLines, restore: podChurn } = excludePodChurn(gitAnswered ? dirtyPaths(path, { limit: Infinity }) : []);
1654
+ const { lines: withoutCookies, cookies } = excludeWatchmanCookies(gitAnswered ? dirtyPaths(path, { limit: Infinity }) : []);
1655
+ const { lines: dirtyLines, restore: podChurn } = excludePodChurn(withoutCookies);
1756
1656
  const dirty = gitAnswered === null ? null : dirtyLines.length > 0;
1757
1657
  const unpushed = unpushedCommits(path);
1758
1658
  const blockers = removalBlockers({
@@ -1763,6 +1663,7 @@ function inspectRemoval(path, mergedHead) {
1763
1663
  return {
1764
1664
  dirtyLines,
1765
1665
  podChurn,
1666
+ cookies,
1766
1667
  unpushed,
1767
1668
  blockers
1768
1669
  };
@@ -1781,6 +1682,9 @@ function printRemovalRefusal(path, inspection) {
1781
1682
  console.error(chalk.dim("work stays on the branch."));
1782
1683
  process.exitCode = 1;
1783
1684
  }
1685
+ function deleteWatchmanCookies(path, cookies) {
1686
+ for (const cookie of cookies) rmSync$1(join$1(path, cookie), { force: true });
1687
+ }
1784
1688
  function restorePodChurn(path, files) {
1785
1689
  for (const file of files) if (restoreFile(path, file)) console.error(chalk.dim(` restored ${file} (pod install churn; the worktree is being removed)`));
1786
1690
  else console.error(chalk.yellow(` could not restore ${file}; git may refuse to remove the worktree`));
@@ -1828,7 +1732,7 @@ async function runRemove(target, opts, onRemoved) {
1828
1732
  console.error(chalk.dim(phaseLine("branch", `${branch} is already absent; cleared its pending cleanup record`)));
1829
1733
  return;
1830
1734
  }
1831
- const checkedOutAt = listWorktrees(mainRoot).find((candidate) => candidate.branch === branch && resolve(candidate.path) !== resolve(path))?.path;
1735
+ const checkedOutAt = listWorktrees(mainRoot).find((candidate) => candidate.branch === branch && resolve$1(candidate.path) !== resolve$1(path))?.path;
1832
1736
  if (checkedOutAt) {
1833
1737
  console.error(chalk.red(`Refusing pending cleanup for ${branch}: it is checked out at ${checkedOutAt}.`));
1834
1738
  console.error(chalk.dim("Switch that worktree to another branch, then retry this command."));
@@ -1944,6 +1848,7 @@ async function runRemove(target, opts, onRemoved) {
1944
1848
  return;
1945
1849
  }
1946
1850
  restorePodChurn(path, current.podChurn);
1851
+ deleteWatchmanCookies(path, current.cookies);
1947
1852
  try {
1948
1853
  removeWorktree(path, {
1949
1854
  from: source.path,
@@ -1959,6 +1864,7 @@ async function runRemove(target, opts, onRemoved) {
1959
1864
  return;
1960
1865
  }
1961
1866
  onRemoved();
1867
+ forgetStatusMeasures(path, result.reclaimedKeys);
1962
1868
  const finish = () => {
1963
1869
  printRemovalCleanup(result, false);
1964
1870
  console.error(chalk.dim(phaseLine("removed", path)));
@@ -1974,7 +1880,7 @@ async function runRemove(target, opts, onRemoved) {
1974
1880
  worktreeRemovalComplete: true,
1975
1881
  worktreePendingBranchSha: approvedBranchSha
1976
1882
  });
1977
- const checkedOutAt = listWorktrees(branchDeleteCwd).find((candidate) => candidate.branch === branch && resolve(candidate.path) !== resolve(path))?.path;
1883
+ const checkedOutAt = listWorktrees(branchDeleteCwd).find((candidate) => candidate.branch === branch && resolve$1(candidate.path) !== resolve$1(path))?.path;
1978
1884
  if (checkedOutAt) {
1979
1885
  console.error(chalk.yellow(phaseLine("branch", `kept ${branch} (it is checked out at ${checkedOutAt})`)));
1980
1886
  console.error(chalk.dim(` Switch that worktree to another branch, then retry: stim worktree remove ${path}`));
@@ -2008,4 +1914,4 @@ function worktreeCommand(program) {
2008
1914
  registerRemove(worktree);
2009
1915
  }
2010
1916
  //#endregion
2011
- export { reclaimProject as _, reclaimKeys as a, removalBlockers as c, removeWorktreeTarget as d, worktreeCommand as f, describeDereferenced as g, pullRequestLookups as h, porcelainPath as i, removalPath as l, endedPullRequest as m, excludePodChurn as n, registerRemove as o, describePullRequest as p, matchWorktreeEntry as r, registerWarm as s, dependencyInstallCommand as t, removalRemedy as u };
1917
+ export { porcelainPath as a, registerWarm as c, removalRemedy as d, removeWorktreeTarget as f, reclaimProject as g, describeDereferenced as h, matchWorktreeEntry as i, removalBlockers as l, worktreeCommand as m, excludePodChurn as n, reclaimKeys as o, warmedWorkspace as p, excludeWatchmanCookies as r, registerRemove as s, dependencyInstallCommand as t, removalPath as u };