stim 1.13.0 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/{activity-DfVKZTEB.mjs → activity-B2-kb7k0.mjs} +281 -41
- package/dist/{android-BHfAu2mf.mjs → android-CYmq0jNi.mjs} +2 -2
- package/dist/{android-CD8pr8Q2.mjs → android-Cu_L4RpL.mjs} +524 -412
- package/dist/{android-qx_31wJo.mjs → android-Db2eEMaf.mjs} +152 -65
- package/dist/android-cas-PnKfAdB2.mjs +167 -0
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/{app-install-CDFY2nUL.mjs → app-install-CAAplXAt.mjs} +162 -173
- package/dist/{budget-E5o91NR1.mjs → budget-CMiqghig.mjs} +1048 -77
- package/dist/{build-plan-BTa93o_r.mjs → build-plan-aeGMFX7e.mjs} +193 -650
- package/dist/build-progress-C2Jv1AtA.mjs +872 -0
- package/dist/build-slots-Df1ApSuH.mjs +127 -0
- package/dist/{cache-manifest-DWPF4RKJ.mjs → cache-manifest-DnUHBJaU.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/{chrome-B5hWgQJS.mjs → chrome-BrcVWQyD.mjs} +4 -4
- package/dist/cli-0cX5s4NM.mjs +45 -0
- package/dist/cli.mjs +6 -42
- package/dist/client-C2IS36Qf.mjs +1333 -0
- package/dist/collector-run.mjs +8 -7
- package/dist/{command-output-BIL7rlX7.mjs → command-output-CliHHn7a.mjs} +1 -1
- package/dist/{config-0jCcMM0E.mjs → config-D0DbpZtM.mjs} +29 -29
- package/dist/{created-devices-Cd558h31.mjs → created-devices-BwVb3liH.mjs} +2 -2
- package/dist/{dependency-state-DNN0yfx6.mjs → dependency-state-BmsjKZ2H.mjs} +2 -2
- package/dist/{deps-2Wo81np9.mjs → deps-BFlKbOIp.mjs} +3 -3
- package/dist/{detached-entry-DJHoca49.mjs → detached-entry-CiTEgMR1.mjs} +1 -1
- package/dist/dev-client-CjzLoNs0.mjs +80 -0
- package/dist/{device-Cf7vl-so.mjs → device-BZ1IyS5A.mjs} +7 -7
- package/dist/device-capacity-DMuKC7nx.mjs +136 -0
- package/dist/device-host-worker.d.mts +1 -0
- package/dist/device-host-worker.mjs +769 -0
- package/dist/device-ios-DMQBUPus.mjs +381 -0
- package/dist/{device-lease-CL9dZf73.mjs → device-lease-W5Pi1q38.mjs} +43 -36
- package/dist/{device-pool-MuoiyRcu.mjs → device-pool-B2r38YjS.mjs} +4 -4
- package/dist/{device-remote-DT9ngZiD.mjs → device-remote-9ulPsdhY.mjs} +68 -162
- package/dist/{doctor-D2iysNNH.mjs → doctor-BcE5xsBn.mjs} +191 -99
- package/dist/{error-diagnostics-BoiOhck8.mjs → error-diagnostics-Cb0P1WLS.mjs} +12 -11
- package/dist/{exec-DK8KaVHK.mjs → exec-CaErVv7t.mjs} +5 -1
- package/dist/{gc-eA-PRBX8.mjs → gc-BuL1Genr.mjs} +430 -102
- package/dist/{android-cas-B9Kv9aIy.mjs → gradle-CmdOBq1d.mjs} +8 -294
- package/dist/{guide-BfZ0OmiP.mjs → guide-cCSGRFd3.mjs} +2052 -240
- package/dist/{guide-status-BaMYu55M.mjs → guide-status-CIoWK6mG.mjs} +3 -3
- package/dist/hosted-macos-DwowNU7W.mjs +447 -0
- package/dist/idle-shutdown-B5LPaNyY.mjs +122 -0
- package/dist/in-use-CndPMilN.mjs +110 -0
- package/dist/{ios-CmEzZiOj.mjs → ios-3CtCRhwr.mjs} +201 -10
- package/dist/{ios-CcfCeZ19.mjs → ios-Chob8ny7.mjs} +560 -259
- package/dist/{ios-device-BW-t_Lkv.mjs → ios-device-DERmMmk-.mjs} +12 -7
- package/dist/{ios-device-BiOsSVzd.mjs → ios-device-Ee7sDXfp.mjs} +1 -1
- package/dist/{launch-verify-CuJaBpEa.mjs → launch-verify-CLlofxRt.mjs} +42 -11
- package/dist/{logs-DcSRbC2t.mjs → logs-h9h6MUvE.mjs} +95 -13
- package/dist/machine-usage-D_YYMzfR.mjs +283 -0
- package/dist/machines-CYtOCqcj.mjs +259 -0
- package/dist/macos-BwtbUtTN.mjs +468 -0
- package/dist/macos-run.d.mts +5 -0
- package/dist/macos-run.mjs +2 -0
- package/dist/{merge-state-ytFQCeh4.mjs → merge-state-BnxLNQiM.mjs} +22 -12
- package/dist/{metro-BPeY1YMh.mjs → metro-Di8EN2Tb.mjs} +20 -120
- package/dist/{named-ports-n7tnJsI8.mjs → named-ports-Dj_XBNaL.mjs} +8 -64
- package/dist/native-run-DFDoIRbY.mjs +147 -0
- package/dist/{native-runtime-CiiFHt0e.mjs → native-runtime-i6OkVg-w.mjs} +11 -6
- package/dist/offload-worker.d.mts +121 -0
- package/dist/offload-worker.mjs +601 -0
- package/dist/{ownership-Bz2kETAS.mjs → ownership-BkYxUtuj.mjs} +3 -2
- package/dist/{page-BTu7I60K.mjs → page-QF_Ke10s.mjs} +7 -2
- package/dist/{ports-C5UutqpJ.mjs → ports-DddwttKM.mjs} +3 -3
- package/dist/prebuild-BvxDlD6C.mjs +213 -0
- package/dist/process-identity-BmRm1qV0.mjs +12 -0
- package/dist/{project-DGeclhvO.mjs → project-CV3tDFHl.mjs} +9 -18
- package/dist/pull-request-pxnDCQzn.mjs +322 -0
- package/dist/pull-requests.d.mts +120 -0
- package/dist/pull-requests.mjs +2 -0
- package/dist/recordings-GEXp4iBT.mjs +37 -0
- package/dist/{reload-C-cV5yy_.mjs → reload-ChPiwvot.mjs} +70 -22
- package/dist/{remote-cache--aehr4Pr.mjs → remote-cache-CO2elm_b.mjs} +4 -4
- package/dist/run-B-xqRrsg.mjs +119 -0
- package/dist/{server-bare-Bq8BpSIo.mjs → server-bare-X7PUgizL.mjs} +60 -13
- package/dist/{server-expo-DUCg52-K.mjs → server-expo-DhtX6bom.mjs} +6 -10
- package/dist/server-expo-KJeD109I.mjs +2 -0
- package/dist/{settings-xB0Frkg6.mjs → settings-CduVcoxj.mjs} +77 -26
- package/dist/{settings-ssVBToDR.mjs → settings-DbL6uhH8.mjs} +16 -10
- package/dist/settings.schema.json +357 -2
- package/dist/{simslim-Bbg9UWrt.mjs → simslim-Ciluxlq4.mjs} +8 -132
- package/dist/slot-launch-BY0BrOLh.mjs +65 -0
- package/dist/{spawn-claims-PT8aQpCa.mjs → spawn-claims-D0XfSe8u.mjs} +1 -1
- package/dist/spawn-entry-DncpJvWL.mjs +16 -0
- package/dist/stage-CzaiwFsI.mjs +143 -0
- package/dist/start-CPO4wRg7.mjs +2 -0
- package/dist/{start-BZhItkiL.mjs → start-CUD9nasA.mjs} +108 -61
- package/dist/state-B4PQFXMW.d.mts +12 -0
- package/dist/state-BxCgeTQu.mjs +41 -0
- package/dist/{state-DwAU2_-D.mjs → state-CaeaHbDA.mjs} +7 -4
- package/dist/{state-l1pYwOre.mjs → state-DFm77wRn.mjs} +67 -5
- package/dist/stats-DBLLFmKd.mjs +66 -0
- package/dist/{status-TJlL0HWo.mjs → status-DYXxNOT0.mjs} +425 -260
- package/dist/{status-sszP4XKU.mjs → status-l84Gbvp2.mjs} +122 -20
- package/dist/stdout-reader-C8yjre1x.mjs +23 -0
- package/dist/stim-desktop-Cnz8CPnl.mjs +63 -0
- package/dist/stim-footprint.swift +32 -0
- package/dist/{stim-installations-CINXy_su.mjs → stim-installations-QMEI32v1.mjs} +1 -1
- package/dist/{stop-BZ2qMiBP.mjs → stop-Bad61z_2.mjs} +128 -19
- package/dist/stop-Bvf1MflS.mjs +48 -0
- package/dist/{stop-DskDKNTU.mjs → stop-CceYh1yG.mjs} +2 -2
- package/dist/stop-cause-CZ_Mr-v9.mjs +665 -0
- package/dist/supervisor-run.d.mts +44 -12
- package/dist/supervisor-run.mjs +276 -33
- package/dist/support-CDQbDkL5.mjs +315 -0
- package/dist/support-WLOzVKUv.mjs +176 -0
- package/dist/teardown-BYuQafiA.mjs +718 -0
- package/dist/{xcode-LM6A8cNZ.mjs → toolchain-DmeYjXD9.mjs} +363 -13
- package/dist/warm-progress-DVHLN4uC.mjs +463 -0
- package/dist/{idle-DxLi5OkN.mjs → watchman-Bx0wceoF.mjs} +55 -8
- package/dist/{web-sWXd5iek.mjs → web-k6w8-HnE.mjs} +39 -22
- package/dist/web-run.mjs +326 -13
- package/dist/workspace-process-lock-BL7Q0HCO.mjs +57 -0
- package/dist/workspace-state-CL2V2Ons.mjs +371 -0
- package/dist/worktree-BT_uULFx.mjs +2 -0
- package/dist/{worktree-DeBQSVtR.mjs → worktree-C0c5Udwa.mjs} +55 -54
- package/dist/{worktree-D4Do_XiQ.mjs → worktree-qXKzADSC.mjs} +161 -255
- package/package.json +10 -5
- package/shim/bundle-response.cjs +55 -10
- package/shim/bundle-response.d.cts +1 -0
- package/shim/expo-metro-config.cjs +10 -2
- package/dist/build-progress-l7d-X-ZP.mjs +0 -247
- package/dist/in-use-wYEaKQM9.mjs +0 -249
- package/dist/ownership-PUP4Dwai.mjs +0 -1138
- package/dist/server-expo-BeYwP_Xy.mjs +0 -2
- package/dist/slot-launch-BIWEW-wI.mjs +0 -19
- package/dist/start-Rw2GD3nV.mjs +0 -2
- package/dist/stats-CaSwmsSZ.mjs +0 -62
- package/dist/stats-D8rUTPHd.mjs +0 -368
- package/dist/stim-desktop-CXt8-Z3J.mjs +0 -40
- package/dist/worktree-3cBa-bQt.mjs +0 -2
- package/dist/{metro-store-DzeWkfqW.mjs → metro-store-CDDZHtPl.mjs} +1 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as formatElapsed, o as formatLongDuration, r as clockTime, u as plural } from "./command-output-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { H as projectDeviceSlots, R as deviceSlotKey } from "./config-D0DbpZtM.mjs";
|
|
2
|
+
import { a as formatElapsed, o as formatLongDuration, r as clockTime, u as plural } from "./command-output-CliHHn7a.mjs";
|
|
3
|
+
import { n as isInsideProject } from "./metro-Di8EN2Tb.mjs";
|
|
4
|
+
import { r as cdpEndpoint } from "./state-DFm77wRn.mjs";
|
|
5
|
+
import { basename, sep } from "path";
|
|
6
|
+
import { ACTIVITY_RECENCY_BASES } from "@stim-cli/core/state";
|
|
5
7
|
//#region src/status.ts
|
|
6
8
|
const IOS_SIM_MB = 1500;
|
|
7
9
|
const ANDROID_EMULATOR_MB = 2500;
|
|
@@ -31,7 +33,7 @@ function avdExpected({ slot, serial, launches, leasedIds, running, now }) {
|
|
|
31
33
|
if (!launch) return false;
|
|
32
34
|
return running || now - Date.parse(launch.launchedAt) < 18e5;
|
|
33
35
|
}
|
|
34
|
-
function androidStatus(android, androidRuntime, androidDeviceProfile) {
|
|
36
|
+
function androidStatus(android, androidRuntime, androidDeviceProfile, facts) {
|
|
35
37
|
if (!android) return null;
|
|
36
38
|
return {
|
|
37
39
|
name: android.avdName ?? android.serial,
|
|
@@ -41,10 +43,20 @@ function androidStatus(android, androidRuntime, androidDeviceProfile) {
|
|
|
41
43
|
serial: androidRuntime.serial,
|
|
42
44
|
state: androidRuntime.state
|
|
43
45
|
} : {},
|
|
44
|
-
...androidDeviceProfile ? { deviceProfile: androidDeviceProfile } : {}
|
|
46
|
+
...androidDeviceProfile ? { deviceProfile: androidDeviceProfile } : {},
|
|
47
|
+
...facts
|
|
45
48
|
};
|
|
46
49
|
}
|
|
47
|
-
function
|
|
50
|
+
function idleShutdownFact(stopped, record) {
|
|
51
|
+
return stopped && record ? { idleShutdown: record } : {};
|
|
52
|
+
}
|
|
53
|
+
function stoppedMetroFacts(idleStop, lastStop) {
|
|
54
|
+
return {
|
|
55
|
+
...idleStop ? { idleStop } : {},
|
|
56
|
+
...lastStop ? { lastStop } : {}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function environmentState(project, { simsByUdid = {}, metro = null, worktrees = [], simsAvailable = true, androidRuntime = null, androidRuntimes = {}, androidDeviceProfile = null, androidDeviceProfiles = {}, supervisor = null, logs = null, remote = null, idleStop = null, lastStop = null, idleShutdowns = {}, launches = {}, leasedIds = /* @__PURE__ */ new Set(), now = Date.now(), slot = "default", workspaceRunning = false, workspaces } = {}) {
|
|
48
60
|
const ios = project.platforms?.ios;
|
|
49
61
|
const android = project.platforms?.android;
|
|
50
62
|
const sim = ios ? simsByUdid[ios.deviceUdid] : null;
|
|
@@ -67,9 +79,9 @@ function environmentState(project, { simsByUdid = {}, metro = null, worktrees =
|
|
|
67
79
|
...slot === "default" ? {} : { slot }
|
|
68
80
|
});
|
|
69
81
|
const slotFlag = slot === "default" ? "" : ` --slot ${slot}`;
|
|
70
|
-
if (metro?.notOurs && slot === "default") add("port-not-ours",
|
|
71
|
-
if (ios && !sim && simsAvailable) add("sim-missing", `recorded sim ${ios.deviceUdid} no longer exists`, `stim ios${slotFlag}
|
|
72
|
-
if (simBooted && project.metroPort && !metroRunning) add("sim-without-metro", "simulator is booted with no Metro serving it", "stim start");
|
|
82
|
+
if (metro?.notOurs && slot === "default") add("port-not-ours", ...portNotOurs(project.metroPort, portHolder(metro.cwd, project.__path, workspaces), supervisor));
|
|
83
|
+
if (ios && !sim && simsAvailable) add("sim-missing", `recorded sim ${ios.deviceUdid} no longer exists`, `stim ios${slotFlag}`, "info");
|
|
84
|
+
if (simBooted && project.metroPort && !metroRunning) add("sim-without-metro", "simulator is booted with no Metro serving it", "stim start", "info");
|
|
73
85
|
if (androidRuntime && android?.avdName) {
|
|
74
86
|
const recordedSerial = android.consolePort ? `emulator-${android.consolePort}` : android.serial;
|
|
75
87
|
const expected = avdExpected({
|
|
@@ -80,7 +92,7 @@ function environmentState(project, { simsByUdid = {}, metro = null, worktrees =
|
|
|
80
92
|
running,
|
|
81
93
|
now
|
|
82
94
|
});
|
|
83
|
-
for (const [code, message] of androidIssues(android, recordedSerial, androidRuntime, expected)) add(code, message, code === "avd-unchecked" ? "stim doctor" : `stim android${slotFlag}
|
|
95
|
+
for (const [code, message, severity] of androidIssues(android, recordedSerial, androidRuntime, expected)) add(code, message, code === "avd-unchecked" ? "stim doctor" : `stim android${slotFlag}`, severity);
|
|
84
96
|
}
|
|
85
97
|
if (supervisor?.status === "unverified") add("supervisor-unverified", `supervisor pid ${supervisor.pid} could not be verified${supervisor.reason ? `: ${supervisor.reason}` : ""}`, "stim guide errors teardown", "error");
|
|
86
98
|
const slots = [];
|
|
@@ -95,6 +107,7 @@ function environmentState(project, { simsByUdid = {}, metro = null, worktrees =
|
|
|
95
107
|
metro,
|
|
96
108
|
androidRuntime: androidRuntimes[name],
|
|
97
109
|
androidDeviceProfile: androidDeviceProfiles[name],
|
|
110
|
+
idleShutdowns,
|
|
98
111
|
launches,
|
|
99
112
|
leasedIds,
|
|
100
113
|
now,
|
|
@@ -115,20 +128,23 @@ function environmentState(project, { simsByUdid = {}, metro = null, worktrees =
|
|
|
115
128
|
...slots.length ? { slots } : {},
|
|
116
129
|
live,
|
|
117
130
|
memoryMb,
|
|
118
|
-
|
|
131
|
+
memorySource: "estimate",
|
|
132
|
+
warnings: warningTexts(issues),
|
|
119
133
|
issues,
|
|
120
134
|
ios: ios ? {
|
|
121
135
|
name: sim?.name ?? null,
|
|
122
136
|
udid: ios.deviceUdid,
|
|
123
137
|
owned: Boolean(ios.owned),
|
|
124
|
-
state: sim?.state ?? (simsAvailable ? "missing" : "unknown")
|
|
138
|
+
state: sim?.state ?? (simsAvailable ? "missing" : "unknown"),
|
|
139
|
+
...idleShutdownFact(!simBooted && sim, idleShutdowns[deviceSlotKey("ios", slot)])
|
|
125
140
|
} : null,
|
|
126
|
-
android: androidStatus(android, androidRuntime, androidDeviceProfile),
|
|
141
|
+
android: androidStatus(android, androidRuntime, androidDeviceProfile, idleShutdownFact(!androidDetected, idleShutdowns[deviceSlotKey("android", slot)])),
|
|
127
142
|
metro: project.metroPort ? {
|
|
128
143
|
port: project.metroPort,
|
|
129
144
|
running: metroRunning,
|
|
130
145
|
pid: metro?.metro?.pid ?? null,
|
|
131
|
-
...
|
|
146
|
+
...metroRunning ? {} : stoppedMetroFacts(idleStop, lastStop),
|
|
147
|
+
...portHeldBy(metro)
|
|
132
148
|
} : null,
|
|
133
149
|
supervisor: supervisor && supervisor.status !== "stale" ? {
|
|
134
150
|
pid: supervisor.pid ?? null,
|
|
@@ -144,6 +160,14 @@ function environmentState(project, { simsByUdid = {}, metro = null, worktrees =
|
|
|
144
160
|
remoteDevices: remote ? [remote] : []
|
|
145
161
|
};
|
|
146
162
|
}
|
|
163
|
+
/** Sets each environment's `memoryMb` to the sum of its owners' footprints; without footprints it keeps the estimate. */
|
|
164
|
+
function applyMachineMemory(states, machine) {
|
|
165
|
+
if (machine.memorySource !== "footprint") return;
|
|
166
|
+
for (const state of states) {
|
|
167
|
+
state.memoryMb = machine.owners.reduce((sum, owner) => owner.workspace === state.path ? sum + owner.memoryMb : sum, 0);
|
|
168
|
+
state.memorySource = machine.memorySource;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
147
171
|
/** Adds the workspace's owned Chrome to its status: the web entry, its memory, and an unverifiable browser. */
|
|
148
172
|
function withWebFacts(state, web) {
|
|
149
173
|
if (!web) return state;
|
|
@@ -167,7 +191,7 @@ function withWebFacts(state, web) {
|
|
|
167
191
|
...state,
|
|
168
192
|
live: state.live || running || web.status === "orphaned",
|
|
169
193
|
memoryMb: state.memoryMb + (running || web.status === "orphaned" ? BROWSER_MB : 0),
|
|
170
|
-
warnings: issues
|
|
194
|
+
warnings: warningTexts(issues),
|
|
171
195
|
issues,
|
|
172
196
|
web: webBrowserState(web)
|
|
173
197
|
};
|
|
@@ -184,20 +208,94 @@ function webBrowserState({ record, status }) {
|
|
|
184
208
|
headless: record.headless,
|
|
185
209
|
viewport: record.viewport,
|
|
186
210
|
profile: record.profile,
|
|
187
|
-
cdpEndpoint: running ? cdpEndpoint(record.cdpPort) : null
|
|
211
|
+
cdpEndpoint: running ? cdpEndpoint(record.cdpPort) : null,
|
|
212
|
+
targetId: running ? record.targetId ?? null : null
|
|
188
213
|
};
|
|
189
214
|
}
|
|
190
215
|
function androidIssues(android, recordedSerial, runtime, expected) {
|
|
191
216
|
const issues = [];
|
|
192
217
|
if (runtime.serial && recordedSerial && runtime.serial !== recordedSerial) issues.push(["avd-serial-changed", `owned AVD ${android.avdName} changed serial (${recordedSerial} -> ${runtime.serial}), so Metro forwarding is lost; rerun with the same build options, then reopen agent-device on ${runtime.serial}`]);
|
|
193
|
-
if (runtime.state === "missing") issues.push([
|
|
218
|
+
if (runtime.state === "missing") issues.push([
|
|
219
|
+
"avd-missing",
|
|
220
|
+
`recorded AVD ${android.avdName} no longer exists`,
|
|
221
|
+
"info"
|
|
222
|
+
]);
|
|
194
223
|
if (runtime.state === "not-detected" && expected) issues.push(["avd-not-detected", `owned AVD ${android.avdName} is not detected by adb`]);
|
|
195
224
|
if (runtime.error) issues.push(["avd-unchecked", `could not check owned AVD ${android.avdName}: ${runtime.error}`]);
|
|
196
225
|
return issues;
|
|
197
226
|
}
|
|
227
|
+
/**
|
|
228
|
+
* The owned emulators worth an `adb reverse --list`: each runs this workspace's debug app, launched onto that serial
|
|
229
|
+
* against the Metro port that is serving now.
|
|
230
|
+
*/
|
|
231
|
+
function metroReverseTargets(state, launches) {
|
|
232
|
+
const metroPort = state.metro?.running ? state.metro.port : null;
|
|
233
|
+
if (!metroPort) return [];
|
|
234
|
+
return [{
|
|
235
|
+
slot: "default",
|
|
236
|
+
android: state.android
|
|
237
|
+
}, ...state.slots ?? []].flatMap(({ slot, android }) => {
|
|
238
|
+
const launch = launches[deviceSlotKey("android", slot)];
|
|
239
|
+
if (!android?.owned || android.physical || !android.serial || android.app?.state !== "running") return [];
|
|
240
|
+
if (!launch || launch.release || launch.metroPort !== metroPort || launch.deviceId !== android.serial) return [];
|
|
241
|
+
return [{
|
|
242
|
+
slot,
|
|
243
|
+
serial: android.serial,
|
|
244
|
+
metroPort
|
|
245
|
+
}];
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
function addReverseMissingIssue(state, { slot, serial, metroPort }) {
|
|
249
|
+
const issue = {
|
|
250
|
+
code: "android-reverse-missing",
|
|
251
|
+
severity: "warning",
|
|
252
|
+
message: `the adb reverse for Metro port ${metroPort} is missing on ${serial}, so the app cannot reach Metro`,
|
|
253
|
+
remedy: "stim reload android",
|
|
254
|
+
workspace: state.path,
|
|
255
|
+
...slot === "default" ? {} : { slot }
|
|
256
|
+
};
|
|
257
|
+
state.issues.push(issue);
|
|
258
|
+
state.warnings.push(issueText(issue));
|
|
259
|
+
}
|
|
260
|
+
function portNotOurs(port, holder, supervisor) {
|
|
261
|
+
if (supervisor?.status === "ours") return [
|
|
262
|
+
`port ${port} is in use by ${holder}, so this workspace's Metro cannot serve on it`,
|
|
263
|
+
"stim stop",
|
|
264
|
+
"warning"
|
|
265
|
+
];
|
|
266
|
+
if (supervisor?.status === "unverified") return [
|
|
267
|
+
`port ${port} is in use by ${holder}`,
|
|
268
|
+
"stim guide errors teardown",
|
|
269
|
+
"warning"
|
|
270
|
+
];
|
|
271
|
+
return [
|
|
272
|
+
`port ${port} is in use by ${holder}; stim start will choose a free port`,
|
|
273
|
+
"stim start",
|
|
274
|
+
"info"
|
|
275
|
+
];
|
|
276
|
+
}
|
|
277
|
+
function portHeldBy(metro) {
|
|
278
|
+
return metro?.notOurs && metro.pid ? { heldBy: {
|
|
279
|
+
pid: metro.pid,
|
|
280
|
+
cwd: metro.cwd ?? null
|
|
281
|
+
} } : {};
|
|
282
|
+
}
|
|
283
|
+
function warningTexts(issues) {
|
|
284
|
+
return issues.filter((issue) => issue.severity !== "info").map(issueText);
|
|
285
|
+
}
|
|
286
|
+
function portHolder(cwd, self, workspaces = []) {
|
|
287
|
+
if (!cwd) return "another app";
|
|
288
|
+
const owner = workspaces.filter((path) => path !== self && isInsideProject(cwd, path)).toSorted((a, b) => b.length - a.length)[0];
|
|
289
|
+
if (owner) return `Metro for workspace ${basename(owner)}`;
|
|
290
|
+
return `a dev server in ${cwd.split(/[\\/]/).filter(Boolean).slice(-3).join("/")}`;
|
|
291
|
+
}
|
|
198
292
|
function issueText(issue) {
|
|
199
293
|
return `${issue.slot ? `${issue.slot}: ` : ""}${issue.message}; run \`${issue.remedy}\``;
|
|
200
294
|
}
|
|
295
|
+
function toMinute(iso) {
|
|
296
|
+
const time = Date.parse(iso);
|
|
297
|
+
return (/* @__PURE__ */ new Date(time - time % 6e4)).toISOString();
|
|
298
|
+
}
|
|
201
299
|
/**
|
|
202
300
|
* Activity as status reports it: `lastActivityAt` rounded down to the minute, the precision its readers show, so a
|
|
203
301
|
* new log record within the same minute does not change the payload.
|
|
@@ -207,7 +305,11 @@ function statusActivity(activity) {
|
|
|
207
305
|
if (!Number.isFinite(at)) return activity;
|
|
208
306
|
return {
|
|
209
307
|
...activity,
|
|
210
|
-
lastActivityAt: (
|
|
308
|
+
lastActivityAt: toMinute(activity.lastActivityAt),
|
|
309
|
+
...activity.recent ? { recent: Object.fromEntries(ACTIVITY_RECENCY_BASES.flatMap((basis) => {
|
|
310
|
+
const iso = activity.recent?.[basis];
|
|
311
|
+
return iso ? [[basis, toMinute(iso)]] : [];
|
|
312
|
+
})) } : {}
|
|
211
313
|
};
|
|
212
314
|
}
|
|
213
315
|
function activityLabel(activity, now) {
|
|
@@ -329,4 +431,4 @@ function deviceLeaseLines(states, now) {
|
|
|
329
431
|
return lines;
|
|
330
432
|
}
|
|
331
433
|
//#endregion
|
|
332
|
-
export {
|
|
434
|
+
export { statusActivity as _, capacity as a, withWebFacts as b, diskLine as c, gitSummaryText as d, metroReverseTargets as f, remoteDeviceState as g, remoteDeviceLine as h, applyMachineMemory as i, environmentState as l, poolLine as m, activityLabel as n, deviceLeaseLines as o, parseDfFree as p, addReverseMissingIssue as r, deviceLeaseStates as s, RECENT_LAUNCH_MS as t, formatSpace as u, tightVolumes as v, unprovisionedWorktrees as y };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { fstatSync } from "node:fs";
|
|
2
|
+
//#region src/stdout-reader.ts
|
|
3
|
+
const READER_CHECK_MS = 2e3;
|
|
4
|
+
/**
|
|
5
|
+
* Calls `onGone` once the process reading stdout is gone, for a streaming command that can stay quiet for a long
|
|
6
|
+
* time. Returns a function that stops watching.
|
|
7
|
+
*/
|
|
8
|
+
function watchStdoutReader(onGone) {
|
|
9
|
+
const parent = process.ppid;
|
|
10
|
+
const stdout = fstatSync(1);
|
|
11
|
+
const linuxPipe = process.platform === "linux" && (stdout.isFIFO() || stdout.isSocket());
|
|
12
|
+
const timer = setInterval(() => {
|
|
13
|
+
if (linuxPipe && process.ppid !== parent) onGone();
|
|
14
|
+
else process.stdout.write("");
|
|
15
|
+
}, READER_CHECK_MS);
|
|
16
|
+
process.stdout.on("error", onGone);
|
|
17
|
+
return () => {
|
|
18
|
+
clearInterval(timer);
|
|
19
|
+
process.stdout.off("error", onGone);
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
//#endregion
|
|
23
|
+
export { watchStdoutReader as t };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { t as getExecutor } from "./exec-CaErVv7t.mjs";
|
|
2
|
+
//#region src/devices/stim-desktop.ts
|
|
3
|
+
const LAUNCH_SERVICES_LOOKUP = `ObjC.import('AppKit'); const url = $.NSWorkspace.sharedWorkspace.URLForApplicationWithBundleIdentifier('dev.stim.desktop'); url.isNil() ? '' : url.path.js`;
|
|
4
|
+
const SCHEME_HANDLER_LOOKUP = `ObjC.import('AppKit'); const url = $.NSWorkspace.sharedWorkspace.URLForApplicationToOpenURL($.NSURL.URLWithString('stim-desktop://workspace')); url.isNil() ? '' : url.path.js`;
|
|
5
|
+
const STIM_DESKTOP_INSTALLED = "Stim Desktop installed";
|
|
6
|
+
/**
|
|
7
|
+
* macOS `open` passes its environment to an app it launches, so Stim Desktop
|
|
8
|
+
* started by a command run under a scoped `STIM_HOME` would serve that home.
|
|
9
|
+
*/
|
|
10
|
+
const STIM_DESKTOP_OPEN_OPTIONS = {
|
|
11
|
+
timeoutMs: 5e3,
|
|
12
|
+
killSignal: "SIGKILL",
|
|
13
|
+
omitEnv: ["STIM_HOME"]
|
|
14
|
+
};
|
|
15
|
+
/** Stim Desktop sets `STIM_DESKTOP_APP` to its bundle path for every command it runs; a non-empty value means Desktop is installed. */
|
|
16
|
+
function stimDesktopInstalled(platform = process.platform, env = process.env) {
|
|
17
|
+
if (platform !== "darwin") return false;
|
|
18
|
+
if (env.STIM_DESKTOP_APP) return true;
|
|
19
|
+
return launchServicesFinds(LAUNCH_SERVICES_LOOKUP);
|
|
20
|
+
}
|
|
21
|
+
function launchServicesFinds(script) {
|
|
22
|
+
return Boolean(getExecutor().runFileQuiet("osascript", [
|
|
23
|
+
"-l",
|
|
24
|
+
"JavaScript",
|
|
25
|
+
"-e",
|
|
26
|
+
script
|
|
27
|
+
], {
|
|
28
|
+
timeoutMs: 5e3,
|
|
29
|
+
killSignal: "SIGKILL"
|
|
30
|
+
}));
|
|
31
|
+
}
|
|
32
|
+
function stimDesktopHandlesLinks(platform = process.platform, env = process.env) {
|
|
33
|
+
if (platform !== "darwin") return false;
|
|
34
|
+
if (env.STIM_DESKTOP_APP) return true;
|
|
35
|
+
return launchServicesFinds(SCHEME_HANDLER_LOOKUP);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The links that open this workspace for a human, or undefined when no app on this Mac handles
|
|
39
|
+
* `stim-desktop://`. `target` names the device Stim Desktop focuses when the user opens the workspace.
|
|
40
|
+
*/
|
|
41
|
+
function workspaceLinks(root, target, handlesLinks = stimDesktopHandlesLinks) {
|
|
42
|
+
if (!handlesLinks()) return void 0;
|
|
43
|
+
let desktop = `stim-desktop://workspace?path=${linkParam(root)}`;
|
|
44
|
+
if (target) desktop += `&platform=${target.platform}`;
|
|
45
|
+
if (target?.slot && target.slot !== "default") desktop += `&slot=${linkParam(target.slot)}`;
|
|
46
|
+
return { desktop };
|
|
47
|
+
}
|
|
48
|
+
const linkParam = (value) => encodeURIComponent(value).replaceAll("%2F", "/");
|
|
49
|
+
function workspaceLinkLine(links) {
|
|
50
|
+
return `Open in Stim Desktop: ${links.desktop}`;
|
|
51
|
+
}
|
|
52
|
+
function settingDefault(setting, desktopInstalled = stimDesktopInstalled) {
|
|
53
|
+
if (setting.desktopDefault !== void 0 && desktopInstalled()) return {
|
|
54
|
+
value: setting.desktopDefault,
|
|
55
|
+
reason: STIM_DESKTOP_INSTALLED
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
value: setting.default,
|
|
59
|
+
reason: null
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//#endregion
|
|
63
|
+
export { workspaceLinks as a, workspaceLinkLine as i, settingDefault as n, stimDesktopInstalled as r, STIM_DESKTOP_OPEN_OPTIONS as t };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Prints "<pid> <bytes>" for each pid, the pid's physical footprint: the value Activity Monitor's Memory column and
|
|
2
|
+
// top's MEM column show. With no arguments it reads every pid proc_listallpids(3) returns.
|
|
3
|
+
//
|
|
4
|
+
// The footprint comes from proc_pid_rusage(pid, RUSAGE_INFO_V4, ...) in <libproc.h>, field ri_phys_footprint of
|
|
5
|
+
// struct rusage_info_v4 in <sys/resource.h> (macOS 10.14+). The call fails with EPERM for another user's process
|
|
6
|
+
// unless the caller is root, and with ESRCH for a pid that has exited; those pids are left out of the output.
|
|
7
|
+
|
|
8
|
+
import Darwin
|
|
9
|
+
|
|
10
|
+
func allPids() -> [pid_t] {
|
|
11
|
+
var capacity = Int(proc_listallpids(nil, 0)) + 64
|
|
12
|
+
while true {
|
|
13
|
+
var pids = [pid_t](repeating: 0, count: capacity)
|
|
14
|
+
let count = pids.withUnsafeMutableBytes { proc_listallpids($0.baseAddress, Int32($0.count)) }
|
|
15
|
+
if count < 0 { return [] }
|
|
16
|
+
if Int(count) < capacity { return Array(pids.prefix(Int(count))) }
|
|
17
|
+
capacity *= 2
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let arguments = CommandLine.arguments.dropFirst()
|
|
22
|
+
let pids = arguments.isEmpty ? allPids() : arguments.compactMap { pid_t($0) }
|
|
23
|
+
var output = ""
|
|
24
|
+
output.reserveCapacity(pids.count * 20)
|
|
25
|
+
var info = rusage_info_v4()
|
|
26
|
+
for pid in pids where pid > 0 {
|
|
27
|
+
let status = withUnsafeMutablePointer(to: &info) {
|
|
28
|
+
$0.withMemoryRebound(to: rusage_info_t?.self, capacity: 1) { proc_pid_rusage(pid, RUSAGE_INFO_V4, $0) }
|
|
29
|
+
}
|
|
30
|
+
if status == 0 { output += "\(pid) \(info.ri_phys_footprint)\n" }
|
|
31
|
+
}
|
|
32
|
+
fputs(output, stdout)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as getExecutor } from "./exec-
|
|
1
|
+
import { t as getExecutor } from "./exec-CaErVv7t.mjs";
|
|
2
2
|
import { accessSync, constants, realpathSync, statSync } from "node:fs";
|
|
3
3
|
import { delimiter, join, resolve } from "node:path";
|
|
4
4
|
//#region src/diagnostics/stim-installations.ts
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { a as
|
|
6
|
-
import {
|
|
7
|
-
import { u as
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
1
|
+
import { B as parseDeviceSlotKey, D as withConfigLock, E as upsertProject, H as projectDeviceSlots, V as parseDeviceSlotOption, W as validateDeviceSlot, o as clearSupervisor, p as getProject, z as deviceSlotPlatforms } from "./config-D0DbpZtM.mjs";
|
|
2
|
+
import { n as ownership_claim_exports, t as process_identity_exports } from "./process-identity-BmRm1qV0.mjs";
|
|
3
|
+
import { b as workspaceDir, c as writeWorkspaceState, n as clearWorkspaceStateKeys, o as withWorkspaceStateLock, r as readWorkspaceState } from "./workspace-state-CL2V2Ons.mjs";
|
|
4
|
+
import { i as findCommandWorkspace } from "./project-CV3tDFHl.mjs";
|
|
5
|
+
import { a as clearRemoteSession, c as readMetroTunnel, i as clearManagedMetroTunnel, o as clearWorkspaceLaunches, s as clearWorkspaceSupervisor, u as readRemoteSession } from "./state-CaeaHbDA.mjs";
|
|
6
|
+
import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BL7Q0HCO.mjs";
|
|
7
|
+
import { f as refuseNoProject, l as phaseLine, p as releasedLeaseFact, u as plural } from "./command-output-CliHHn7a.mjs";
|
|
8
|
+
import { n as fileLeaseIo, u as releaseWorkspaceLeases } from "./device-lease-W5Pi1q38.mjs";
|
|
9
|
+
import { l as resolveProjectMetro, o as pidExists, r as killMetroTree, u as signalProcessTree } from "./metro-Di8EN2Tb.mjs";
|
|
10
|
+
import { a as teardownOwnedBrowser, i as teardownOwnedAvd, o as teardownOwnedIosSim } from "./teardown-BYuQafiA.mjs";
|
|
11
|
+
import { a as logVanishedSupervisor, l as withdrawDevServerStopRequest, p as stopTunnel, s as requestDevServerStop, u as resolveSupervisorTarget } from "./stop-cause-CZ_Mr-v9.mjs";
|
|
12
|
+
import { n as verifyCollectorOwnership } from "./ownership-BkYxUtuj.mjs";
|
|
13
|
+
import { t as stopMacosApp } from "./stop-Bvf1MflS.mjs";
|
|
14
|
+
import { f as resolveEasCliBin } from "./remote-cache-CO2elm_b.mjs";
|
|
15
|
+
import { i as endRecordedSession } from "./device-remote-9ulPsdhY.mjs";
|
|
16
|
+
import { a as decideStopAction, c as nativeRunWaitNotice, i as clearNativeRunCancel, l as requestNativeRunCancel, n as NATIVE_RUN_WAIT_MS, o as describeNativeRunHolder, s as nativeRunHolder, t as NATIVE_RUN_LOCK } from "./native-run-DFDoIRbY.mjs";
|
|
12
17
|
import chalk from "chalk";
|
|
13
18
|
//#region src/commands/stop.ts
|
|
14
19
|
const DEFAULT_WAIT_MS = 1e4;
|
|
@@ -121,6 +126,7 @@ function remoteEnded(entry) {
|
|
|
121
126
|
}
|
|
122
127
|
async function runStop(options) {
|
|
123
128
|
if (options.slot === void 0) return stopWorkspace(options);
|
|
129
|
+
if (options.slot === "web") return stopBrowserOnly(options);
|
|
124
130
|
const slot = validateDeviceSlot(options.slot);
|
|
125
131
|
const root = options.root;
|
|
126
132
|
const project = options.project === void 0 ? getProject(root) : options.project;
|
|
@@ -149,6 +155,7 @@ async function runStop(options) {
|
|
|
149
155
|
},
|
|
150
156
|
clearRegistration: async () => true,
|
|
151
157
|
teardownBrowser: null,
|
|
158
|
+
stopMacos: false,
|
|
152
159
|
releaseLeases: (projectRoot) => releaseWorkspaceLeases(projectRoot, { slot })
|
|
153
160
|
});
|
|
154
161
|
result.outcomes.port = {
|
|
@@ -163,7 +170,65 @@ async function runStop(options) {
|
|
|
163
170
|
result.summary = summarize(root, result.outcomes, result.ok);
|
|
164
171
|
return result;
|
|
165
172
|
}
|
|
166
|
-
async function
|
|
173
|
+
async function stopBrowserOnly({ root, project = void 0, teardownBrowser = (projectRoot) => teardownOwnedBrowser(projectRoot), report = (line) => console.error(line) }) {
|
|
174
|
+
const kept = "Only the owned Chrome stops; the workspace server and devices keep running.";
|
|
175
|
+
const port = (project === void 0 ? getProject(root) : project)?.metroPort ?? null;
|
|
176
|
+
const outcomes = {
|
|
177
|
+
supervisor: {
|
|
178
|
+
status: "skipped",
|
|
179
|
+
reason: kept
|
|
180
|
+
},
|
|
181
|
+
collectors: {
|
|
182
|
+
status: "skipped",
|
|
183
|
+
entries: []
|
|
184
|
+
},
|
|
185
|
+
metro: {
|
|
186
|
+
status: "skipped",
|
|
187
|
+
reason: kept
|
|
188
|
+
},
|
|
189
|
+
device: {
|
|
190
|
+
ios: null,
|
|
191
|
+
android: null
|
|
192
|
+
},
|
|
193
|
+
port: port === null ? {
|
|
194
|
+
status: "none",
|
|
195
|
+
port
|
|
196
|
+
} : {
|
|
197
|
+
status: "kept",
|
|
198
|
+
port,
|
|
199
|
+
reason: kept
|
|
200
|
+
},
|
|
201
|
+
metroTunnel: { status: "none" },
|
|
202
|
+
releasedLeases: []
|
|
203
|
+
};
|
|
204
|
+
outcomes.device.web = await closeBrowser(root, teardownBrowser, report);
|
|
205
|
+
if (!outcomes.device.web) return {
|
|
206
|
+
ok: true,
|
|
207
|
+
outcomes,
|
|
208
|
+
summary: `Stopped: no owned Chrome was running (${root})`
|
|
209
|
+
};
|
|
210
|
+
const ok = outcomes.device.web.status === "shut-down";
|
|
211
|
+
return {
|
|
212
|
+
ok,
|
|
213
|
+
outcomes,
|
|
214
|
+
summary: summarize(root, outcomes, ok)
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
async function stopWorkspaceMacos(root, failed, enabled) {
|
|
218
|
+
if (enabled === false || readWorkspaceState(root)?.macos === void 0) return void 0;
|
|
219
|
+
try {
|
|
220
|
+
await stopMacosApp(root);
|
|
221
|
+
return { status: "stopped" };
|
|
222
|
+
} catch (error) {
|
|
223
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
224
|
+
failed(reason);
|
|
225
|
+
return {
|
|
226
|
+
status: "failed",
|
|
227
|
+
reason
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async function stopWorkspace({ root, project = void 0, state = void 0, collectors = void 0, signalCollector = (pid) => signalProcessTree(pid), verifyCollector = verifyCollectorOwnership, clearCollectors = clearCollectorState, isAlive = pidExists, killGroup = killMetroTree, signalServer = (pid) => signalProcessTree(pid), inspectIdentity = process_identity_exports.inspectProcessIdentity, waitForDeath = void 0, waitMs = DEFAULT_WAIT_MS, resolveMetro = resolveProjectMetro, teardownIos = teardownOwnedIosSim, teardownAvd = teardownOwnedAvd, teardownBrowser = (projectRoot) => teardownOwnedBrowser(projectRoot), stopMacos, remoteDevice = void 0, teardownRemoteSession = defaultTeardownRemoteSession, metroTunnel = void 0, stopMetroTunnel = stopTunnel, releaseLeases = releaseWorkspaceLeases, freePort = defaultFreePort, clearRegistration = defaultClearRegistration, clearState = clearSupervisorState, report = (line) => console.error(line) }) {
|
|
167
232
|
const proj = project === void 0 ? getProject(root) : project;
|
|
168
233
|
const sup = state === void 0 ? readSupervisorState(root) : state;
|
|
169
234
|
const collectorRecords = collectors === void 0 ? readCollectorState(root) : collectors;
|
|
@@ -202,6 +267,7 @@ async function stopWorkspace({ root, project = void 0, state = void 0, collector
|
|
|
202
267
|
});
|
|
203
268
|
if (target.status === "none") report(chalk.dim(phaseLine("stop", "no supervisor recorded")));
|
|
204
269
|
else if (target.status === "stale") {
|
|
270
|
+
logVanishedSupervisor(root, sup);
|
|
205
271
|
outcomes.supervisor = {
|
|
206
272
|
status: "already-stopped",
|
|
207
273
|
pid: target.pid,
|
|
@@ -219,6 +285,7 @@ async function stopWorkspace({ root, project = void 0, state = void 0, collector
|
|
|
219
285
|
stillHolding = `supervisor pid ${target.pid} could not be verified`;
|
|
220
286
|
} else {
|
|
221
287
|
outcomes.supervisor = await stopSupervisor(target, {
|
|
288
|
+
root,
|
|
222
289
|
killGroup,
|
|
223
290
|
waiter,
|
|
224
291
|
report
|
|
@@ -318,6 +385,10 @@ async function stopWorkspace({ root, project = void 0, state = void 0, collector
|
|
|
318
385
|
}
|
|
319
386
|
outcomes.device.web = await closeBrowser(root, teardownBrowser, report);
|
|
320
387
|
if (outcomes.device.web && outcomes.device.web.status !== "shut-down") ok = false;
|
|
388
|
+
outcomes.macos = await stopWorkspaceMacos(root, (reason) => {
|
|
389
|
+
ok = false;
|
|
390
|
+
report(reason);
|
|
391
|
+
}, stopMacos);
|
|
321
392
|
const remote = remoteDevice === void 0 ? readRemoteSession(root) : remoteDevice;
|
|
322
393
|
const sessionId = typeof remote?.sessionId === "string" ? remote.sessionId : null;
|
|
323
394
|
if (sessionId) {
|
|
@@ -470,8 +541,9 @@ async function reapCollectors(root, collectors, { isAlive, signal, report, verif
|
|
|
470
541
|
entries
|
|
471
542
|
};
|
|
472
543
|
}
|
|
473
|
-
async function stopSupervisor(target, { killGroup, waiter, report }) {
|
|
544
|
+
async function stopSupervisor(target, { root, killGroup, waiter, report }) {
|
|
474
545
|
report(chalk.dim(phaseLine("stop", `sending SIGTERM to supervisor process group ${target.pid}`)));
|
|
546
|
+
requestDevServerStop(root, target.processToken, { by: "stim stop" });
|
|
475
547
|
let signalled = false;
|
|
476
548
|
try {
|
|
477
549
|
signalled = killGroup(target.pid, target.processToken);
|
|
@@ -480,6 +552,7 @@ async function stopSupervisor(target, { killGroup, waiter, report }) {
|
|
|
480
552
|
report(chalk.red(phaseLine("stop", `could not signal supervisor pid ${target.pid}: ${String(e?.message || e)}`)));
|
|
481
553
|
}
|
|
482
554
|
if (!signalled) {
|
|
555
|
+
withdrawDevServerStopRequest(root, target.processToken);
|
|
483
556
|
const reason = `could not signal supervisor pid ${target.pid}`;
|
|
484
557
|
report(chalk.red(phaseLine("stop", reason)));
|
|
485
558
|
return {
|
|
@@ -691,6 +764,8 @@ async function closeBrowser(root, teardownBrowser, report) {
|
|
|
691
764
|
}
|
|
692
765
|
function summarize(root, outcomes, ok) {
|
|
693
766
|
const parts = [];
|
|
767
|
+
if (outcomes.macos?.status === "stopped") parts.push("macOS app stopped");
|
|
768
|
+
if (outcomes.macos?.status === "failed") parts.push("macOS app could not be stopped");
|
|
694
769
|
if (outcomes.supervisor.status === "stopped") parts.push(`supervisor pid ${outcomes.supervisor.pid} stopped`);
|
|
695
770
|
if (outcomes.supervisor.status === "already-stopped") parts.push("supervisor already stopped");
|
|
696
771
|
if (outcomes.supervisor.status === "timeout") parts.push(`supervisor pid ${outcomes.supervisor.pid} still running`);
|
|
@@ -752,6 +827,17 @@ function ownerGoneRefusal(holder, at) {
|
|
|
752
827
|
remedy: `Wait for the build tool${child} to exit, or end it yourself, then run \`stim stop\` again.`
|
|
753
828
|
});
|
|
754
829
|
}
|
|
830
|
+
function recordedDeviceSlots(root) {
|
|
831
|
+
const slots = new Set(projectDeviceSlots(getProject(root)).map(({ slot }) => slot));
|
|
832
|
+
for (const key of Object.keys({
|
|
833
|
+
...readCollectorState(root),
|
|
834
|
+
...fileLeaseIo.readHolder(root)
|
|
835
|
+
})) {
|
|
836
|
+
const parsed = parseDeviceSlotKey(key);
|
|
837
|
+
if (parsed) slots.add(parsed.slot);
|
|
838
|
+
}
|
|
839
|
+
return [...slots].toSorted();
|
|
840
|
+
}
|
|
755
841
|
function workspaceDeviceSlots(root) {
|
|
756
842
|
const slots = projectDeviceSlots(getProject(root)).filter(({ platforms }) => Object.values(platforms).some(Boolean)).map(({ slot }) => slot);
|
|
757
843
|
if (typeof readRemoteSession(root)?.sessionId === "string" && !slots.includes("default")) slots.push("default");
|
|
@@ -772,7 +858,21 @@ function readRemoteSessionEntry(root, report) {
|
|
|
772
858
|
* SIGINT and given INTERRUPT_WAIT_MS to exit; a build for another slot that stays is left running while
|
|
773
859
|
* the slot stops without the lock; any other holder is waited on with a visible notice.
|
|
774
860
|
*/
|
|
775
|
-
async function stopWorkspaceNow({ root, slot, report = (line) => console.error(line), now = Date.now, sleep, waitMs = NATIVE_RUN_WAIT_MS, interruptWaitMs = INTERRUPT_WAIT_MS, inspectIdentity = process_identity_exports.inspectProcessIdentity, interrupt = (pid) => process.kill(pid, "SIGINT"), endRemote = (projectRoot) => readRemoteSessionEntry(projectRoot, report), deviceSlots = workspaceDeviceSlots, stop = (options) => runStop(options) }) {
|
|
861
|
+
async function stopWorkspaceNow({ root, slot, report = (line) => console.error(line), now = Date.now, sleep, waitMs = NATIVE_RUN_WAIT_MS, interruptWaitMs = INTERRUPT_WAIT_MS, inspectIdentity = process_identity_exports.inspectProcessIdentity, interrupt = (pid) => process.kill(pid, "SIGINT"), endRemote = (projectRoot) => readRemoteSessionEntry(projectRoot, report), deviceSlots = workspaceDeviceSlots, recordedSlots = recordedDeviceSlots, stop = (options) => runStop(options) }) {
|
|
862
|
+
if (slot === "web") return stop({
|
|
863
|
+
root,
|
|
864
|
+
slot
|
|
865
|
+
});
|
|
866
|
+
let unknownSlot = slot !== void 0 && !recordedSlots(root).includes(slot);
|
|
867
|
+
const stillUnknown = () => {
|
|
868
|
+
const known = recordedSlots(root);
|
|
869
|
+
if (known.includes(slot)) return null;
|
|
870
|
+
return new StopBlocked({
|
|
871
|
+
code: "STIM_BAD_ARG",
|
|
872
|
+
message: `This workspace has no device slot named ${slot}. Its slots are ${known.join(", ")}.`,
|
|
873
|
+
remedy: `Run \`stim stop --slot <name>\` with one of those slots, \`stim stop --slot web\` to close only the owned Chrome, or \`stim stop\` for the whole workspace.`
|
|
874
|
+
});
|
|
875
|
+
};
|
|
776
876
|
let remote = null;
|
|
777
877
|
let remoteHandled = slot !== void 0;
|
|
778
878
|
const endRemoteNow = () => {
|
|
@@ -802,6 +902,11 @@ async function stopWorkspaceNow({ root, slot, report = (line) => console.error(l
|
|
|
802
902
|
}
|
|
803
903
|
endRemoteNow();
|
|
804
904
|
const target = nativeRunHolder(holder);
|
|
905
|
+
if (unknownSlot) {
|
|
906
|
+
const refusal = target.slot === slot ? null : stillUnknown();
|
|
907
|
+
if (refusal) throw refusal;
|
|
908
|
+
unknownSlot = false;
|
|
909
|
+
}
|
|
805
910
|
const { action } = decideStopAction({
|
|
806
911
|
stopSlot: slot,
|
|
807
912
|
holder: target,
|
|
@@ -845,10 +950,14 @@ async function stopWorkspaceNow({ root, slot, report = (line) => console.error(l
|
|
|
845
950
|
return result;
|
|
846
951
|
};
|
|
847
952
|
try {
|
|
848
|
-
return withRemote(await withWorkspaceProcessLock(workspaceDir(root), NATIVE_RUN_LOCK, () =>
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
953
|
+
return withRemote(await withWorkspaceProcessLock(workspaceDir(root), NATIVE_RUN_LOCK, () => {
|
|
954
|
+
const refusal = unknownSlot ? stillUnknown() : null;
|
|
955
|
+
if (refusal) throw refusal;
|
|
956
|
+
return stop({
|
|
957
|
+
root,
|
|
958
|
+
slot
|
|
959
|
+
});
|
|
960
|
+
}, {
|
|
852
961
|
external: true,
|
|
853
962
|
waitMs,
|
|
854
963
|
now,
|
|
@@ -875,7 +984,7 @@ async function stopWorkspaceNow({ root, slot, report = (line) => console.error(l
|
|
|
875
984
|
}
|
|
876
985
|
}
|
|
877
986
|
function stopCommand(program) {
|
|
878
|
-
program.command("stop").description("The inverse of `start`: halt this workspace's supervisor, shut the owned device down (never deleted), and free the reserved port. Non-destructive -- the device stays assigned, so coming back costs a boot. Acts on the current workspace.").option("--slot <name>", "Stop only this device slot, keeping the shared server running. Use 'default' for the workspace's default device.", parseDeviceSlotOption).option("--json", "print the per-step outcomes as JSON").action(async (opts) => {
|
|
987
|
+
program.command("stop").description("The inverse of `start`: halt this workspace's supervisor, shut the owned device down (never deleted), and free the reserved port. Non-destructive -- the device stays assigned, so coming back costs a boot. Acts on the current workspace.").option("--slot <name>", "Stop only this device slot, keeping the shared server running. Use 'default' for the workspace's default device, or 'web' to close only the owned Chrome.", (slot) => slot === "web" ? slot : parseDeviceSlotOption(slot)).option("--json", "print the per-step outcomes as JSON").action(async (opts) => {
|
|
879
988
|
const root = findCommandWorkspace(process.cwd());
|
|
880
989
|
if (!root) {
|
|
881
990
|
refuseNoProject({ json: Boolean(opts.json) });
|