stim 1.21.0 → 1.23.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 +60 -7
- package/dist/{acquire-artifact-CD4lbiWE.mjs → acquire-artifact-ybouwJSR.mjs} +21 -3
- package/dist/{act-DHAw2A2S.mjs → act-DG54PWtJ.mjs} +298 -164
- package/dist/{activity-BzVfHSrY.mjs → activity-B5RtpFda.mjs} +33 -12
- package/dist/{agent-device-usage-output-zh1vhbLp.mjs → agent-device-usage-output-UJ5gC80l.mjs} +7 -7
- package/dist/{android-BLgbNqox.mjs → android-BoUJKbfz.mjs} +66 -52
- package/dist/{android-DUYkNF4u.mjs → android-C7sVmyI9.mjs} +160 -59
- package/dist/{android-CV_GCELZ.mjs → android-CHH4KLOR.mjs} +5 -5
- package/dist/{android-BEJ8Jpl7.mjs → android-DR5lZ2Az.mjs} +2 -2
- package/dist/android-cas-compiler.mjs +1 -1
- package/dist/{android-cas-Ct5b1xCK.mjs → android-cas-ngferVq2.mjs} +3 -3
- package/dist/{client-B7QVOlCr.d.mts → android-project-DVWyCbWU.d.mts} +145 -7
- package/dist/api.d.mts +132 -118
- package/dist/api.mjs +239 -42
- package/dist/{artifact-DAuZ-FNU.mjs → artifact-5RteZem7.mjs} +69 -40
- package/dist/{artifact-B8T3S5gi.mjs → artifact-Dwdo7k1E.mjs} +40 -35
- package/dist/artifact-platform-CXcmX_j3.mjs +27 -0
- package/dist/{browser-web-QQzVjz3O.mjs → browser-web-CeEVlGug.mjs} +187 -59
- package/dist/{budget-Bs6SxxzO.mjs → budget-DnPX3DXj.mjs} +830 -699
- package/dist/{build-CoPzQRNw.mjs → build-CqufZFqy.mjs} +34 -27
- package/dist/{build-cache-DmyR9Zzc.mjs → build-cache-Dv7RN200.mjs} +4 -4
- package/dist/{build-plan-CaFskB7_.mjs → build-plan-C8CIvRyi.mjs} +74 -14
- package/dist/{cache-manifest-DkkqguoK.mjs → cache-manifest-DSeQjt-Q.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{ccache-BJRZ9pG3.mjs → ccache-RcAy92gH.mjs} +6 -6
- package/dist/{cdp-CWmIUMbw.mjs → cdp-3fwICjzi.mjs} +1 -1
- package/dist/{chrome-B6tfCta1.mjs → chrome-Bbw689-6.mjs} +1 -1
- package/dist/{cli-0DYf8QhQ.mjs → cli-D-J9fWGV.mjs} +20 -20
- package/dist/cli.mjs +1 -1
- package/dist/{client-UXD70ZbM.mjs → client-yyso1bwi.mjs} +218 -47
- package/dist/collector-run.d.mts +1 -1
- package/dist/collector-run.mjs +6 -6
- package/dist/{command-output-B1UrjZHL.mjs → command-output-Db8BqzxH.mjs} +1 -1
- package/dist/{config-CYcBF478.mjs → config-DlrsduwG.mjs} +17 -11
- package/dist/{created-devices-CqeBlrEn.mjs → created-devices-x9rN6bhs.mjs} +13 -3
- package/dist/{dependency-state-BbKQW83h.mjs → dependency-state-1I9KZT1Y.mjs} +2 -2
- package/dist/{detached-entry-DFRyG1_o.mjs → detached-entry-BwNEtIrP.mjs} +1 -1
- package/dist/{dev-client-UOeESKm6.mjs → dev-client-BnYSDiKa.mjs} +2 -2
- package/dist/{device-DCxH7_By.mjs → device-BvzPUBs3.mjs} +16 -16
- package/dist/{device-ExJq8Ei7.mjs → device-CCkWmkAA.mjs} +71 -14
- package/dist/{device-capacity-CMlqEcvw.mjs → device-capacity-CjaSdrhg.mjs} +9 -10
- package/dist/device-host-worker.mjs +25 -32
- package/dist/{device-ios-DRUeKaFV.mjs → device-ios-uOiEETX2.mjs} +39 -16
- package/dist/{device-lease-run-C8raGFoP.mjs → device-lease-run-BgwPHc6u.mjs} +5 -5
- package/dist/{device-pool-DMRpVX9a.mjs → device-pool-C-ptmZTa.mjs} +3 -3
- package/dist/{doctor-DTvaDOda.mjs → doctor-8sr2tFOL.mjs} +24 -37
- package/dist/{doctor-Dhqebm0q.mjs → doctor-D2SN3Ltx.mjs} +19 -16
- package/dist/doctor-fingerprint-DZLoYFVb.mjs +16 -0
- package/dist/doctor-fingerprint.mjs +1 -1
- package/dist/{doctor-BLHCYxzi.mjs → doctor-pxxvipYC.mjs} +1 -1
- package/dist/{doctor-watchman-Cv1F10Kj.mjs → doctor-watchman-sy_RNhFt.mjs} +5 -5
- package/dist/{eas-build-BomfRnFT.mjs → eas-build-CyjU5K7E.mjs} +4 -4
- package/dist/{eas-fallback-DJwZpy0c.mjs → eas-fallback-DV3Wytf1.mjs} +27 -18
- package/dist/{eas-session-ledger-op6A54yi.mjs → eas-session-ledger-BzzsX97d.mjs} +8 -7
- package/dist/{error-diagnostics-Bf0rZvG9.mjs → error-diagnostics-C6sVoeqO.mjs} +10 -10
- package/dist/{exec-_UbtCQcG.mjs → exec-DEoAhcze.mjs} +155 -47
- package/dist/{exec-H20xSg2T.d.mts → exec-DnYc4HGH.d.mts} +17 -4
- package/dist/{gc-_-dCXdyL.mjs → gc-CDoogxAI.mjs} +587 -313
- package/dist/gc-DLbzvTP2.mjs +3 -0
- package/dist/{guide-Yqwt2bd6.mjs → guide-BC37GcDG.mjs} +288 -59
- package/dist/{hosted-android-Bw-honsd.mjs → hosted-android-CkclQig_.mjs} +6 -6
- package/dist/{hosted-client-DB_gBPfY.mjs → hosted-client-ByuMMO5s.mjs} +4 -4
- package/dist/{hosted-ios-CUvOf4Vr.mjs → hosted-ios-DtYHeuYR.mjs} +1 -1
- package/dist/{hosted-logs-DL_P5AdN.mjs → hosted-logs-Br84qWy1.mjs} +5 -5
- package/dist/{hosted-macos-QI141yKk.mjs → hosted-macos-DdtYajZt.mjs} +21 -12
- package/dist/{hosted-native-Ksyjgzq-.mjs → hosted-native-UPI9zdZ4.mjs} +27 -14
- package/dist/{idle-BVeRGroT.d.mts → idle-C4cZ757K.d.mts} +3 -3
- package/dist/{idle-shutdown-DaKmZi0z.mjs → idle-shutdown-DO7oIRlc.mjs} +7 -7
- package/dist/ios-CDvlmUs7.d.mts +24 -0
- package/dist/{ios-ubFsBzwq.mjs → ios-DZZbNYlY.mjs} +59 -50
- package/dist/{ios-device-CXGP1bsD.mjs → ios-device-BXZ0c7Kb.mjs} +1 -1
- package/dist/{ios-device-CwFMehUS.mjs → ios-device-JC3uWM4K.mjs} +3 -3
- package/dist/{ios-CpByZy12.mjs → ios-hcBpnEGk.mjs} +81 -34
- package/dist/{ios-project-WuwwBdvW.mjs → ios-project-CVyNwjAF.mjs} +5 -5
- package/dist/{ios-signing-DTTIChGs.mjs → ios-signing-BtAffm0A.mjs} +2 -2
- package/dist/{ios-state-DvFX-K_l.mjs → ios-state-Dtw5K2aI.mjs} +1 -1
- package/dist/{ios-CQ68hG9s.mjs → ios-vbUOqGXu.mjs} +8 -8
- package/dist/{ios-qNJt4dFm.mjs → ios-xtlRHaeB.mjs} +1 -1
- package/dist/{js-swap-Cc1dttps.mjs → js-swap-C3QxUFJR.mjs} +7 -29
- package/dist/{launch-CD1yVc7U.mjs → launch-Bz7JtaC4.mjs} +16 -16
- package/dist/{launch-Zq2mOSDy.mjs → launch-CHY3p-yz.mjs} +40 -39
- package/dist/{logs-CMm05HxN.mjs → logs-CtNeNDuq.mjs} +10 -17
- package/dist/{logs-BS00Anxf.mjs → logs-mxqsWpJs.mjs} +151 -24
- package/dist/{macos-B65--pPd.mjs → macos-inNFbBWc.mjs} +1 -1
- package/dist/macos-run.mjs +1 -1
- package/dist/{macos-C-0qJ6zq.mjs → macos-u0BoHKnO.mjs} +143 -46
- package/dist/maintenance-run.mjs +8 -8
- package/dist/{metro-DEXpIhHC.mjs → metro-DiXtA3Ty.mjs} +59 -37
- package/dist/{metro-gateway-BMJNlj3E.mjs → metro-gateway-CKZqKqx7.mjs} +120 -22
- package/dist/{named-ports-BWTzt6mu.mjs → named-ports-DkEzOfzO.mjs} +40 -33
- package/dist/{native-android-JACyjTZs.mjs → native-android-CdCSylTn.mjs} +43 -242
- package/dist/native-gradle-artifact-inputs-DHFnmrVf.mjs +270 -0
- package/dist/{native-gradle-inputs-Bep9JcSF.mjs → native-gradle-inputs-Dnh84Zbd.mjs} +3 -129
- package/dist/{native-run-DY_RLx3C.mjs → native-run-BUTu8T7A.mjs} +4 -5
- package/dist/{native-runtime-Dg1Q7tfF.mjs → native-runtime-CRSOZxKY.mjs} +8 -7
- package/dist/native-source-Y0yLhFyQ.mjs +133 -0
- package/dist/{native-xcode-inputs-CB2iQeWq.mjs → native-xcode-inputs-G6rkkhxu.mjs} +2 -3
- package/dist/{native-xcode-ios-CXX39F7o.mjs → native-xcode-ios-_os2PlCD.mjs} +27 -32
- package/dist/offload-worker.d.mts +65 -2
- package/dist/offload-worker.mjs +305 -73
- package/dist/operation-exec-BW0em61G.mjs +171 -0
- package/dist/{ownership-DEmM5vhh.mjs → ownership-C5fo5Edb.mjs} +3 -3
- package/dist/{page-CPaHeYqj.mjs → page-DBL2PiUU.mjs} +8 -4
- package/dist/{placement-log-BqGbTa61.mjs → placement-log-BnGgJRRZ.mjs} +1 -1
- package/dist/{plan-placement-Bwj-IjY1.mjs → plan-placement-BU42oOyT.mjs} +12 -8
- package/dist/{ports-D-MmCTdv.mjs → ports-BSmQKzW7.mjs} +3 -3
- package/dist/{prebuild-uD_ipCxF.mjs → prebuild-BFm63bQ0.mjs} +63 -16
- package/dist/{project-BZNjmW_8.mjs → project-CTLNHCfE.mjs} +16 -16
- package/dist/{pull-request-Lfetn8Kr.mjs → pull-request-W1IuLXDt.mjs} +102 -20
- package/dist/{pull-request-zjCSAryH.d.mts → pull-request-eODyIqZF.d.mts} +6 -2
- package/dist/pull-requests.d.mts +2 -2
- package/dist/pull-requests.mjs +2 -2
- package/dist/{react-native-android-DuzH0cn3.mjs → react-native-android-CFgUroxW.mjs} +73 -53
- package/dist/{react-native-build-rhk4smwQ.mjs → react-native-build-CmJkTljQ.mjs} +8 -8
- package/dist/{react-native-doctor-CQpuIMso.mjs → react-native-doctor-LTF-Xt8l.mjs} +7 -7
- package/dist/{react-native-ios-Bhq1rJeV.mjs → react-native-ios-ChBF1MkB.mjs} +43 -29
- package/dist/{recordings-DYteQSu4.mjs → recordings-CQmfzAh7.mjs} +1 -1
- package/dist/registration-BcRgF6Ox.mjs +51 -0
- package/dist/{reload-CmTRFJaX.mjs → reload-DF9hqUUB.mjs} +16 -15
- package/dist/{reload-DzjSl5P_.mjs → reload-RBi1nj6Z.mjs} +1 -1
- package/dist/{remote-ByYkXdGD.mjs → remote-BmvMCcJw.mjs} +18 -15
- package/dist/{result-DNiS-tTP.mjs → result-Cx5CRLvS.mjs} +24 -23
- package/dist/{run-D23HmJMs.mjs → run-DidHghD1.mjs} +3 -2
- package/dist/{selection-CSl9i5qU.mjs → selection-DSMbWEX2.mjs} +6 -5
- package/dist/{server-bare-CxTrvDg8.mjs → server-bare-CafzuyFB.mjs} +2 -2
- package/dist/{server-command-CtzloH3v.mjs → server-command-BTiGPEjS.mjs} +5 -5
- package/dist/server-expo-C-Buqvn_.mjs +2 -0
- package/dist/{server-expo-DT_F_uVI.mjs → server-expo-D9lEz4aq.mjs} +7 -5
- package/dist/{settings-CsJDEXgK.mjs → settings-C7O8A6ZI.mjs} +223 -243
- package/dist/{settings-T1gtc3bf.mjs → settings-eKF_vJJs.mjs} +1 -1
- package/dist/{settings-write-HyVZ8eQ8.mjs → settings-write-79mQRr9C.mjs} +7 -6
- package/dist/settings.schema.json +13 -0
- package/dist/{simslim-Cgk17SXV.mjs → simslim-BNckWpXi.mjs} +2 -2
- package/dist/{slot-launch-DF8VfT64.mjs → slot-launch-D7M2nE4b.mjs} +6 -6
- package/dist/{stage-BIS0ydsV.mjs → stage-DKNM4ZPd.mjs} +21 -4
- package/dist/{start-RChynRDp.mjs → start-CRfN1kWV.mjs} +1 -1
- package/dist/{start-7lpH7hmN.mjs → start-bjfiScgz.mjs} +622 -393
- package/dist/{state-C4ZtUisW.mjs → state--smjFSTL.mjs} +22 -7
- package/dist/{state-XMziexv2.mjs → state-08I3hw3_.mjs} +2 -2
- package/dist/{stats-53T7OTb9.mjs → stats-DioA6cBU.mjs} +5 -5
- package/dist/{stats-report-BFfMpWWF.mjs → stats-report-BzaPrRm6.mjs} +1 -1
- package/dist/{status-DqEUSSxs.mjs → status-BRROykC8.mjs} +2 -2
- package/dist/{status-CSi6tlRq.mjs → status-Eqx_20WC.mjs} +1 -1
- package/dist/{status-BL7jVyAB.mjs → status-kzbeWk4x.mjs} +43 -29
- package/dist/{status-watch-GDCfqpTE.mjs → status-watch-BIqqbJ6q.mjs} +48 -16
- package/dist/status-worker.mjs +1 -1
- package/dist/{stim-desktop-C12we7lz.mjs → stim-desktop-D5OGpYZM.mjs} +2 -2
- package/dist/{stop-Bk_1qiV6.mjs → stop-B18d8KDw.mjs} +2 -2
- package/dist/{stop-CPzlM1L-.mjs → stop-B2uLQfoM.mjs} +15 -16
- package/dist/{stop-D7h6jAXQ.mjs → stop-DzLJc7Ei.mjs} +31 -22
- package/dist/supervisor-run.d.mts +3 -3
- package/dist/supervisor-run.mjs +28 -36
- package/dist/{support-Dlu37g6H.mjs → support-BTpZ6RAJ.mjs} +15 -11
- package/dist/{support-67vraa1Q.mjs → support-DTBV56gy.mjs} +8 -8
- package/dist/swiftpm-artifact-inputs-DFcGsXDP.mjs +153 -0
- package/dist/swiftpm-macos-r_WSKlXr.mjs +532 -0
- package/dist/{toolchain-Hp9sCpK4.mjs → toolchain-CWeGJgCc.mjs} +9 -6
- package/dist/toolchain-GVgnf8d1.mjs +2 -0
- package/dist/{trigger-BAahxUPq.mjs → trigger-Cbyc41d_.mjs} +3 -3
- package/dist/trigger-yb_nFzPF.mjs +2 -0
- package/dist/{warm-progress-BHX5r0Ei.mjs → warm-progress-CA3TgT3-.mjs} +29 -16
- package/dist/web-Cduk0NSE.mjs +2 -0
- package/dist/{web-CJmIj6AI.mjs → web-CwREhjD-.mjs} +11 -10
- package/dist/web-run.mjs +11 -9
- package/dist/windows-report.mjs +1 -1
- package/dist/{workspace-state-Cjuigeeu.mjs → workspace-state-U08dEzhG.mjs} +3 -3
- package/dist/{workspaces-BccMFmGQ.mjs → workspaces-BxzYlo3P.mjs} +594 -266
- package/dist/{worktree-DQ6Zi2Mv.mjs → worktree-8lqn_lBp.mjs} +850 -551
- package/dist/worktree-BxfVxG_b.mjs +2 -0
- package/dist/{xcode-Zlx3qjEs.mjs → xcode-iQnROM5o.mjs} +5 -5
- package/package.json +7 -7
- package/shim/android-audio/Main.java +24 -0
- package/shim/android-audio/README.md +17 -0
- package/shim/android-audio.jar +0 -0
- package/dist/gc-C-WwL0yi.mjs +0 -3
- package/dist/git-C3iTsJaZ.mjs +0 -73
- package/dist/server-expo-C-90EhcG.mjs +0 -2
- package/dist/settings-DkdmlelE.d.mts +0 -15
- package/dist/swiftpm-macos-p8WZbxKU.mjs +0 -130
- package/dist/toolchain-DhKwp9iI.mjs +0 -2
- package/dist/trigger-Bf3uDcpv.mjs +0 -2
- package/dist/web-DknQETPs.mjs +0 -2
- package/dist/workspace-process-lock-1_1OoVW5.mjs +0 -57
- package/dist/worktree-CK2MpJW5.mjs +0 -2
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import { c as
|
|
1
|
+
import { a as workspaceProcessLockError, c as declareSpawnsOn, f as stopDeclaringSpawnsOn, g as process_identity_exports, i as withWorkspaceProcessLock, o as workspaceProcessLockPath, r as WorkspaceProcessCleanupError, t as getExecutor, x as ownership_claim_exports } from "./exec-DEoAhcze.mjs";
|
|
2
2
|
import { d as workspaceDir, h as workspaceName$1, l as workspaceAgentDeviceDir } from "./paths-DQtTgsBf.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { F as isOnMountedVolume, G as projectDeviceSlots, H as deviceSlotPlatforms, I as listMountedVolumes, L as measuredDirectorySizeAsync, O as withConfigLockAsync$1, P as formatBytes, _ as loadConfig$1, d as getConfigDir, g as isPathPrefix, j as acquireAvdClaim, p as getProject } from "./config-DlrsduwG.mjs";
|
|
4
4
|
import { t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
|
|
5
|
-
import { a as formatElapsed, l as phaseLine } from "./command-output-
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
5
|
+
import { a as formatElapsed, l as phaseLine } from "./command-output-Db8BqzxH.mjs";
|
|
6
|
+
import { f as throwIfCancelled, t as runOperationFile, u as cancellableSleep } from "./operation-exec-BW0em61G.mjs";
|
|
7
|
+
import { l as withWorkspaceStateLockAsync, r as readWorkspaceState$1, u as workspaceLastUsed } from "./workspace-state-U08dEzhG.mjs";
|
|
8
|
+
import { Nt as repoRootAsync, ut as resolveTailscaleBinary } from "./settings-C7O8A6ZI.mjs";
|
|
9
|
+
import { a as macosRuntimeClaim, n as macosDir } from "./state-08I3hw3_.mjs";
|
|
10
|
+
import { c as pidExists, f as signalProcessTree, p as signalProcessTreeAsync } from "./metro-DiXtA3Ty.mjs";
|
|
11
|
+
import { _ as chromeProcessStateAsync, c as webClaimRoot, i as clearWebRecord, o as readWebRecord, p as webProfileDir, t as CDP_PORT_LABEL, u as webFacts, v as liveProfileHolder, y as removeSingletonFiles } from "./state--smjFSTL.mjs";
|
|
11
12
|
import { t as canonicalPath } from "./paths-BsJMoPP2.mjs";
|
|
12
|
-
import { N as listBuildLocks, d as readAgentDeviceRecords, t as agentDeviceDaemon } from "./activity-
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { E as resolveOwnedIosSim, O as shutdownIosSim, S as renameIosSim, c as eraseIosSim, f as listAllIosSimsAsync, o as deleteIosSim, p as listIosDeviceTypesAsync, s as deleteParkedIosSim, v as parkedSimName, y as parseRuntimeVersion } from "./ios-
|
|
16
|
-
import {
|
|
17
|
-
import { r as releaseBrowserPort } from "./named-ports-BWTzt6mu.mjs";
|
|
13
|
+
import { N as listBuildLocks, d as readAgentDeviceRecords, t as agentDeviceDaemon } from "./activity-B5RtpFda.mjs";
|
|
14
|
+
import { i as recordCreatedDevice, r as readCreatedDevices$1, t as forgetCreatedDevice } from "./created-devices-x9rN6bhs.mjs";
|
|
15
|
+
import { H as ownedAvdMatchesConfiguration, K as ownedAvdSystemImage, L as listOrphanedAvdDirectories, S as deleteAvd, V as ownedAvdDirectory, at as waitForAndroidEmulatorShutdown, et as resolveOwnedAvdSerialAsync, g as avdPathExists, gt as parkSim, j as listAdbDevicesAsync, lt as isStimOwnedAvd, mt as eraseParkedAfter, nt as shutdownAndroidEmulator, p as assertOwnedAvdStoppedAsync, st as wipeAvdUserData, v as avdStorageRoots, vt as readParked, yt as removeParkedAfter } from "./android-C7sVmyI9.mjs";
|
|
16
|
+
import { E as resolveOwnedIosSim, O as shutdownIosSim, S as renameIosSim, c as eraseIosSim, f as listAllIosSimsAsync, o as deleteIosSim, p as listIosDeviceTypesAsync, s as deleteParkedIosSim, v as parkedSimName, y as parseRuntimeVersion } from "./ios-hcBpnEGk.mjs";
|
|
17
|
+
import { r as releaseBrowserPort } from "./named-ports-DkEzOfzO.mjs";
|
|
18
18
|
import { createHash, randomUUID } from "node:crypto";
|
|
19
19
|
import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
|
|
20
20
|
import { isDeepStrictEqual } from "node:util";
|
|
@@ -24,26 +24,35 @@ import { ambientContext, ambientEnv, ambientWrite } from "@stim-cli/core";
|
|
|
24
24
|
import { buildSlotsDir, hostedNativeRecords, isJsonObject, macosAppState, parseHostedNativePlacement, readJsonFile, readMacosRecord, requireAutomaticMachine, tryAcquireBuildSlotClaim, unreadableHostedNative } from "@stim-cli/core/state";
|
|
25
25
|
import { basename as basename$1, isAbsolute as isAbsolute$1, join as join$1, relative as relative$1 } from "path";
|
|
26
26
|
import chalk from "chalk";
|
|
27
|
-
import { existsSync as existsSync$1, lstatSync as lstatSync$1, readdirSync as readdirSync$1,
|
|
27
|
+
import { existsSync as existsSync$1, lstatSync as lstatSync$1, readdirSync as readdirSync$1, statSync as statSync$1 } from "fs";
|
|
28
|
+
import { lstat, readFile, readdir, rm, stat, unlink } from "node:fs/promises";
|
|
28
29
|
import { resolve4 } from "node:dns/promises";
|
|
29
30
|
import { request } from "node:https";
|
|
30
31
|
//#region src/commands/gc/results.ts
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
kind,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
32
|
+
function createGcResults() {
|
|
33
|
+
const results = [];
|
|
34
|
+
return {
|
|
35
|
+
record(kind, status, label, { id = null, bytes = null, detail = null } = {}) {
|
|
36
|
+
results.push({
|
|
37
|
+
kind,
|
|
38
|
+
status,
|
|
39
|
+
label,
|
|
40
|
+
id,
|
|
41
|
+
bytes,
|
|
42
|
+
detail
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
take() {
|
|
46
|
+
return results.splice(0);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
44
49
|
}
|
|
50
|
+
const detachedResults = createGcResults();
|
|
51
|
+
const recordGcResult = detachedResults.record;
|
|
52
|
+
const takeGcResults = detachedResults.take;
|
|
45
53
|
const GC_OUTPUT = {
|
|
46
54
|
log: (line) => console.log(line),
|
|
55
|
+
error: (line) => console.error(line),
|
|
47
56
|
record: recordGcResult,
|
|
48
57
|
fail: () => {
|
|
49
58
|
process.exitCode = 1;
|
|
@@ -205,12 +214,18 @@ function parseDu(output) {
|
|
|
205
214
|
}
|
|
206
215
|
return sizes;
|
|
207
216
|
}
|
|
208
|
-
async function measureDu(args) {
|
|
217
|
+
async function measureDu(args, operation) {
|
|
209
218
|
let output = "";
|
|
210
219
|
let complete = true;
|
|
211
220
|
try {
|
|
212
|
-
output = await getExecutor()
|
|
221
|
+
output = await runOperationFile(operation, getExecutor(), "du", args, {
|
|
222
|
+
timeoutMs: 2e4,
|
|
223
|
+
cwd: operation?.cwd,
|
|
224
|
+
signal: operation?.signal
|
|
225
|
+
});
|
|
213
226
|
} catch (error) {
|
|
227
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
228
|
+
operation?.signal.throwIfAborted();
|
|
214
229
|
complete = false;
|
|
215
230
|
const failure = error;
|
|
216
231
|
if (failure.status === 1 && !failure.code && typeof failure.stdout === "string") output = failure.stdout;
|
|
@@ -349,9 +364,10 @@ function explainFailure(error) {
|
|
|
349
364
|
function report(message) {
|
|
350
365
|
ambientWrite("stderr", `${phaseLine("device", message)}\n`);
|
|
351
366
|
}
|
|
352
|
-
async function closeOwnedDeviceSessions(device, stillOwned, workspace, repoRoot) {
|
|
367
|
+
async function closeOwnedDeviceSessions(device, stillOwned, workspace, repoRoot, operation) {
|
|
353
368
|
const exec = getExecutor();
|
|
354
369
|
try {
|
|
370
|
+
operation?.signal.throwIfAborted();
|
|
355
371
|
if (!exec.findExecutable("agent-device")) return;
|
|
356
372
|
const root = workspace === void 0 ? void 0 : canonical(workspace);
|
|
357
373
|
if (root === null) return;
|
|
@@ -368,7 +384,7 @@ async function closeOwnedDeviceSessions(device, stillOwned, workspace, repoRoot)
|
|
|
368
384
|
const remaining = deadline - Date.now();
|
|
369
385
|
if (remaining <= 0) throw new Error("agent-device cleanup timed out");
|
|
370
386
|
try {
|
|
371
|
-
return await exec
|
|
387
|
+
return await runOperationFile(operation, exec, "agent-device", [
|
|
372
388
|
...args,
|
|
373
389
|
...dir ? ["--state-dir", dir] : [],
|
|
374
390
|
"--json",
|
|
@@ -379,6 +395,8 @@ async function closeOwnedDeviceSessions(device, stillOwned, workspace, repoRoot)
|
|
|
379
395
|
killSignal: "SIGKILL"
|
|
380
396
|
});
|
|
381
397
|
} catch (error) {
|
|
398
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
399
|
+
operation?.signal.throwIfAborted();
|
|
382
400
|
throw explainFailure(error);
|
|
383
401
|
}
|
|
384
402
|
};
|
|
@@ -406,27 +424,35 @@ async function closeOwnedDeviceSessions(device, stillOwned, workspace, repoRoot)
|
|
|
406
424
|
]))?.success !== true) throw new Error("agent-device did not confirm session close");
|
|
407
425
|
report(`closed agent-device session ${session.name} on ${session.id}`);
|
|
408
426
|
} catch (error) {
|
|
427
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
428
|
+
operation?.signal.throwIfAborted();
|
|
409
429
|
report(`could not close agent-device session ${session.name} on ${session.id}: ${error.message}`);
|
|
410
430
|
}
|
|
411
431
|
} catch (error) {
|
|
432
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
433
|
+
operation?.signal.throwIfAborted();
|
|
412
434
|
report(`could not list agent-device sessions for ${device.platform === "ios" ? device.id : device.id ?? device.avdName}: ${error.message}`);
|
|
413
435
|
}
|
|
414
436
|
}
|
|
415
437
|
} catch (error) {
|
|
438
|
+
if (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error) throw error;
|
|
439
|
+
operation?.signal.throwIfAborted();
|
|
416
440
|
report(`could not list agent-device sessions for ${device.platform === "ios" ? device.id : device.id ?? device.avdName}: ${error.message}`);
|
|
417
441
|
}
|
|
418
442
|
}
|
|
419
443
|
//#endregion
|
|
420
444
|
//#region src/devices/teardown.ts
|
|
421
|
-
async function teardownParkedAvd(avdName) {
|
|
445
|
+
async function teardownParkedAvd(avdName, { operation } = {}) {
|
|
422
446
|
try {
|
|
447
|
+
operation?.signal.throwIfAborted();
|
|
423
448
|
return await removeParkedAfter("android", avdName, async () => {
|
|
424
449
|
const result = await teardownOwnedAvd(avdName, {
|
|
425
450
|
del: true,
|
|
426
|
-
owner: { pool: true }
|
|
451
|
+
owner: { pool: true },
|
|
452
|
+
operation
|
|
427
453
|
});
|
|
428
454
|
if (result.status !== "torn-down" && result.status !== "missing") throw new Error(result.reason);
|
|
429
|
-
}) ? {
|
|
455
|
+
}, operation) ? {
|
|
430
456
|
status: "torn-down",
|
|
431
457
|
label: avdName
|
|
432
458
|
} : {
|
|
@@ -435,21 +461,24 @@ async function teardownParkedAvd(avdName) {
|
|
|
435
461
|
reason: "emulator is no longer parked"
|
|
436
462
|
};
|
|
437
463
|
} catch (error) {
|
|
464
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
465
|
+
operation?.signal.throwIfAborted();
|
|
438
466
|
return {
|
|
439
467
|
status: "failed",
|
|
440
468
|
reason: String(error?.message || error)
|
|
441
469
|
};
|
|
442
470
|
}
|
|
443
471
|
}
|
|
444
|
-
async function teardownParkedIosSim(udid, { label, deleteSim = deleteParkedIosSim } = {}) {
|
|
472
|
+
async function teardownParkedIosSim(udid, { label, deleteSim = deleteParkedIosSim, operation } = {}) {
|
|
445
473
|
try {
|
|
474
|
+
operation?.signal.throwIfAborted();
|
|
446
475
|
const removed = await removeParkedAfter("ios", udid, async () => {
|
|
447
476
|
await closeOwnedDeviceSessions({
|
|
448
477
|
platform: "ios",
|
|
449
478
|
id: udid
|
|
450
|
-
}, async () => Boolean((await resolveOwnedIosSimNow(udid)).sim));
|
|
451
|
-
await deleteSim(udid);
|
|
452
|
-
});
|
|
479
|
+
}, async () => Boolean((await resolveOwnedIosSimNow(udid, operation)).sim), void 0, void 0, operation);
|
|
480
|
+
await deleteSim(udid, operation);
|
|
481
|
+
}, operation);
|
|
453
482
|
if (!removed) return {
|
|
454
483
|
status: "skipped",
|
|
455
484
|
kind: "not-parked",
|
|
@@ -460,27 +489,34 @@ async function teardownParkedIosSim(udid, { label, deleteSim = deleteParkedIosSi
|
|
|
460
489
|
label: label ?? removed.name
|
|
461
490
|
};
|
|
462
491
|
} catch (error) {
|
|
492
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
493
|
+
operation?.signal.throwIfAborted();
|
|
463
494
|
return {
|
|
464
495
|
status: "failed",
|
|
465
496
|
reason: String(error?.message || error)
|
|
466
497
|
};
|
|
467
498
|
}
|
|
468
499
|
}
|
|
469
|
-
async function eraseParkedIosSim(udid, { label } = {}) {
|
|
500
|
+
async function eraseParkedIosSim(udid, { label, operation } = {}) {
|
|
470
501
|
try {
|
|
502
|
+
operation?.signal.throwIfAborted();
|
|
471
503
|
const erased = await eraseParkedAfter("ios", udid, async (_record, eraseStarted) => {
|
|
472
504
|
await closeOwnedDeviceSessions({
|
|
473
505
|
platform: "ios",
|
|
474
506
|
id: udid
|
|
475
|
-
}, async () => Boolean((await resolveOwnedIosSimNow(udid)).sim));
|
|
476
|
-
const resolved = resolveOwnedIosSim(udid, await listAllIosSimsAsync(
|
|
507
|
+
}, async () => Boolean((await resolveOwnedIosSimNow(udid, operation)).sim), void 0, void 0, operation);
|
|
508
|
+
const resolved = resolveOwnedIosSim(udid, await listAllIosSimsAsync({
|
|
509
|
+
signal: operation?.signal,
|
|
510
|
+
operation
|
|
511
|
+
}));
|
|
477
512
|
if (resolved.missing) throw new Error(`simulator ${udid} is not on this machine`);
|
|
478
513
|
if (resolved.notOwned) throw new Error(`simulator ${udid} is now named ${JSON.stringify(resolved.notOwned)}`);
|
|
479
514
|
const state = resolved.sim.state;
|
|
480
515
|
if (state !== "Shutdown") throw new Error(`simulator ${udid} is ${state}, not Shutdown; it was kept`);
|
|
516
|
+
operation?.signal.throwIfAborted();
|
|
481
517
|
eraseStarted();
|
|
482
|
-
await eraseIosSim(udid);
|
|
483
|
-
});
|
|
518
|
+
await eraseIosSim(udid, operation);
|
|
519
|
+
}, operation);
|
|
484
520
|
if (!erased) return {
|
|
485
521
|
status: "skipped",
|
|
486
522
|
kind: "not-parked",
|
|
@@ -491,23 +527,31 @@ async function eraseParkedIosSim(udid, { label } = {}) {
|
|
|
491
527
|
label: label ?? erased.name
|
|
492
528
|
};
|
|
493
529
|
} catch (error) {
|
|
530
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
531
|
+
operation?.signal.throwIfAborted();
|
|
494
532
|
return {
|
|
495
533
|
status: "failed",
|
|
496
534
|
reason: String(error?.message || error)
|
|
497
535
|
};
|
|
498
536
|
}
|
|
499
537
|
}
|
|
500
|
-
async function eraseParkedAvd(avdName) {
|
|
538
|
+
async function eraseParkedAvd(avdName, { operation } = {}) {
|
|
501
539
|
try {
|
|
540
|
+
operation?.signal.throwIfAborted();
|
|
502
541
|
return await eraseParkedAfter("android", avdName, async (_record, eraseStarted) => {
|
|
503
|
-
if ((await resolveOwnedAvdSerialAsync(avdName, {
|
|
542
|
+
if ((await resolveOwnedAvdSerialAsync(avdName, {
|
|
543
|
+
timeoutMs: AVD_INVENTORY_TIMEOUT_MS,
|
|
544
|
+
operation,
|
|
545
|
+
signal: operation?.signal
|
|
546
|
+
})).serial) throw new Error(`AVD ${avdName} is running; it was kept`);
|
|
504
547
|
const result = await teardownOwnedAvd(avdName, {
|
|
505
548
|
wipe: true,
|
|
506
549
|
owner: { pool: true },
|
|
507
|
-
onWipeStart: eraseStarted
|
|
550
|
+
onWipeStart: eraseStarted,
|
|
551
|
+
operation
|
|
508
552
|
});
|
|
509
553
|
if (result.status !== "torn-down") throw new Error(result.reason ?? `AVD ${avdName} is ${result.status}`);
|
|
510
|
-
}) ? {
|
|
554
|
+
}, operation) ? {
|
|
511
555
|
status: "torn-down",
|
|
512
556
|
label: avdName
|
|
513
557
|
} : {
|
|
@@ -516,6 +560,8 @@ async function eraseParkedAvd(avdName) {
|
|
|
516
560
|
reason: "emulator is no longer parked"
|
|
517
561
|
};
|
|
518
562
|
} catch (error) {
|
|
563
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
564
|
+
operation?.signal.throwIfAborted();
|
|
519
565
|
return {
|
|
520
566
|
status: "failed",
|
|
521
567
|
reason: String(error?.message || error)
|
|
@@ -525,9 +571,13 @@ async function eraseParkedAvd(avdName) {
|
|
|
525
571
|
const IOS_SHUTDOWN_SETTLE_MS = 15e3;
|
|
526
572
|
const ADB_RELEASE_WAIT_MS = 3e3;
|
|
527
573
|
const AVD_INVENTORY_TIMEOUT_MS = 15e3;
|
|
528
|
-
async function repoRootAbove(workspace) {
|
|
574
|
+
async function repoRootAbove(workspace, operation) {
|
|
529
575
|
if (workspace === void 0 || !getExecutor().findExecutable("agent-device")) return void 0;
|
|
530
|
-
const root = await repoRootAsync(workspace
|
|
576
|
+
const root = await repoRootAsync(workspace, {
|
|
577
|
+
declareFor: operation,
|
|
578
|
+
env: operation?.env,
|
|
579
|
+
signal: operation?.signal
|
|
580
|
+
});
|
|
531
581
|
try {
|
|
532
582
|
return root && realpathSync(root) !== realpathSync(workspace) ? root : void 0;
|
|
533
583
|
} catch {
|
|
@@ -536,37 +586,49 @@ async function repoRootAbove(workspace) {
|
|
|
536
586
|
}
|
|
537
587
|
const IOS_SHUTDOWN_POLL_MS = 250;
|
|
538
588
|
const IOS_SHUTDOWN_ATTEMPTS = 2;
|
|
539
|
-
async function resolveOwnedIosSimNow(udid) {
|
|
540
|
-
|
|
589
|
+
async function resolveOwnedIosSimNow(udid, operation) {
|
|
590
|
+
operation?.signal.throwIfAborted();
|
|
591
|
+
const sims = await listAllIosSimsAsync({
|
|
592
|
+
operation,
|
|
593
|
+
signal: operation?.signal
|
|
594
|
+
});
|
|
595
|
+
operation?.signal.throwIfAborted();
|
|
596
|
+
return resolveOwnedIosSim(udid, sims);
|
|
541
597
|
}
|
|
542
|
-
async function settleIosSimShutdown(udid, { now = Date.now, sleep
|
|
543
|
-
let resolved = await resolveOwnedIosSimNow(udid);
|
|
598
|
+
async function settleIosSimShutdown(udid, { now = Date.now, sleep }, operation) {
|
|
599
|
+
let resolved = await resolveOwnedIosSimNow(udid, operation);
|
|
544
600
|
for (let attempt = 1;; attempt++) {
|
|
545
601
|
const deadline = now() + IOS_SHUTDOWN_SETTLE_MS;
|
|
546
602
|
while (resolved.sim && resolved.sim.state !== "Shutdown" && now() < deadline) {
|
|
547
|
-
await sleep(IOS_SHUTDOWN_POLL_MS)
|
|
548
|
-
|
|
603
|
+
await (sleep ? sleep(IOS_SHUTDOWN_POLL_MS) : cancellableSleep(IOS_SHUTDOWN_POLL_MS, {
|
|
604
|
+
signal: operation?.signal,
|
|
605
|
+
what: "simulator shutdown"
|
|
606
|
+
}));
|
|
607
|
+
resolved = await resolveOwnedIosSimNow(udid, operation);
|
|
549
608
|
}
|
|
550
609
|
if (!resolved.sim || resolved.sim.state === "Shutdown" || attempt === IOS_SHUTDOWN_ATTEMPTS) return resolved;
|
|
551
|
-
await shutdownIosSim(udid);
|
|
552
|
-
resolved = await resolveOwnedIosSimNow(udid);
|
|
610
|
+
await shutdownIosSim(udid, operation);
|
|
611
|
+
resolved = await resolveOwnedIosSimNow(udid, operation);
|
|
553
612
|
}
|
|
554
613
|
}
|
|
555
614
|
function iosShutdownFailureReason(udid, state) {
|
|
556
615
|
return `simulator ${udid} is still ${state} after ${IOS_SHUTDOWN_ATTEMPTS} shutdown attempts and ${IOS_SHUTDOWN_ATTEMPTS * IOS_SHUTDOWN_SETTLE_MS / 1e3}s of waiting`;
|
|
557
616
|
}
|
|
558
|
-
async function parkOwnedIosSim(settled, udid, park) {
|
|
617
|
+
async function parkOwnedIosSim(settled, udid, park, operation) {
|
|
559
618
|
if (settled.missing) throw new Error(`simulator ${udid} disappeared after shutdown`);
|
|
560
619
|
if (settled.notOwned) throw new Error(`simulator ${udid} is now named ${JSON.stringify(settled.notOwned)}`);
|
|
561
620
|
const sim = settled.sim;
|
|
562
621
|
if (sim.state !== "Shutdown") throw new Error(iosShutdownFailureReason(udid, sim.state));
|
|
563
|
-
const model = (await listIosDeviceTypesAsync(
|
|
622
|
+
const model = (await listIosDeviceTypesAsync({
|
|
623
|
+
operation,
|
|
624
|
+
signal: operation?.signal
|
|
625
|
+
})).find((d) => d.identifier === sim.deviceTypeIdentifier)?.name ?? null;
|
|
564
626
|
const runtime = parseRuntimeVersion(sim.runtime);
|
|
565
627
|
const name = parkedSimName(sim.udid, {
|
|
566
628
|
model,
|
|
567
629
|
runtime
|
|
568
630
|
});
|
|
569
|
-
await renameIosSim(sim.udid, name);
|
|
631
|
+
await renameIosSim(sim.udid, name, operation);
|
|
570
632
|
const record = {
|
|
571
633
|
udid: sim.udid,
|
|
572
634
|
name,
|
|
@@ -588,10 +650,10 @@ async function parkOwnedIosSim(settled, udid, park) {
|
|
|
588
650
|
})
|
|
589
651
|
};
|
|
590
652
|
}
|
|
591
|
-
async function teardownOwnedIosSim(udid, { del = false, label, park, workspace, shutdownClock = {} } = {}) {
|
|
653
|
+
async function teardownOwnedIosSim(udid, { del = false, label, park, workspace, shutdownClock = {}, operation } = {}) {
|
|
592
654
|
let parkFallback;
|
|
593
655
|
try {
|
|
594
|
-
const resolved = await resolveOwnedIosSimNow(udid);
|
|
656
|
+
const resolved = await resolveOwnedIosSimNow(udid, operation);
|
|
595
657
|
if (resolved.notOwned) return {
|
|
596
658
|
status: "skipped",
|
|
597
659
|
kind: "not-owned",
|
|
@@ -601,22 +663,25 @@ async function teardownOwnedIosSim(udid, { del = false, label, park, workspace,
|
|
|
601
663
|
await closeOwnedDeviceSessions({
|
|
602
664
|
platform: "ios",
|
|
603
665
|
id: udid
|
|
604
|
-
}, async () => Boolean((await resolveOwnedIosSimNow(udid)).sim), workspace);
|
|
605
|
-
const current = await resolveOwnedIosSimNow(udid);
|
|
666
|
+
}, async () => Boolean((await resolveOwnedIosSimNow(udid, operation)).sim), workspace, void 0, operation);
|
|
667
|
+
const current = await resolveOwnedIosSimNow(udid, operation);
|
|
606
668
|
if (current.missing) return { status: "missing" };
|
|
607
669
|
if (current.notOwned) return {
|
|
608
670
|
status: "skipped",
|
|
609
671
|
kind: "not-owned",
|
|
610
672
|
reason: "simulator ownership changed before shutdown"
|
|
611
673
|
};
|
|
612
|
-
await shutdownIosSim(udid);
|
|
674
|
+
await shutdownIosSim(udid, operation);
|
|
613
675
|
const sim = resolved.sim;
|
|
614
676
|
if (del && park && park.max > 0) try {
|
|
615
|
-
const { record, evicted } = await parkOwnedIosSim(await settleIosSimShutdown(udid, shutdownClock), udid, park);
|
|
677
|
+
const { record, evicted } = await parkOwnedIosSim(await settleIosSimShutdown(udid, shutdownClock, operation), udid, park, operation);
|
|
616
678
|
const removed = [];
|
|
617
679
|
const failures = [];
|
|
618
680
|
for (const entry of evicted) {
|
|
619
|
-
const result = await teardownParkedIosSim(entry.udid, {
|
|
681
|
+
const result = await teardownParkedIosSim(entry.udid, {
|
|
682
|
+
label: entry.name,
|
|
683
|
+
operation
|
|
684
|
+
});
|
|
620
685
|
if (result.status === "torn-down") removed.push({
|
|
621
686
|
udid: entry.udid,
|
|
622
687
|
name: entry.name
|
|
@@ -634,32 +699,36 @@ async function teardownOwnedIosSim(udid, { del = false, label, park, workspace,
|
|
|
634
699
|
...failures.length ? { evictionFailures: failures } : {}
|
|
635
700
|
};
|
|
636
701
|
} catch (e) {
|
|
702
|
+
if (e instanceof WorkspaceProcessCleanupError) throw e;
|
|
703
|
+
operation?.signal.throwIfAborted();
|
|
637
704
|
parkFallback = String(e?.message || e);
|
|
638
705
|
}
|
|
639
706
|
if (del) {
|
|
640
|
-
await deleteIosSim(udid);
|
|
707
|
+
await deleteIosSim(udid, operation);
|
|
641
708
|
return {
|
|
642
709
|
status: "torn-down",
|
|
643
710
|
label: label ?? sim.name ?? udid,
|
|
644
711
|
...parkFallback ? { parkFallback } : {}
|
|
645
712
|
};
|
|
646
713
|
}
|
|
647
|
-
const settled = await settleIosSimShutdown(udid, shutdownClock);
|
|
714
|
+
const settled = await settleIosSimShutdown(udid, shutdownClock, operation);
|
|
648
715
|
if (settled.sim && settled.sim.state !== "Shutdown") return {
|
|
649
716
|
status: "failed",
|
|
650
717
|
label: label ?? sim.name ?? udid,
|
|
651
718
|
reason: iosShutdownFailureReason(udid, settled.sim.state)
|
|
652
719
|
};
|
|
653
|
-
const root = sim.state === "Shutdown" ? void 0 : await repoRootAbove(workspace);
|
|
720
|
+
const root = sim.state === "Shutdown" ? void 0 : await repoRootAbove(workspace, operation);
|
|
654
721
|
if (root && settled.sim) await closeOwnedDeviceSessions({
|
|
655
722
|
platform: "ios",
|
|
656
723
|
id: udid
|
|
657
|
-
}, async () => (await resolveOwnedIosSimNow(udid)).sim?.state === "Shutdown", workspace, root);
|
|
724
|
+
}, async () => (await resolveOwnedIosSimNow(udid, operation)).sim?.state === "Shutdown", workspace, root, operation);
|
|
658
725
|
return {
|
|
659
726
|
status: "torn-down",
|
|
660
727
|
label: label ?? sim.name ?? udid
|
|
661
728
|
};
|
|
662
729
|
} catch (e) {
|
|
730
|
+
if (e instanceof WorkspaceProcessCleanupError) throw e;
|
|
731
|
+
operation?.signal.throwIfAborted();
|
|
663
732
|
return {
|
|
664
733
|
status: "failed",
|
|
665
734
|
reason: String(e?.message || e),
|
|
@@ -677,11 +746,12 @@ function assertAvdReferences(avdName, owner) {
|
|
|
677
746
|
}
|
|
678
747
|
if (readParked("android", { config }).some((entry) => entry.name.toLowerCase() === avdName.toLowerCase()) && !(owner && "pool" in owner)) throw new Error(`AVD ${avdName} is in the emulator pool; its data was kept.`);
|
|
679
748
|
}
|
|
680
|
-
function removeOrphanedAvdDirectory(candidate, owner) {
|
|
749
|
+
async function removeOrphanedAvdDirectory(candidate, owner, operation) {
|
|
750
|
+
operation?.signal.throwIfAborted();
|
|
681
751
|
const surveyed = listOrphanedAvdDirectories(candidate.name).find((entry) => entry.directory === candidate.directory);
|
|
682
752
|
if (!surveyed || surveyed.name !== candidate.name || surveyed.dev !== candidate.dev || surveyed.ino !== candidate.ino) throw new Error(`AVD data at ${candidate.directory} changed or is registered; it was kept.`);
|
|
683
|
-
|
|
684
|
-
const detached =
|
|
753
|
+
await assertOwnedAvdStoppedAsync(candidate.name, operation, () => candidate.directory);
|
|
754
|
+
const detached = await withConfigLockAsync$1(() => {
|
|
685
755
|
const current = lstatSync(candidate.directory);
|
|
686
756
|
if (!current.isDirectory() || current.dev !== candidate.dev || current.ino !== candidate.ino) throw new Error(`AVD data at ${candidate.directory} changed or is registered; it was kept.`);
|
|
687
757
|
assertAvdReferences(candidate.name, owner);
|
|
@@ -696,17 +766,19 @@ function removeOrphanedAvdDirectory(candidate, owner) {
|
|
|
696
766
|
}
|
|
697
767
|
forgetCreatedDevice("android", candidate.name);
|
|
698
768
|
return destination;
|
|
699
|
-
});
|
|
769
|
+
}, operation?.signal);
|
|
700
770
|
const current = lstatSync(detached);
|
|
701
771
|
if (!current.isDirectory() || current.dev !== candidate.dev || current.ino !== candidate.ino) throw new Error(`Detached AVD data changed at ${detached}; it was kept.`);
|
|
772
|
+
operation?.signal.throwIfAborted();
|
|
702
773
|
try {
|
|
703
|
-
|
|
774
|
+
await rm(detached, { recursive: true });
|
|
704
775
|
} catch (error) {
|
|
705
776
|
throw new Error(`Could not remove AVD data at ${detached}; retry gc --delete: ${error.message}`, { cause: error });
|
|
706
777
|
}
|
|
707
778
|
forgetCreatedDevice("android", basename(detached, ".avd"));
|
|
708
779
|
}
|
|
709
|
-
function teardownUnregisteredAvd(avdName, del, owner, surveyed) {
|
|
780
|
+
async function teardownUnregisteredAvd(avdName, del, owner, surveyed, operation) {
|
|
781
|
+
operation?.signal.throwIfAborted();
|
|
710
782
|
const candidates = surveyed ? [surveyed] : listOrphanedAvdDirectories(avdName);
|
|
711
783
|
if (candidates.length === 0) {
|
|
712
784
|
if (avdStorageRoots().some((root) => avdPathExists(join(root, `${avdName}.ini`)) || avdPathExists(join(root, `${avdName}.avd`)))) throw new Error(`AVD ${avdName} is not listed but its storage remains; it was kept.`);
|
|
@@ -718,7 +790,7 @@ function teardownUnregisteredAvd(avdName, del, owner, surveyed) {
|
|
|
718
790
|
};
|
|
719
791
|
for (const candidate of candidates) {
|
|
720
792
|
if (candidate.name !== avdName) throw new Error(`AVD data does not match ${avdName}; it was kept.`);
|
|
721
|
-
removeOrphanedAvdDirectory(candidate, owner);
|
|
793
|
+
await removeOrphanedAvdDirectory(candidate, owner, operation);
|
|
722
794
|
}
|
|
723
795
|
return {
|
|
724
796
|
status: "torn-down",
|
|
@@ -727,37 +799,65 @@ function teardownUnregisteredAvd(avdName, del, owner, surveyed) {
|
|
|
727
799
|
}
|
|
728
800
|
async function teardownOwnedAvd(avdName, options = {}) {
|
|
729
801
|
let claim;
|
|
802
|
+
let retained = false;
|
|
803
|
+
const operation = options.operation;
|
|
730
804
|
try {
|
|
805
|
+
operation?.signal.throwIfAborted();
|
|
731
806
|
if (!isStimOwnedAvd(avdName)) return {
|
|
732
807
|
status: "skipped",
|
|
733
808
|
kind: "not-owned",
|
|
734
809
|
reason: `AVD ${avdName} is not Stim-owned: Stim has no record of creating it`
|
|
735
810
|
};
|
|
736
811
|
claim = acquireAvdClaim(avdName);
|
|
812
|
+
if (operation) declareSpawnsOn(operation, claim);
|
|
737
813
|
const result = await teardownClaimedAvd(avdName, claim, options);
|
|
814
|
+
operation?.signal.throwIfAborted();
|
|
738
815
|
if (result.status === "torn-down" || result.status === "missing") options.onRemoved?.();
|
|
739
816
|
return result;
|
|
740
817
|
} catch (error) {
|
|
818
|
+
if (claim && (error instanceof WorkspaceProcessCleanupError || error instanceof Error && "reportCleanup" in error)) {
|
|
819
|
+
retained = true;
|
|
820
|
+
throw Object.assign(new WorkspaceProcessCleanupError([.../* @__PURE__ */ new Set([claim, ...error instanceof WorkspaceProcessCleanupError ? error.claims : []])], error.message, "Verify the AVD operation and its descendants have stopped."), { reportCleanup: true });
|
|
821
|
+
}
|
|
822
|
+
operation?.signal.throwIfAborted();
|
|
741
823
|
return {
|
|
742
824
|
status: "failed",
|
|
743
825
|
reason: String(error?.message || error)
|
|
744
826
|
};
|
|
745
827
|
} finally {
|
|
746
|
-
(
|
|
828
|
+
if (operation) stopDeclaringSpawnsOn(operation, claim);
|
|
829
|
+
if (!retained) (0, ownership_claim_exports.releaseClaim)(claim);
|
|
747
830
|
}
|
|
748
831
|
}
|
|
749
|
-
async function serialFree(serial, timeoutMs = AVD_INVENTORY_TIMEOUT_MS) {
|
|
750
|
-
|
|
832
|
+
async function serialFree(serial, timeoutMs = AVD_INVENTORY_TIMEOUT_MS, operation) {
|
|
833
|
+
operation?.signal.throwIfAborted();
|
|
834
|
+
const adb = await listAdbDevicesAsync({
|
|
835
|
+
timeoutMs,
|
|
836
|
+
operation,
|
|
837
|
+
signal: operation?.signal
|
|
838
|
+
});
|
|
839
|
+
operation?.signal.throwIfAborted();
|
|
751
840
|
return ![
|
|
752
841
|
...adb.emulators,
|
|
753
842
|
...adb.physical,
|
|
754
843
|
...adb.unhealthy
|
|
755
844
|
].some((entry) => entry.serial === serial);
|
|
756
845
|
}
|
|
757
|
-
async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, park, owner, workspace, orphanedDirectory, onlyIfMissing = false, onWipeStart, waitForShutdown = waitForAndroidEmulatorShutdown, assertStopped =
|
|
846
|
+
async function teardownClaimedAvd(avdName, claim, { operation, del = false, wipe = false, park, owner, workspace, orphanedDirectory, onlyIfMissing = false, onWipeStart, waitForShutdown = waitForAndroidEmulatorShutdown, assertStopped = (name) => assertOwnedAvdStoppedAsync(name, operation), resolveAvd: resolve = (name) => resolveOwnedAvdSerialAsync(name, {
|
|
847
|
+
timeoutMs: AVD_INVENTORY_TIMEOUT_MS,
|
|
848
|
+
operation,
|
|
849
|
+
signal: operation?.signal
|
|
850
|
+
}) }) {
|
|
758
851
|
let parkFallback;
|
|
852
|
+
const resolveAvd = async (name) => {
|
|
853
|
+
operation?.signal.throwIfAborted();
|
|
854
|
+
const result = await resolve(name);
|
|
855
|
+
operation?.signal.throwIfAborted();
|
|
856
|
+
return result;
|
|
857
|
+
};
|
|
759
858
|
try {
|
|
760
|
-
|
|
859
|
+
operation?.signal.throwIfAborted();
|
|
860
|
+
if (del || wipe) await withConfigLockAsync$1(() => assertAvdReferences(avdName, owner), operation?.signal);
|
|
761
861
|
const resolved = await resolveAvd(avdName);
|
|
762
862
|
if (resolved.notOwned) return {
|
|
763
863
|
status: "skipped",
|
|
@@ -766,7 +866,7 @@ async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, p
|
|
|
766
866
|
};
|
|
767
867
|
if (resolved.missing) {
|
|
768
868
|
if (wipe) throw new Error(`AVD ${avdName} is not registered; its data was kept.`);
|
|
769
|
-
return teardownUnregisteredAvd(avdName, del, owner, orphanedDirectory);
|
|
869
|
+
return await teardownUnregisteredAvd(avdName, del, owner, orphanedDirectory, operation);
|
|
770
870
|
}
|
|
771
871
|
if (onlyIfMissing) return {
|
|
772
872
|
status: "skipped",
|
|
@@ -779,7 +879,7 @@ async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, p
|
|
|
779
879
|
const serial = resolved.serial;
|
|
780
880
|
const stillStopped = async (sessionSerial) => {
|
|
781
881
|
const current = await resolveAvd(avdName);
|
|
782
|
-
return !current.notOwned && !current.missing && !current.serial && await serialFree(sessionSerial);
|
|
882
|
+
return !current.notOwned && !current.missing && !current.serial && await serialFree(sessionSerial, AVD_INVENTORY_TIMEOUT_MS, operation);
|
|
783
883
|
};
|
|
784
884
|
if (serial) {
|
|
785
885
|
const stillOwned = async () => {
|
|
@@ -790,44 +890,48 @@ async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, p
|
|
|
790
890
|
platform: "android",
|
|
791
891
|
id: serial,
|
|
792
892
|
avdName
|
|
793
|
-
}, stillOwned, workspace);
|
|
893
|
+
}, stillOwned, workspace, void 0, operation);
|
|
794
894
|
if (!await stillOwned()) throw new Error(`Owned AVD ${avdName} changed before shutdown; it was kept.`);
|
|
795
895
|
await waitForShutdown(avdName, async (timeoutMs) => {
|
|
796
|
-
(0, ownership_claim_exports.markClaimChildPending)(claim);
|
|
896
|
+
if (!operation) (0, ownership_claim_exports.markClaimChildPending)(claim);
|
|
797
897
|
try {
|
|
798
|
-
await shutdownAndroidEmulator(serial, timeoutMs);
|
|
898
|
+
await shutdownAndroidEmulator(serial, timeoutMs, operation);
|
|
799
899
|
} finally {
|
|
800
|
-
(0, ownership_claim_exports.clearClaimChild)(claim);
|
|
900
|
+
if (!operation) (0, ownership_claim_exports.clearClaimChild)(claim);
|
|
801
901
|
}
|
|
802
|
-
});
|
|
803
|
-
const root = await repoRootAbove(workspace);
|
|
902
|
+
}, { operation });
|
|
903
|
+
const root = await repoRootAbove(workspace, operation);
|
|
804
904
|
if (root) {
|
|
805
905
|
const deadline = Date.now() + ADB_RELEASE_WAIT_MS;
|
|
806
|
-
while (!await serialFree(serial, Math.max(1, deadline - Date.now())) && Date.now() < deadline) await
|
|
906
|
+
while (!await serialFree(serial, Math.max(1, deadline - Date.now()), operation) && Date.now() < deadline) await cancellableSleep(250, {
|
|
907
|
+
signal: operation?.signal,
|
|
908
|
+
what: "emulator release"
|
|
909
|
+
});
|
|
807
910
|
await closeOwnedDeviceSessions({
|
|
808
911
|
platform: "android",
|
|
809
912
|
id: serial,
|
|
810
913
|
avdName
|
|
811
|
-
}, stillStopped, workspace, root);
|
|
914
|
+
}, stillStopped, workspace, root, operation);
|
|
812
915
|
}
|
|
813
916
|
} else {
|
|
814
917
|
await closeOwnedDeviceSessions({
|
|
815
918
|
platform: "android",
|
|
816
919
|
id: null,
|
|
817
920
|
avdName
|
|
818
|
-
}, stillStopped, workspace);
|
|
819
|
-
await waitForShutdown(avdName, null);
|
|
921
|
+
}, stillStopped, workspace, void 0, operation);
|
|
922
|
+
await waitForShutdown(avdName, null, { operation });
|
|
820
923
|
}
|
|
821
924
|
if (wipe && !del) {
|
|
822
925
|
const current = await resolveAvd(avdName);
|
|
823
926
|
if (current.notOwned || current.missing) throw new Error(`AVD ${avdName} changed before its data was wiped.`);
|
|
824
927
|
if (current.serial) throw new Error(`Owned AVD ${avdName} started again before its data was wiped.`);
|
|
825
|
-
assertStopped(avdName);
|
|
826
|
-
await withConfigLockAsync$1(() => assertAvdReferences(avdName, owner));
|
|
928
|
+
await assertStopped(avdName);
|
|
929
|
+
await withConfigLockAsync$1(() => assertAvdReferences(avdName, owner), operation?.signal);
|
|
930
|
+
operation?.signal.throwIfAborted();
|
|
827
931
|
const directory = ownedAvdDirectory(avdName);
|
|
828
932
|
if (!directory) throw new Error(`The data directory of AVD ${avdName} could not be resolved.`);
|
|
829
933
|
onWipeStart?.();
|
|
830
|
-
wipeAvdUserData(directory);
|
|
934
|
+
await wipeAvdUserData(directory, operation?.signal);
|
|
831
935
|
}
|
|
832
936
|
if (del) {
|
|
833
937
|
const current = await resolveAvd(avdName);
|
|
@@ -836,10 +940,11 @@ async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, p
|
|
|
836
940
|
kind: "not-owned",
|
|
837
941
|
reason: `AVD ${avdName} is not Stim-owned: Stim has no record of creating it`
|
|
838
942
|
};
|
|
839
|
-
if (current.missing) return teardownUnregisteredAvd(avdName, del, owner);
|
|
943
|
+
if (current.missing) return await teardownUnregisteredAvd(avdName, del, owner, void 0, operation);
|
|
840
944
|
if (current.serial) throw new Error(`Owned AVD ${avdName} started again before deletion.`);
|
|
841
|
-
assertStopped(avdName);
|
|
842
|
-
await withConfigLockAsync$1(() => assertAvdReferences(avdName, owner));
|
|
945
|
+
await assertStopped(avdName);
|
|
946
|
+
await withConfigLockAsync$1(() => assertAvdReferences(avdName, owner), operation?.signal);
|
|
947
|
+
operation?.signal.throwIfAborted();
|
|
843
948
|
if (park && park.max > 0) try {
|
|
844
949
|
const systemImage = ownedAvdSystemImage(avdName);
|
|
845
950
|
if (!systemImage || !park.configuration || !ownedAvdMatchesConfiguration(avdName, park.configuration)) throw new Error("the AVD has no verified creation configuration");
|
|
@@ -861,7 +966,7 @@ async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, p
|
|
|
861
966
|
const removed = [];
|
|
862
967
|
const failures = [];
|
|
863
968
|
for (const entry of evicted) {
|
|
864
|
-
const result = await teardownParkedAvd(entry.name);
|
|
969
|
+
const result = await teardownParkedAvd(entry.name, { operation });
|
|
865
970
|
if (result.status === "torn-down") removed.push({
|
|
866
971
|
udid: entry.name,
|
|
867
972
|
name: entry.name,
|
|
@@ -881,16 +986,18 @@ async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, p
|
|
|
881
986
|
...failures.length ? { evictionFailures: failures } : {}
|
|
882
987
|
};
|
|
883
988
|
} catch (error) {
|
|
989
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
990
|
+
operation?.signal.throwIfAborted();
|
|
884
991
|
parkFallback = String(error?.message || error);
|
|
885
992
|
}
|
|
886
993
|
const directory = ownedAvdDirectory(avdName);
|
|
887
994
|
const paths = avdStorageRoots().map((root) => join(root, `${avdName}.ini`));
|
|
888
995
|
if (directory) paths.push(directory);
|
|
889
|
-
(0, ownership_claim_exports.markClaimChildPending)(claim);
|
|
996
|
+
if (!operation) (0, ownership_claim_exports.markClaimChildPending)(claim);
|
|
890
997
|
try {
|
|
891
|
-
await deleteAvd(avdName);
|
|
998
|
+
await deleteAvd(avdName, operation);
|
|
892
999
|
} finally {
|
|
893
|
-
(0, ownership_claim_exports.clearClaimChild)(claim);
|
|
1000
|
+
if (!operation) (0, ownership_claim_exports.clearClaimChild)(claim);
|
|
894
1001
|
}
|
|
895
1002
|
const remaining = paths.filter(avdPathExists);
|
|
896
1003
|
if (remaining.length) throw new Error(`AVD ${avdName} was only partially deleted; data remains at ${remaining.join(", ")}.`);
|
|
@@ -902,6 +1009,8 @@ async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, p
|
|
|
902
1009
|
...parkFallback ? { parkFallback } : {}
|
|
903
1010
|
};
|
|
904
1011
|
} catch (e) {
|
|
1012
|
+
if (e instanceof WorkspaceProcessCleanupError) throw e;
|
|
1013
|
+
operation?.signal.throwIfAborted();
|
|
905
1014
|
return {
|
|
906
1015
|
status: "failed",
|
|
907
1016
|
reason: String(e?.message || e)
|
|
@@ -910,20 +1019,29 @@ async function teardownClaimedAvd(avdName, claim, { del = false, wipe = false, p
|
|
|
910
1019
|
}
|
|
911
1020
|
const BROWSER_EXIT_WAIT_MS = 15e3;
|
|
912
1021
|
const CHROME_KILL_WAIT_MS = 5e3;
|
|
913
|
-
async function stopOwnedChrome(record, profile) {
|
|
1022
|
+
async function stopOwnedChrome(record, profile, operation) {
|
|
914
1023
|
for (const signal of ["SIGTERM", "SIGKILL"]) {
|
|
915
|
-
|
|
1024
|
+
operation?.signal.throwIfAborted();
|
|
1025
|
+
const state = await chromeProcessStateAsync(record, profile, operation);
|
|
916
1026
|
if (state === "gone") return true;
|
|
917
1027
|
if (state === "unknown") return false;
|
|
918
1028
|
try {
|
|
919
|
-
await signalProcessTreeAsync(record.pid, signal, {
|
|
920
|
-
|
|
1029
|
+
await signalProcessTreeAsync(record.pid, signal, {
|
|
1030
|
+
group: true,
|
|
1031
|
+
operation
|
|
1032
|
+
});
|
|
1033
|
+
} catch (error) {
|
|
1034
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
1035
|
+
operation?.signal.throwIfAborted();
|
|
1036
|
+
}
|
|
921
1037
|
const deadline = Date.now() + CHROME_KILL_WAIT_MS;
|
|
922
|
-
while (await chromeProcessStateAsync(record, profile) !== "gone" && Date.now() < deadline) await
|
|
1038
|
+
while (await chromeProcessStateAsync(record, profile, operation) !== "gone" && Date.now() < deadline) await cancellableSleep(50, {
|
|
1039
|
+
signal: operation?.signal,
|
|
1040
|
+
what: "Chrome shutdown"
|
|
1041
|
+
});
|
|
923
1042
|
}
|
|
924
|
-
return await chromeProcessStateAsync(record, profile) === "gone";
|
|
1043
|
+
return await chromeProcessStateAsync(record, profile, operation) === "gone";
|
|
925
1044
|
}
|
|
926
|
-
const sleepAsync = (ms) => new Promise((done) => setTimeout(done, ms));
|
|
927
1045
|
/**
|
|
928
1046
|
* Closes the workspace's Stim-owned Chrome and its supervisor, after verifying both process identities. With
|
|
929
1047
|
* `deleteProfile`, it also deletes the profile directory, only when the created-devices ledger lists it.
|
|
@@ -932,9 +1050,13 @@ async function teardownOwnedBrowser(root, options = {}) {
|
|
|
932
1050
|
try {
|
|
933
1051
|
return await withWorkspaceProcessLock(workspaceDir(root), "web", () => teardownBrowserHeld(root, options), {
|
|
934
1052
|
external: true,
|
|
935
|
-
ownerPurpose: "browser teardown"
|
|
1053
|
+
ownerPurpose: "browser teardown",
|
|
1054
|
+
signal: options.operation?.signal,
|
|
1055
|
+
declareSpawnsFor: options.operation
|
|
936
1056
|
});
|
|
937
1057
|
} catch (error) {
|
|
1058
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
1059
|
+
options.operation?.signal.throwIfAborted();
|
|
938
1060
|
return {
|
|
939
1061
|
status: "failed",
|
|
940
1062
|
reason: String(error?.message || error)
|
|
@@ -942,8 +1064,10 @@ async function teardownOwnedBrowser(root, options = {}) {
|
|
|
942
1064
|
}
|
|
943
1065
|
}
|
|
944
1066
|
/** {@link teardownOwnedBrowser} for a caller that already holds the {@link BROWSER_LOCK} lock. */
|
|
945
|
-
async function teardownBrowserHeld(root, { deleteProfile = false } = {}) {
|
|
1067
|
+
async function teardownBrowserHeld(root, { deleteProfile = false, operation } = {}) {
|
|
1068
|
+
const signal = operation?.signal;
|
|
946
1069
|
try {
|
|
1070
|
+
signal?.throwIfAborted();
|
|
947
1071
|
const record = readWebRecord(root);
|
|
948
1072
|
const profile = record?.profile ?? webProfileDir(root);
|
|
949
1073
|
if (record) {
|
|
@@ -958,12 +1082,12 @@ async function teardownBrowserHeld(root, { deleteProfile = false } = {}) {
|
|
|
958
1082
|
try {
|
|
959
1083
|
process.kill(record.pid, "SIGTERM");
|
|
960
1084
|
} catch {}
|
|
961
|
-
if (!await (0, process_identity_exports.waitForProcessExit)(record, BROWSER_EXIT_WAIT_MS)) return {
|
|
1085
|
+
if (!await (0, process_identity_exports.waitForProcessExit)(record, BROWSER_EXIT_WAIT_MS, signal)) return {
|
|
962
1086
|
status: "failed",
|
|
963
1087
|
reason: `browser supervisor pid ${record.pid} did not exit`
|
|
964
1088
|
};
|
|
965
1089
|
}
|
|
966
|
-
if (record.chromeProcess && !await stopOwnedChrome(record.chromeProcess, profile)) return {
|
|
1090
|
+
if (record.chromeProcess && !await stopOwnedChrome(record.chromeProcess, profile, operation)) return {
|
|
967
1091
|
status: "failed",
|
|
968
1092
|
reason: `Chrome pid ${record.chromeProcess.pid} did not exit`
|
|
969
1093
|
};
|
|
@@ -983,8 +1107,8 @@ async function teardownBrowserHeld(root, { deleteProfile = false } = {}) {
|
|
|
983
1107
|
kind: "not-verified",
|
|
984
1108
|
reason: claims.status === "held" ? `the browser supervisor claim is still held${claims.holder ? ` by pid ${claims.holder.owner.pid}` : ""}` : `the browser supervisor claim could not be cleared (${claims.reason}); remove it with ${(0, ownership_claim_exports.claimRemoveCommand)(webClaimRoot(root))}`
|
|
985
1109
|
};
|
|
986
|
-
await withWorkspaceStateLockAsync(root, () => clearWebRecord(root, record));
|
|
987
|
-
await releaseBrowserPort(root, record.cdpPort);
|
|
1110
|
+
await withWorkspaceStateLockAsync(root, () => clearWebRecord(root, record), signal);
|
|
1111
|
+
await releaseBrowserPort(root, record.cdpPort, signal);
|
|
988
1112
|
} else {
|
|
989
1113
|
const holder = liveProfileHolder(profile);
|
|
990
1114
|
if (holder !== null) return {
|
|
@@ -993,8 +1117,9 @@ async function teardownBrowserHeld(root, { deleteProfile = false } = {}) {
|
|
|
993
1117
|
reason: `pid ${holder} holds ${profile} but no browser supervisor is recorded; it was left running`
|
|
994
1118
|
};
|
|
995
1119
|
const port = getProject(root)?.ports?.[CDP_PORT_LABEL];
|
|
996
|
-
if (typeof port === "number") await releaseBrowserPort(root, port);
|
|
1120
|
+
if (typeof port === "number") await releaseBrowserPort(root, port, signal);
|
|
997
1121
|
}
|
|
1122
|
+
signal?.throwIfAborted();
|
|
998
1123
|
if (existsSync(profile)) removeSingletonFiles(profile);
|
|
999
1124
|
if (deleteProfile && existsSync(profile)) {
|
|
1000
1125
|
if (!readCreatedDevices$1().web.has(profile)) return {
|
|
@@ -1002,10 +1127,11 @@ async function teardownBrowserHeld(root, { deleteProfile = false } = {}) {
|
|
|
1002
1127
|
kind: "not-owned",
|
|
1003
1128
|
reason: `${profile} is not in Stim's ledger; it was kept`
|
|
1004
1129
|
};
|
|
1005
|
-
|
|
1130
|
+
await rm(profile, {
|
|
1006
1131
|
recursive: true,
|
|
1007
1132
|
force: true
|
|
1008
1133
|
});
|
|
1134
|
+
signal?.throwIfAborted();
|
|
1009
1135
|
forgetCreatedDevice("web", profile);
|
|
1010
1136
|
}
|
|
1011
1137
|
return record ? {
|
|
@@ -1013,6 +1139,8 @@ async function teardownBrowserHeld(root, { deleteProfile = false } = {}) {
|
|
|
1013
1139
|
label: `Chrome (${profile})`
|
|
1014
1140
|
} : { status: "missing" };
|
|
1015
1141
|
} catch (error) {
|
|
1142
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
1143
|
+
signal?.throwIfAborted();
|
|
1016
1144
|
return {
|
|
1017
1145
|
status: "failed",
|
|
1018
1146
|
reason: String(error?.message || error)
|
|
@@ -1055,6 +1183,21 @@ function candidatePids(dirs) {
|
|
|
1055
1183
|
function bundleInstancePids(root) {
|
|
1056
1184
|
return candidatePids(bundleDirs(root));
|
|
1057
1185
|
}
|
|
1186
|
+
async function bundleInstancePidsAsync(root, signal, operation) {
|
|
1187
|
+
signal?.throwIfAborted();
|
|
1188
|
+
const dirs = bundleDirs(root);
|
|
1189
|
+
if (!dirs.length) return [];
|
|
1190
|
+
return bundleCandidates(await runOperationFile(operation, getExecutor(), "ps", [
|
|
1191
|
+
"-axww",
|
|
1192
|
+
"-o",
|
|
1193
|
+
"pid=,comm="
|
|
1194
|
+
], {
|
|
1195
|
+
timeoutMs: 1e4,
|
|
1196
|
+
killSignal: "SIGKILL",
|
|
1197
|
+
signal,
|
|
1198
|
+
cwd: operation?.cwd
|
|
1199
|
+
}), dirs).filter((pid) => pid !== process.pid);
|
|
1200
|
+
}
|
|
1058
1201
|
function unverified(pid, why) {
|
|
1059
1202
|
return Object.assign(/* @__PURE__ */ new Error(`Cannot verify macOS app pid ${pid}: ${why}; no signal was sent.`), { code: "STIM_MACOS_OWNER_UNVERIFIED" });
|
|
1060
1203
|
}
|
|
@@ -1078,24 +1221,27 @@ function failedBundleListing(record, error) {
|
|
|
1078
1221
|
return open;
|
|
1079
1222
|
}
|
|
1080
1223
|
/** Verifies owned bundle instances without blocking on process and executable probes. */
|
|
1081
|
-
async function runningBundleInstancesAsync(root) {
|
|
1224
|
+
async function runningBundleInstancesAsync(root, operation) {
|
|
1225
|
+
operation?.signal.throwIfAborted();
|
|
1082
1226
|
const dirs = bundleDirs(root);
|
|
1083
1227
|
if (!dirs.length) return [];
|
|
1084
|
-
const listing = await getExecutor()
|
|
1228
|
+
const listing = await runOperationFile(operation, getExecutor(), "ps", [
|
|
1085
1229
|
"-axww",
|
|
1086
1230
|
"-o",
|
|
1087
1231
|
"pid=,comm="
|
|
1088
1232
|
], {
|
|
1233
|
+
cwd: operation?.cwd,
|
|
1089
1234
|
timeoutMs: 1e4,
|
|
1090
1235
|
killSignal: "SIGKILL"
|
|
1091
1236
|
});
|
|
1092
1237
|
const instances = [];
|
|
1093
1238
|
for (const pid of bundleCandidates(listing, dirs).filter((candidate) => candidate !== process.pid)) {
|
|
1239
|
+
operation?.signal.throwIfAborted();
|
|
1094
1240
|
const record = bundleRecord(pid);
|
|
1095
1241
|
if (!record) continue;
|
|
1096
1242
|
let open;
|
|
1097
1243
|
try {
|
|
1098
|
-
open = await getExecutor()
|
|
1244
|
+
open = await runOperationFile(operation, getExecutor(), "lsof", [
|
|
1099
1245
|
"-a",
|
|
1100
1246
|
"-p",
|
|
1101
1247
|
String(pid),
|
|
@@ -1103,10 +1249,13 @@ async function runningBundleInstancesAsync(root) {
|
|
|
1103
1249
|
"txt",
|
|
1104
1250
|
"-Fn"
|
|
1105
1251
|
], {
|
|
1252
|
+
cwd: operation?.cwd,
|
|
1106
1253
|
timeoutMs: 1e4,
|
|
1107
1254
|
killSignal: "SIGKILL"
|
|
1108
1255
|
});
|
|
1109
1256
|
} catch (error) {
|
|
1257
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
1258
|
+
operation?.signal.throwIfAborted();
|
|
1110
1259
|
const partial = failedBundleListing(record, error);
|
|
1111
1260
|
if (partial === null) continue;
|
|
1112
1261
|
open = partial;
|
|
@@ -1230,7 +1379,7 @@ async function defaultProbeReachable(url, signal) {
|
|
|
1230
1379
|
const status = await probePublicHttp(url, signal);
|
|
1231
1380
|
return status !== null && status > 0;
|
|
1232
1381
|
}
|
|
1233
|
-
function waitForUrl(child, parseLine, timeoutMs) {
|
|
1382
|
+
function waitForUrl(child, parseLine, timeoutMs, signal) {
|
|
1234
1383
|
return new Promise((resolve) => {
|
|
1235
1384
|
let settled = false;
|
|
1236
1385
|
let lastLine = null;
|
|
@@ -1247,12 +1396,14 @@ function waitForUrl(child, parseLine, timeoutMs) {
|
|
|
1247
1396
|
child.stderr?.removeListener("data", onErr);
|
|
1248
1397
|
child.removeListener("error", onError);
|
|
1249
1398
|
child.removeListener("exit", onExit);
|
|
1399
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1250
1400
|
resolve({
|
|
1251
1401
|
url,
|
|
1252
1402
|
exited,
|
|
1253
1403
|
lastLine
|
|
1254
1404
|
});
|
|
1255
1405
|
};
|
|
1406
|
+
const onAbort = () => finish(null);
|
|
1256
1407
|
const onLine = (line) => {
|
|
1257
1408
|
if (settled) return;
|
|
1258
1409
|
if (line.trim()) lastLine = line.trim();
|
|
@@ -1276,9 +1427,11 @@ function waitForUrl(child, parseLine, timeoutMs) {
|
|
|
1276
1427
|
child.on("error", onError);
|
|
1277
1428
|
child.on("exit", onExit);
|
|
1278
1429
|
timer = setTimeout(() => finish(null), timeoutMs);
|
|
1430
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1431
|
+
if (signal?.aborted) onAbort();
|
|
1279
1432
|
});
|
|
1280
1433
|
}
|
|
1281
|
-
function waitForFileUrl(child, logFile, parseLine, timeoutMs) {
|
|
1434
|
+
function waitForFileUrl(child, logFile, parseLine, timeoutMs, signal) {
|
|
1282
1435
|
return new Promise((resolve) => {
|
|
1283
1436
|
let settled = false;
|
|
1284
1437
|
let watcher = null;
|
|
@@ -1290,11 +1443,13 @@ function waitForFileUrl(child, logFile, parseLine, timeoutMs) {
|
|
|
1290
1443
|
clearTimeout(timer);
|
|
1291
1444
|
child.removeListener("error", onError);
|
|
1292
1445
|
child.removeListener("exit", onExit);
|
|
1446
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1293
1447
|
resolve({
|
|
1294
1448
|
url,
|
|
1295
1449
|
exited
|
|
1296
1450
|
});
|
|
1297
1451
|
};
|
|
1452
|
+
const onAbort = () => finish(null);
|
|
1298
1453
|
const probe = () => {
|
|
1299
1454
|
try {
|
|
1300
1455
|
for (const line of readFileSync(logFile, "utf-8").split(/\r?\n/)) {
|
|
@@ -1319,7 +1474,9 @@ function waitForFileUrl(child, logFile, parseLine, timeoutMs) {
|
|
|
1319
1474
|
} catch {}
|
|
1320
1475
|
const interval = setInterval(probe, Math.min(1e3, timeoutMs));
|
|
1321
1476
|
const timer = setTimeout(() => finish(null), timeoutMs);
|
|
1322
|
-
|
|
1477
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
1478
|
+
if (signal?.aborted) onAbort();
|
|
1479
|
+
else probe();
|
|
1323
1480
|
});
|
|
1324
1481
|
}
|
|
1325
1482
|
function createTunnelLogFile(provider) {
|
|
@@ -1342,17 +1499,25 @@ function removeRecordedTunnelLogFile(path) {
|
|
|
1342
1499
|
if (!resolved.startsWith(`${dir}${sep}`) || resolve(join(dir, basename(resolved))) !== resolved) return;
|
|
1343
1500
|
removeTunnelLogFile(resolved);
|
|
1344
1501
|
}
|
|
1345
|
-
async function waitUntilReachable({ url, now, sleep, probe, timeoutMs }) {
|
|
1502
|
+
async function waitUntilReachable({ url, now, sleep, probe, timeoutMs, signal }) {
|
|
1346
1503
|
const deadline = now() + timeoutMs;
|
|
1347
1504
|
for (;;) {
|
|
1348
|
-
|
|
1505
|
+
throwIfCancelled(signal, "Metro tunnel startup");
|
|
1506
|
+
const controller = new AbortController();
|
|
1507
|
+
const ok = await probe(url, signal ? AbortSignal.any([signal, controller.signal]) : controller.signal).catch(() => false);
|
|
1508
|
+
throwIfCancelled(signal, "Metro tunnel startup");
|
|
1509
|
+
if (ok) return true;
|
|
1349
1510
|
if (now() >= deadline) return false;
|
|
1350
|
-
await
|
|
1511
|
+
if (sleep === defaultSleep) await cancellableSleep(REACHABLE_POLL_MS, {
|
|
1512
|
+
signal,
|
|
1513
|
+
what: "Metro tunnel startup"
|
|
1514
|
+
});
|
|
1515
|
+
else await sleep(REACHABLE_POLL_MS);
|
|
1351
1516
|
}
|
|
1352
1517
|
}
|
|
1353
1518
|
const CLEANUP_TIMEOUT_MS = 1e3;
|
|
1354
|
-
|
|
1355
|
-
|
|
1519
|
+
async function startTunnel({ claims = [], signal, provider, port, spawnFn = null, urlTimeoutMs = URL_TIMEOUT_MS, reachableTimeoutMs = REACHABLE_TIMEOUT_MS, probeReachable = defaultProbeReachable, now = Date.now, sleep = defaultSleep, ngrokUrl = null, requireReachable = true, cleanupTimeoutMs = CLEANUP_TIMEOUT_MS, isChildAlive = pidExists, readProcessToken = process_identity_exports.captureProcessToken, logFile = null }) {
|
|
1520
|
+
throwIfCancelled(signal, "Metro tunnel startup");
|
|
1356
1521
|
const spawn = spawnFn || ((cmd, args, opts) => getExecutor().spawn(cmd, args, opts));
|
|
1357
1522
|
const outputFile = provider === "tailscale" ? null : logFile || (spawnFn ? null : createTunnelLogFile(provider));
|
|
1358
1523
|
let child;
|
|
@@ -1360,6 +1525,7 @@ async function startTunnel({ provider, port, spawnFn = null, urlTimeoutMs = URL_
|
|
|
1360
1525
|
const binary = provider === "tailscale" ? resolveTailscaleBinary((bin) => getExecutor().findExecutable(bin)) : provider;
|
|
1361
1526
|
if (!binary) throw new Error("Install Tailscale and sign in to your tailnet.");
|
|
1362
1527
|
const { bin, args } = tunnelArgv(provider, port, ngrokUrl, outputFile, binary);
|
|
1528
|
+
for (const claim of claims) (0, ownership_claim_exports.markClaimChildPending)(claim);
|
|
1363
1529
|
child = spawn(bin, args, {
|
|
1364
1530
|
stdio: outputFile ? [
|
|
1365
1531
|
"ignore",
|
|
@@ -1373,6 +1539,7 @@ async function startTunnel({ provider, port, spawnFn = null, urlTimeoutMs = URL_
|
|
|
1373
1539
|
detached: true
|
|
1374
1540
|
});
|
|
1375
1541
|
} catch (err) {
|
|
1542
|
+
for (const claim of claims) (0, ownership_claim_exports.clearClaimChild)(claim);
|
|
1376
1543
|
removeTunnelLogFile(outputFile);
|
|
1377
1544
|
return {
|
|
1378
1545
|
failed: true,
|
|
@@ -1380,33 +1547,40 @@ async function startTunnel({ provider, port, spawnFn = null, urlTimeoutMs = URL_
|
|
|
1380
1547
|
};
|
|
1381
1548
|
}
|
|
1382
1549
|
let childExited = false;
|
|
1550
|
+
let childIdentity = null;
|
|
1383
1551
|
child.once("exit", () => {
|
|
1384
1552
|
childExited = true;
|
|
1385
1553
|
});
|
|
1386
1554
|
const cleanup = async (alreadyExited = childExited) => {
|
|
1387
|
-
|
|
1555
|
+
if (claims.length > 0 && childIdentity && process.platform !== "win32" && (0, process_identity_exports.inspectProcessIdentity)(childIdentity) === "same") try {
|
|
1556
|
+
signalProcessTree(childIdentity.pid, "SIGTERM", { group: true });
|
|
1557
|
+
} catch {}
|
|
1558
|
+
const drained = await terminateChild(child, {
|
|
1388
1559
|
alreadyExited,
|
|
1389
1560
|
timeoutMs: cleanupTimeoutMs,
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1561
|
+
isAlive: isChildAlive,
|
|
1562
|
+
waitForGroup: claims.length > 0 && process.platform !== "win32"
|
|
1563
|
+
}) && (claims.length === 0 || process.platform === "win32" || !child.pid || !(0, ownership_claim_exports.processGroupAlive)(child.pid));
|
|
1564
|
+
if (drained) {
|
|
1565
|
+
for (const claim of claims) (0, ownership_claim_exports.clearClaimChild)(claim);
|
|
1566
|
+
removeTunnelLogFile(outputFile);
|
|
1567
|
+
}
|
|
1568
|
+
return drained ? { status: "stopped" } : {
|
|
1396
1569
|
status: "failed",
|
|
1397
1570
|
reason: `Sent SIGKILL but could not confirm that pid ${child.pid ?? "unknown"} exited.`
|
|
1398
1571
|
};
|
|
1399
1572
|
};
|
|
1400
1573
|
const failAfterCleanup = async (reason, alreadyExited = childExited) => {
|
|
1401
1574
|
const stopped = await cleanup(alreadyExited);
|
|
1402
|
-
|
|
1403
|
-
failed: true,
|
|
1404
|
-
reason
|
|
1405
|
-
} : {
|
|
1575
|
+
if (stopped.status === "failed") return {
|
|
1406
1576
|
failed: true,
|
|
1407
1577
|
reason: `${reason} ${stopped.reason}`,
|
|
1408
1578
|
cleanupFailed: true
|
|
1409
1579
|
};
|
|
1580
|
+
return {
|
|
1581
|
+
failed: true,
|
|
1582
|
+
reason: signal?.aborted ? "Metro tunnel startup was cancelled." : reason
|
|
1583
|
+
};
|
|
1410
1584
|
};
|
|
1411
1585
|
const pid = child.pid;
|
|
1412
1586
|
if (!pid) return failAfterCleanup(`${provider} started but reported no pid.`);
|
|
@@ -1419,18 +1593,35 @@ async function startTunnel({ provider, port, spawnFn = null, urlTimeoutMs = URL_
|
|
|
1419
1593
|
};
|
|
1420
1594
|
const initialProcessToken = captureChildIdentity();
|
|
1421
1595
|
if (!initialProcessToken) return failAfterCleanup(`${provider} started but its process identity token could not be read.`);
|
|
1422
|
-
|
|
1596
|
+
childIdentity = {
|
|
1597
|
+
pid,
|
|
1598
|
+
processToken: initialProcessToken
|
|
1599
|
+
};
|
|
1600
|
+
if (claims.length > 0) try {
|
|
1601
|
+
for (const claim of claims) (0, ownership_claim_exports.setClaimChild)(claim, childIdentity);
|
|
1602
|
+
} catch (error) {
|
|
1603
|
+
return failAfterCleanup(`Could not record ${provider} process ownership: ${describe(error)}`);
|
|
1604
|
+
}
|
|
1605
|
+
const { url, exited, lastLine } = outputFile ? await waitForFileUrl(child, outputFile, parserFor(provider), urlTimeoutMs, signal) : await waitForUrl(child, parserFor(provider), urlTimeoutMs, signal);
|
|
1423
1606
|
if (!url) return failAfterCleanup(exited ? provider === "tailscale" && lastLine ? `${provider} exited before printing a tunnel URL: ${lastLine}` : `${provider} exited before printing a tunnel URL.` : `${provider} did not print a tunnel URL within ${urlTimeoutMs}ms.`, exited || childExited);
|
|
1424
1607
|
if (!outputFile) resumeChildPipes(child);
|
|
1425
1608
|
if (requireReachable) {
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1609
|
+
let reachable;
|
|
1610
|
+
try {
|
|
1611
|
+
reachable = await waitUntilReachable({
|
|
1612
|
+
url,
|
|
1613
|
+
now,
|
|
1614
|
+
sleep,
|
|
1615
|
+
probe: probeReachable,
|
|
1616
|
+
timeoutMs: reachableTimeoutMs,
|
|
1617
|
+
signal
|
|
1618
|
+
});
|
|
1619
|
+
} catch (cause) {
|
|
1620
|
+
return failAfterCleanup(`Could not establish ${provider} tunnel reachability: ${describe(cause)}`);
|
|
1621
|
+
}
|
|
1622
|
+
if (!reachable) return failAfterCleanup(`${url} did not become reachable within ${reachableTimeoutMs}ms; ${provider} may still be registering, or the network path is blocked.`);
|
|
1433
1623
|
}
|
|
1624
|
+
if (signal?.aborted) return failAfterCleanup("Metro tunnel startup was cancelled.");
|
|
1434
1625
|
const finalProcessToken = captureChildIdentity();
|
|
1435
1626
|
if (!finalProcessToken || finalProcessToken !== initialProcessToken) return failAfterCleanup(`${provider} process identity changed before its tunnel could be recorded.`);
|
|
1436
1627
|
if (!outputFile) unrefChildPipes(child);
|
|
@@ -1444,10 +1635,16 @@ async function startTunnel({ provider, port, spawnFn = null, urlTimeoutMs = URL_
|
|
|
1444
1635
|
cleanup: () => cleanup()
|
|
1445
1636
|
};
|
|
1446
1637
|
}
|
|
1447
|
-
async function startTunnelSequence({ providers, port, ngrokUrl = null, requireReachable = true, start = startTunnel }) {
|
|
1638
|
+
async function startTunnelSequence({ claims = [], signal, providers, port, ngrokUrl = null, requireReachable = true, start = startTunnel }) {
|
|
1448
1639
|
const failures = [];
|
|
1449
1640
|
for (const provider of providers) {
|
|
1641
|
+
if (signal?.aborted) return {
|
|
1642
|
+
failed: true,
|
|
1643
|
+
reason: "Metro tunnel startup was cancelled."
|
|
1644
|
+
};
|
|
1450
1645
|
const result = await start({
|
|
1646
|
+
claims,
|
|
1647
|
+
signal,
|
|
1451
1648
|
provider,
|
|
1452
1649
|
port,
|
|
1453
1650
|
ngrokUrl: provider === "ngrok" ? ngrokUrl : null,
|
|
@@ -1460,7 +1657,8 @@ async function startTunnelSequence({ providers, port, ngrokUrl = null, requireRe
|
|
|
1460
1657
|
failures.push(`${provider}: ${result.reason}`);
|
|
1461
1658
|
if (result.cleanupFailed) return {
|
|
1462
1659
|
failed: true,
|
|
1463
|
-
reason: failures.join(" ")
|
|
1660
|
+
reason: failures.join(" "),
|
|
1661
|
+
cleanupFailed: true
|
|
1464
1662
|
};
|
|
1465
1663
|
}
|
|
1466
1664
|
return {
|
|
@@ -1480,50 +1678,53 @@ function resumeChildPipes(child) {
|
|
|
1480
1678
|
child.stdout?.resume?.();
|
|
1481
1679
|
child.stderr?.resume?.();
|
|
1482
1680
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
}
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
isAlive,
|
|
1513
|
-
platform
|
|
1514
|
-
})) {
|
|
1515
|
-
closeChildPipes(child);
|
|
1516
|
-
return true;
|
|
1517
|
-
}
|
|
1518
|
-
const killed = await signalAndWaitForExit(child, "SIGKILL", {
|
|
1681
|
+
function signalAndWaitForExit(child, signal, { timeoutMs, isAlive, platform }) {
|
|
1682
|
+
return new Promise((resolve) => {
|
|
1683
|
+
let settled = false;
|
|
1684
|
+
let timer;
|
|
1685
|
+
const onExit = () => finish(true);
|
|
1686
|
+
const onError = (error) => finish(isEsrch(error));
|
|
1687
|
+
const finish = (exited) => {
|
|
1688
|
+
if (settled) return;
|
|
1689
|
+
settled = true;
|
|
1690
|
+
clearTimeout(timer);
|
|
1691
|
+
child.removeListener("exit", onExit);
|
|
1692
|
+
child.removeListener("error", onError);
|
|
1693
|
+
resolve(exited);
|
|
1694
|
+
};
|
|
1695
|
+
child.once("exit", onExit);
|
|
1696
|
+
child.once("error", onError);
|
|
1697
|
+
try {
|
|
1698
|
+
if (platform === "win32" && child.pid) signalProcessTree(child.pid, signal, { platform });
|
|
1699
|
+
child.kill(signal);
|
|
1700
|
+
} catch (error) {
|
|
1701
|
+
finish(isEsrch(error));
|
|
1702
|
+
}
|
|
1703
|
+
if (!settled) timer = setTimeout(() => finish(Boolean(child.pid && !isAlive(child.pid))), timeoutMs);
|
|
1704
|
+
});
|
|
1705
|
+
}
|
|
1706
|
+
async function terminateChild(child, { alreadyExited, timeoutMs, isAlive, platform = process.platform, waitForGroup = false }) {
|
|
1707
|
+
let stopped = alreadyExited;
|
|
1708
|
+
let deadline = Date.now() + timeoutMs;
|
|
1709
|
+
if (!stopped) stopped = await signalAndWaitForExit(child, "SIGTERM", {
|
|
1519
1710
|
timeoutMs,
|
|
1520
|
-
now,
|
|
1521
|
-
sleep,
|
|
1522
1711
|
isAlive,
|
|
1523
1712
|
platform
|
|
1524
1713
|
});
|
|
1714
|
+
if (!stopped) {
|
|
1715
|
+
deadline = Date.now() + timeoutMs;
|
|
1716
|
+
stopped = await signalAndWaitForExit(child, "SIGKILL", {
|
|
1717
|
+
timeoutMs,
|
|
1718
|
+
isAlive,
|
|
1719
|
+
platform
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
if (stopped && waitForGroup && child.pid) {
|
|
1723
|
+
while ((0, ownership_claim_exports.processGroupAlive)(child.pid) && Date.now() < deadline) await cancellableSleep(25);
|
|
1724
|
+
stopped = !(0, ownership_claim_exports.processGroupAlive)(child.pid);
|
|
1725
|
+
}
|
|
1525
1726
|
closeChildPipes(child);
|
|
1526
|
-
return
|
|
1727
|
+
return stopped;
|
|
1527
1728
|
}
|
|
1528
1729
|
function describe(err) {
|
|
1529
1730
|
return err?.message || String(err);
|
|
@@ -1533,7 +1734,14 @@ const STOP_POLL_MS = 100;
|
|
|
1533
1734
|
function isEsrch(err) {
|
|
1534
1735
|
return err?.code === "ESRCH";
|
|
1535
1736
|
}
|
|
1536
|
-
async function stopTunnel(record, { isAlive = pidExists, inspectIdentity = process_identity_exports.inspectProcessIdentity, platform = process.platform, kill = (pid) => signalProcessTreeAsync(pid, "SIGTERM", {
|
|
1737
|
+
async function stopTunnel(record, { isAlive = pidExists, inspectIdentity = process_identity_exports.inspectProcessIdentity, platform = process.platform, operation, kill = (pid) => signalProcessTreeAsync(pid, "SIGTERM", {
|
|
1738
|
+
platform,
|
|
1739
|
+
operation
|
|
1740
|
+
}), now = Date.now, sleep = operation ? (ms) => cancellableSleep(ms, {
|
|
1741
|
+
signal: operation.signal,
|
|
1742
|
+
what: "tunnel stop"
|
|
1743
|
+
}) : defaultSleep, timeoutMs = STOP_TIMEOUT_MS } = {}) {
|
|
1744
|
+
operation?.signal.throwIfAborted();
|
|
1537
1745
|
const missing = () => {
|
|
1538
1746
|
removeRecordedTunnelLogFile(record?.logFile);
|
|
1539
1747
|
return { status: "missing" };
|
|
@@ -1553,6 +1761,8 @@ async function stopTunnel(record, { isAlive = pidExists, inspectIdentity = proce
|
|
|
1553
1761
|
try {
|
|
1554
1762
|
await kill(pid);
|
|
1555
1763
|
} catch (err) {
|
|
1764
|
+
if (err instanceof WorkspaceProcessCleanupError) throw err;
|
|
1765
|
+
operation?.signal.throwIfAborted();
|
|
1556
1766
|
return isEsrch(err) ? missing() : {
|
|
1557
1767
|
status: "failed",
|
|
1558
1768
|
reason: describe(err)
|
|
@@ -1564,7 +1774,11 @@ async function stopTunnel(record, { isAlive = pidExists, inspectIdentity = proce
|
|
|
1564
1774
|
const current = inspectIdentity(record);
|
|
1565
1775
|
return current === "gone" || current === "different";
|
|
1566
1776
|
};
|
|
1567
|
-
while (now() < deadline && !exited())
|
|
1777
|
+
while (now() < deadline && !exited()) {
|
|
1778
|
+
operation?.signal.throwIfAborted();
|
|
1779
|
+
await sleep(STOP_POLL_MS);
|
|
1780
|
+
}
|
|
1781
|
+
operation?.signal.throwIfAborted();
|
|
1568
1782
|
if (!exited()) return {
|
|
1569
1783
|
status: "failed",
|
|
1570
1784
|
reason: `pid ${pid} did not exit within ${timeoutMs}ms.`
|
|
@@ -1638,7 +1852,7 @@ function numberOrNull(v) {
|
|
|
1638
1852
|
//#region src/workspace/in-use.ts
|
|
1639
1853
|
const NATIVE_RUN = "native-run";
|
|
1640
1854
|
const NATIVE_RUN_HELD = "a stim ios, android or stop run holds its native-run.lock";
|
|
1641
|
-
function workspaceInUse(root, { supervisor = true, managedLocks = true, hosted = true, nativeRun = true } = {}) {
|
|
1855
|
+
function workspaceInUse(root, { supervisor = true, managedLocks = true, hosted = true, nativeRun = true } = {}, bundleInstances = bundleInstancePids) {
|
|
1642
1856
|
const reasons = [];
|
|
1643
1857
|
if (supervisor) {
|
|
1644
1858
|
const project = getProject(root);
|
|
@@ -1654,7 +1868,7 @@ function workspaceInUse(root, { supervisor = true, managedLocks = true, hosted =
|
|
|
1654
1868
|
else if (macos?.state === "running" || macos?.state === "orphaned" || macos?.build.state === "running") reasons.push("its macOS app or build is running");
|
|
1655
1869
|
else if (macos?.state === "unverified" || !macos && readWorkspaceState$1(root)?.macos !== void 0) reasons.push("its macOS process owner cannot be verified");
|
|
1656
1870
|
else try {
|
|
1657
|
-
const pids =
|
|
1871
|
+
const pids = bundleInstances(root);
|
|
1658
1872
|
if (pids.length) reasons.push(`its macOS app is running (pid ${pids.join(", ")})`);
|
|
1659
1873
|
} catch (error) {
|
|
1660
1874
|
reasons.push(`its running macOS apps cannot be listed: ${error.message}`);
|
|
@@ -1683,20 +1897,44 @@ function workspaceInUse(root, { supervisor = true, managedLocks = true, hosted =
|
|
|
1683
1897
|
if (managedLocks) reasons.push(...managedLockHolders(root));
|
|
1684
1898
|
return reasons;
|
|
1685
1899
|
}
|
|
1686
|
-
function
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1900
|
+
async function workspaceInUseAsync(root, checks = {}, signal, operation) {
|
|
1901
|
+
let instances;
|
|
1902
|
+
try {
|
|
1903
|
+
instances = checks.supervisor === false ? [] : await bundleInstancePidsAsync(root, signal, operation);
|
|
1904
|
+
} catch (error) {
|
|
1905
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
1906
|
+
signal?.throwIfAborted();
|
|
1907
|
+
instances = error instanceof Error ? error : new Error(String(error));
|
|
1690
1908
|
}
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1909
|
+
signal?.throwIfAborted();
|
|
1910
|
+
return workspaceInUse(root, checks, () => {
|
|
1911
|
+
if (instances instanceof Error) throw instances;
|
|
1912
|
+
return instances;
|
|
1694
1913
|
});
|
|
1695
1914
|
}
|
|
1696
|
-
async function
|
|
1915
|
+
async function emptyWorkspaceDir(dir, signal) {
|
|
1916
|
+
signal?.throwIfAborted();
|
|
1917
|
+
const stat = await lstat(dir);
|
|
1918
|
+
signal?.throwIfAborted();
|
|
1919
|
+
if (stat.isSymbolicLink()) {
|
|
1920
|
+
await unlink(dir);
|
|
1921
|
+
return;
|
|
1922
|
+
}
|
|
1923
|
+
for (const name of await readdir(dir)) {
|
|
1924
|
+
signal?.throwIfAborted();
|
|
1925
|
+
if (name !== `${NATIVE_RUN}.lock`) await rm(join(dir, name), {
|
|
1926
|
+
recursive: true,
|
|
1927
|
+
force: true
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
async function withIdleWorkspace(root, fn, { purpose, operation, signal = operation?.signal, ...checks }) {
|
|
1697
1932
|
try {
|
|
1698
1933
|
return await withWorkspaceProcessLock(workspaceDir(root), NATIVE_RUN, async () => {
|
|
1699
|
-
const reasons =
|
|
1934
|
+
const reasons = signal ? await workspaceInUseAsync(root, {
|
|
1935
|
+
...checks,
|
|
1936
|
+
nativeRun: false
|
|
1937
|
+
}, signal, operation) : workspaceInUse(root, {
|
|
1700
1938
|
...checks,
|
|
1701
1939
|
nativeRun: false
|
|
1702
1940
|
});
|
|
@@ -1711,9 +1949,13 @@ async function withIdleWorkspace(root, fn, { purpose, ...checks }) {
|
|
|
1711
1949
|
}, {
|
|
1712
1950
|
external: true,
|
|
1713
1951
|
waitMs: 0,
|
|
1714
|
-
ownerPurpose: purpose
|
|
1952
|
+
ownerPurpose: purpose,
|
|
1953
|
+
signal,
|
|
1954
|
+
declareSpawnsFor: operation
|
|
1715
1955
|
});
|
|
1716
1956
|
} catch (error) {
|
|
1957
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
1958
|
+
signal?.throwIfAborted();
|
|
1717
1959
|
if (workspaceProcessLockError(error)) return {
|
|
1718
1960
|
ran: false,
|
|
1719
1961
|
reasons: [NATIVE_RUN_HELD]
|
|
@@ -1744,11 +1986,17 @@ function readRecordedRoot(dir, name) {
|
|
|
1744
1986
|
try {
|
|
1745
1987
|
parsed = readJsonFile(join$1(dir, "workspace.json"));
|
|
1746
1988
|
} catch (error) {
|
|
1747
|
-
return
|
|
1748
|
-
projectRoot: null,
|
|
1749
|
-
problem: error?.code === "ENOENT" ? "it has no workspace.json" : "its workspace.json does not parse"
|
|
1750
|
-
};
|
|
1989
|
+
return unreadableRecordedRoot(error);
|
|
1751
1990
|
}
|
|
1991
|
+
return recordedRoot(parsed, name);
|
|
1992
|
+
}
|
|
1993
|
+
function unreadableRecordedRoot(error) {
|
|
1994
|
+
return {
|
|
1995
|
+
projectRoot: null,
|
|
1996
|
+
problem: error?.code === "ENOENT" ? "it has no workspace.json" : "its workspace.json does not parse"
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
function recordedRoot(parsed, name) {
|
|
1752
2000
|
const projectRoot = isJsonObject(parsed) ? parsed.projectRoot : void 0;
|
|
1753
2001
|
if (typeof projectRoot !== "string" || !isAbsolute$1(projectRoot)) return {
|
|
1754
2002
|
projectRoot: null,
|
|
@@ -1786,6 +2034,42 @@ function listWorkspaceDirs() {
|
|
|
1786
2034
|
}
|
|
1787
2035
|
return entries;
|
|
1788
2036
|
}
|
|
2037
|
+
async function listWorkspaceDirsAsync(signal) {
|
|
2038
|
+
signal?.throwIfAborted();
|
|
2039
|
+
const root = workspacesRoot();
|
|
2040
|
+
let names;
|
|
2041
|
+
try {
|
|
2042
|
+
names = (await readdir(root)).toSorted();
|
|
2043
|
+
} catch {
|
|
2044
|
+
signal?.throwIfAborted();
|
|
2045
|
+
return [];
|
|
2046
|
+
}
|
|
2047
|
+
const entries = [];
|
|
2048
|
+
for (const name of names) {
|
|
2049
|
+
signal?.throwIfAborted();
|
|
2050
|
+
const dir = join$1(root, name);
|
|
2051
|
+
try {
|
|
2052
|
+
if (!(await stat(dir)).isDirectory()) continue;
|
|
2053
|
+
} catch {
|
|
2054
|
+
signal?.throwIfAborted();
|
|
2055
|
+
continue;
|
|
2056
|
+
}
|
|
2057
|
+
signal?.throwIfAborted();
|
|
2058
|
+
let recorded;
|
|
2059
|
+
try {
|
|
2060
|
+
recorded = recordedRoot(JSON.parse(await readFile(join$1(dir, "workspace.json"), "utf8")), name);
|
|
2061
|
+
} catch (error) {
|
|
2062
|
+
signal?.throwIfAborted();
|
|
2063
|
+
recorded = unreadableRecordedRoot(error);
|
|
2064
|
+
}
|
|
2065
|
+
signal?.throwIfAborted();
|
|
2066
|
+
entries.push({
|
|
2067
|
+
dir,
|
|
2068
|
+
...recorded
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
return entries;
|
|
2072
|
+
}
|
|
1789
2073
|
function classifyWorkspaceDirs(entries, { registryKeys, exists, isMounted, inUse }) {
|
|
1790
2074
|
const orphaned = [];
|
|
1791
2075
|
const skipped = [];
|
|
@@ -1841,28 +2125,47 @@ function rootPresence(path) {
|
|
|
1841
2125
|
return code === "ENOENT" || code === "ENOTDIR" ? false : null;
|
|
1842
2126
|
}
|
|
1843
2127
|
}
|
|
1844
|
-
function collectOrphanedWorkspaces(registryKeys, mountedVolumes, { measure = true } = {}) {
|
|
1845
|
-
const classified =
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
2128
|
+
async function collectOrphanedWorkspaces(registryKeys, mountedVolumes, { measure = true, operation } = {}) {
|
|
2129
|
+
const classified = {
|
|
2130
|
+
orphaned: [],
|
|
2131
|
+
skipped: []
|
|
2132
|
+
};
|
|
2133
|
+
for (const entry of await listWorkspaceDirsAsync(operation?.signal)) {
|
|
2134
|
+
operation?.signal.throwIfAborted();
|
|
2135
|
+
const current = classifyWorkspaceDirs([entry], {
|
|
2136
|
+
registryKeys,
|
|
2137
|
+
exists: rootPresence,
|
|
2138
|
+
isMounted: (path) => isOnMountedVolume(path, mountedVolumes),
|
|
2139
|
+
inUse: () => []
|
|
2140
|
+
});
|
|
2141
|
+
classified.skipped.push(...current.skipped);
|
|
2142
|
+
for (const orphan of current.orphaned) {
|
|
2143
|
+
const reasons = operation ? await workspaceInUseAsync(orphan.projectRoot, {}, operation.signal, operation) : workspaceInUse(orphan.projectRoot);
|
|
2144
|
+
if (reasons.length) classified.skipped.push({
|
|
2145
|
+
dir: orphan.dir,
|
|
2146
|
+
reason: `its project root ${orphan.projectRoot} is gone, but ${reasons.join("; ")}`
|
|
1859
2147
|
});
|
|
2148
|
+
else classified.orphaned.push(orphan);
|
|
1860
2149
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
2150
|
+
}
|
|
2151
|
+
if (!measure) return classified;
|
|
2152
|
+
const orphaned = [];
|
|
2153
|
+
for (const entry of classified.orphaned) {
|
|
2154
|
+
operation?.signal.throwIfAborted();
|
|
2155
|
+
const bytes = await measuredDirectorySizeAsync(entry.dir, {
|
|
2156
|
+
timeoutMs: SIZE_TIMEOUT_MS,
|
|
2157
|
+
operation,
|
|
2158
|
+
signal: operation?.signal
|
|
2159
|
+
});
|
|
2160
|
+
orphaned.push({
|
|
2161
|
+
...entry,
|
|
2162
|
+
bytes
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
return {
|
|
2166
|
+
orphaned,
|
|
2167
|
+
skipped: classified.skipped
|
|
2168
|
+
};
|
|
1866
2169
|
}
|
|
1867
2170
|
const REBUILD_COST = "The next build of an unchanged app installs from the shared build cache; after a native change the compilation cache speeds the rebuild, but on React Native 0.86 Swift does not use it (explicit modules are off), so that build recompiles Swift. The next stim macos in a cleared workspace is a full Swift build.";
|
|
1868
2171
|
const WORKSPACE_OUTPUT_DIRS = [
|
|
@@ -1897,47 +2200,53 @@ function outputPaths(dir) {
|
|
|
1897
2200
|
} catch {}
|
|
1898
2201
|
return paths;
|
|
1899
2202
|
}
|
|
1900
|
-
async function outputBytes(paths, timeoutMs) {
|
|
2203
|
+
async function outputBytes(paths, timeoutMs, signal, operation) {
|
|
1901
2204
|
let total = 0;
|
|
1902
2205
|
for (const path of paths) {
|
|
1903
|
-
const size = await measuredDirectorySizeAsync(path, {
|
|
2206
|
+
const size = await measuredDirectorySizeAsync(path, {
|
|
2207
|
+
timeoutMs,
|
|
2208
|
+
signal,
|
|
2209
|
+
operation
|
|
2210
|
+
});
|
|
1904
2211
|
if (size === null) return null;
|
|
1905
2212
|
total += size;
|
|
1906
2213
|
}
|
|
1907
2214
|
return total;
|
|
1908
2215
|
}
|
|
1909
|
-
function collectWorkspaceOutputs({ olderThan, now, exclude = [], measure = true, sizeTimeoutMs = SIZE_TIMEOUT_MS }) {
|
|
2216
|
+
function collectWorkspaceOutputs({ olderThan, now, exclude = [], measure = true, sizeTimeoutMs = SIZE_TIMEOUT_MS, signal, operation }) {
|
|
1910
2217
|
const mountedVolumes = listMountedVolumes();
|
|
1911
|
-
const
|
|
2218
|
+
const outputEntries = (entries) => entries.filter((entry) => !exclude.includes(entry.dir)).filter((entry) => entry.projectRoot === null || isOnMountedVolume(entry.projectRoot, mountedVolumes)).map((entry) => Object.assign({}, entry, { paths: outputPaths(entry.dir) })).filter((entry) => entry.paths.length > 0);
|
|
1912
2219
|
const report = (sizes) => ({
|
|
1913
2220
|
root: workspacesRoot(),
|
|
1914
|
-
workspaces: planWorkspaceOutputs(sizes.map((entry) => ({
|
|
1915
|
-
...entry,
|
|
1916
|
-
lastUsed: entry.projectRoot === null ? NaN : workspaceLastUsed(entry.projectRoot),
|
|
1917
|
-
inUse: entry.projectRoot === null ? [] : workspaceInUse(entry.projectRoot)
|
|
1918
|
-
})), {
|
|
2221
|
+
workspaces: planWorkspaceOutputs(sizes.map((entry) => Object.assign({}, entry, { lastUsed: entry.projectRoot === null ? NaN : workspaceLastUsed(entry.projectRoot) })), {
|
|
1919
2222
|
olderThan,
|
|
1920
2223
|
now
|
|
1921
2224
|
})
|
|
1922
2225
|
});
|
|
1923
|
-
if (!measure) return report(
|
|
1924
|
-
|
|
1925
|
-
|
|
2226
|
+
if (!measure && !signal) return report(outputEntries(listWorkspaceDirs()).map(({ paths: _paths, ...entry }) => Object.assign({}, entry, {
|
|
2227
|
+
bytes: null,
|
|
2228
|
+
inUse: entry.projectRoot === null ? [] : workspaceInUse(entry.projectRoot)
|
|
1926
2229
|
})));
|
|
1927
2230
|
return (async () => {
|
|
1928
2231
|
const sizes = [];
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
2232
|
+
const entries = outputEntries(await listWorkspaceDirsAsync(signal));
|
|
2233
|
+
for (const { paths, ...entry } of entries) {
|
|
2234
|
+
signal?.throwIfAborted();
|
|
2235
|
+
sizes.push({
|
|
2236
|
+
...entry,
|
|
2237
|
+
bytes: measure ? await outputBytes(paths, sizeTimeoutMs, signal, operation) : null,
|
|
2238
|
+
inUse: entry.projectRoot === null ? [] : signal ? await workspaceInUseAsync(entry.projectRoot, {}, signal, operation) : workspaceInUse(entry.projectRoot)
|
|
2239
|
+
});
|
|
2240
|
+
}
|
|
1933
2241
|
return report(sizes);
|
|
1934
2242
|
})();
|
|
1935
2243
|
}
|
|
1936
|
-
async function clearWorkspaceOutputs(report, { olderThan, now = Date.now() }, { log, record } = GC_OUTPUT) {
|
|
2244
|
+
async function clearWorkspaceOutputs(report, { olderThan, now = Date.now(), operation, signal = operation?.signal }, { log, record } = GC_OUTPUT) {
|
|
1937
2245
|
let failures = 0;
|
|
1938
2246
|
let cleared = 0;
|
|
1939
2247
|
const clearedRoots = [];
|
|
1940
2248
|
for (const entry of report.workspaces) {
|
|
2249
|
+
signal?.throwIfAborted();
|
|
1941
2250
|
const root = entry.projectRoot;
|
|
1942
2251
|
if (!entry.willClear || root === null) {
|
|
1943
2252
|
if (entry.keptReason) {
|
|
@@ -1951,7 +2260,7 @@ async function clearWorkspaceOutputs(report, { olderThan, now = Date.now() }, {
|
|
|
1951
2260
|
}
|
|
1952
2261
|
let run;
|
|
1953
2262
|
try {
|
|
1954
|
-
run = await withIdleWorkspace(root, () => {
|
|
2263
|
+
run = await withIdleWorkspace(root, async () => {
|
|
1955
2264
|
const [again] = planWorkspaceOutputs([{
|
|
1956
2265
|
...entry,
|
|
1957
2266
|
problem: null,
|
|
@@ -1962,13 +2271,23 @@ async function clearWorkspaceOutputs(report, { olderThan, now = Date.now() }, {
|
|
|
1962
2271
|
now
|
|
1963
2272
|
});
|
|
1964
2273
|
if (again?.keptReason) return again.keptReason;
|
|
1965
|
-
for (const path of outputPaths(entry.dir))
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
2274
|
+
for (const path of outputPaths(entry.dir)) {
|
|
2275
|
+
signal?.throwIfAborted();
|
|
2276
|
+
await rm(path, {
|
|
2277
|
+
recursive: true,
|
|
2278
|
+
force: true
|
|
2279
|
+
});
|
|
2280
|
+
}
|
|
2281
|
+
signal?.throwIfAborted();
|
|
1969
2282
|
return null;
|
|
1970
|
-
}, {
|
|
2283
|
+
}, {
|
|
2284
|
+
purpose: "gc",
|
|
2285
|
+
signal,
|
|
2286
|
+
operation
|
|
2287
|
+
});
|
|
1971
2288
|
} catch (error) {
|
|
2289
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
2290
|
+
signal?.throwIfAborted();
|
|
1972
2291
|
failures++;
|
|
1973
2292
|
log(chalk.red(`Could not clear the build outputs of ${root}: ${error?.message || String(error)}`));
|
|
1974
2293
|
record("workspaceOutputs", "failed", root, {
|
|
@@ -2001,47 +2320,56 @@ function stillOrphaned({ dir, projectRoot }) {
|
|
|
2001
2320
|
if (readRecordedRoot(dir, basename$1(dir)).projectRoot !== projectRoot || rootPresence(projectRoot) !== false || !isOnMountedVolume(projectRoot)) return false;
|
|
2002
2321
|
return !Object.keys(loadConfig$1()?.projects ?? {}).some((key) => isPathPrefix(projectRoot, key));
|
|
2003
2322
|
}
|
|
2004
|
-
async function deleteOrphanedWorkspaces(orphaned) {
|
|
2323
|
+
async function deleteOrphanedWorkspaces(orphaned, output = GC_OUTPUT, operation) {
|
|
2005
2324
|
let failures = 0;
|
|
2006
2325
|
for (const entry of orphaned) {
|
|
2326
|
+
operation?.signal.throwIfAborted();
|
|
2007
2327
|
if (!existsSync$1(entry.dir)) continue;
|
|
2008
2328
|
let run;
|
|
2009
2329
|
try {
|
|
2010
2330
|
run = await withIdleWorkspace(entry.projectRoot, async () => {
|
|
2011
2331
|
if (!stillOrphaned(entry)) return false;
|
|
2012
|
-
const browser = await teardownOwnedBrowser(entry.projectRoot, {
|
|
2332
|
+
const browser = await teardownOwnedBrowser(entry.projectRoot, {
|
|
2333
|
+
deleteProfile: true,
|
|
2334
|
+
...operation ? { operation } : {}
|
|
2335
|
+
});
|
|
2013
2336
|
if (browser.status === "failed" || browser.status === "skipped") throw new Error(`its owned Chrome profile was kept: ${browser.reason}`);
|
|
2014
|
-
emptyWorkspaceDir(entry.dir);
|
|
2337
|
+
await emptyWorkspaceDir(entry.dir, operation?.signal);
|
|
2015
2338
|
return true;
|
|
2016
|
-
}, {
|
|
2339
|
+
}, {
|
|
2340
|
+
purpose: "gc",
|
|
2341
|
+
operation
|
|
2342
|
+
});
|
|
2017
2343
|
} catch (error) {
|
|
2344
|
+
if (error instanceof WorkspaceProcessCleanupError) throw error;
|
|
2345
|
+
operation?.signal.throwIfAborted();
|
|
2018
2346
|
failures++;
|
|
2019
|
-
|
|
2020
|
-
|
|
2347
|
+
output.log(chalk.red(`Could not remove ${entry.dir}: ${error?.message || error}`));
|
|
2348
|
+
output.record("workspaceDirectory", "failed", entry.dir, {
|
|
2021
2349
|
bytes: entry.bytes ?? null,
|
|
2022
2350
|
detail: error?.message || String(error)
|
|
2023
2351
|
});
|
|
2024
2352
|
continue;
|
|
2025
2353
|
}
|
|
2026
2354
|
if (!run.ran) {
|
|
2027
|
-
|
|
2028
|
-
|
|
2355
|
+
output.log(chalk.yellow(`Kept ${entry.dir}: ${run.reasons.join("; ")}`));
|
|
2356
|
+
output.record("workspaceDirectory", "kept", entry.dir, {
|
|
2029
2357
|
bytes: entry.bytes ?? null,
|
|
2030
2358
|
detail: run.reasons.join("; ")
|
|
2031
2359
|
});
|
|
2032
2360
|
continue;
|
|
2033
2361
|
}
|
|
2034
2362
|
if (!run.value) {
|
|
2035
|
-
|
|
2036
|
-
|
|
2363
|
+
output.log(chalk.yellow(`Kept ${entry.dir}: it can no longer be confirmed orphaned.`));
|
|
2364
|
+
output.record("workspaceDirectory", "kept", entry.dir, {
|
|
2037
2365
|
bytes: entry.bytes ?? null,
|
|
2038
2366
|
detail: "it can no longer be confirmed orphaned"
|
|
2039
2367
|
});
|
|
2040
2368
|
continue;
|
|
2041
2369
|
}
|
|
2042
2370
|
const size = entry.bytes === void 0 ? "" : ` (${sizeText(entry.bytes)})`;
|
|
2043
|
-
|
|
2044
|
-
|
|
2371
|
+
output.log(chalk.green(`Removed the orphaned workspace directory ${entry.dir}${size}`));
|
|
2372
|
+
output.record("workspaceDirectory", "done", entry.dir, { bytes: entry.bytes ?? null });
|
|
2045
2373
|
}
|
|
2046
2374
|
return failures;
|
|
2047
2375
|
}
|
|
@@ -2049,4 +2377,4 @@ function sizeText(bytes) {
|
|
|
2049
2377
|
return bytes === null ? "size unknown" : formatBytes(bytes);
|
|
2050
2378
|
}
|
|
2051
2379
|
//#endregion
|
|
2052
|
-
export {
|
|
2380
|
+
export { teardownParkedAvd as A, createGcResults as B, runningBundleInstancesAsync as C, teardownOwnedAvd as D, teardownBrowserHeld as E, writeUsageCache as F, acquireBuildSlot as I, listBuildSlots as L, probePublicHttp as M, canonical$1 as N, teardownOwnedBrowser as O, measureDu as P, releaseBuildSlot as R, withManagedTunnelRemovalLock as S, eraseParkedIosSim as T, takeGcResults as V, stopTunnel as _, collectWorkspaceOutputs as a, withManagedRemoteWorktreeRemovalLock as b, listWorkspaceDirs as c, emptyWorkspaceDir as d, withIdleWorkspace as f, startTunnelSequence as g, resolveSupervisorTarget as h, collectOrphanedWorkspaces as i, teardownParkedIosSim as j, teardownOwnedIosSim as k, rootPresence as l, workspaceInUseAsync as m, WORKSPACE_OUTPUT_DIRS as n, deleteOrphanedWorkspaces as o, workspaceInUse as p, clearWorkspaceOutputs as r, isInsideWorkspaces as s, REBUILD_COST as t, sizeText as u, terminateChild as v, eraseParkedAvd as w, withManagedTunnelLock as x, withManagedRemoteWorktreeLock as y, GC_OUTPUT as z };
|