stim 1.11.0 → 1.13.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 +3 -1
- package/dist/{activity-BtG4Va9l.mjs → activity-DfVKZTEB.mjs} +44 -26
- package/dist/{android-BA2i_KAV.mjs → android-BHfAu2mf.mjs} +1 -1
- package/dist/{android-C1q4l523.mjs → android-CD8pr8Q2.mjs} +112 -54
- package/dist/{android-cas-DfJkwCXW.mjs → android-cas-B9Kv9aIy.mjs} +12 -10
- package/dist/{android-C4zG2Tuc.mjs → android-qx_31wJo.mjs} +414 -118
- package/dist/{app-install-DHut6JnV.mjs → app-install-CDFY2nUL.mjs} +1 -1
- package/dist/budget-E5o91NR1.mjs +1739 -0
- package/dist/{build-plan-CeAYgKAx.mjs → build-plan-BTa93o_r.mjs} +125 -107
- package/dist/{build-progress-CDwogz6R.mjs → build-progress-l7d-X-ZP.mjs} +76 -8
- package/dist/{cache-manifest-Pubfp8wV.mjs → cache-manifest-DWPF4RKJ.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cdp-Dq_ypAlJ.mjs +115 -0
- package/dist/chrome-B5hWgQJS.mjs +61 -0
- package/dist/cli.mjs +17 -16
- package/dist/collector-run.mjs +15 -9
- package/dist/{command-output-D5z2_bcF.mjs → command-output-BIL7rlX7.mjs} +1 -1
- package/dist/{config-BEq0Qw_a.mjs → config-0jCcMM0E.mjs} +10 -1
- package/dist/created-devices-Cd558h31.mjs +37 -0
- package/dist/{dependency-state-r9UesA9a.mjs → dependency-state-DNN0yfx6.mjs} +1 -1
- package/dist/{deps-OF7CD5Dx.mjs → deps-2Wo81np9.mjs} +2 -2
- package/dist/{device-BwH2iI3O.mjs → device-Cf7vl-so.mjs} +7 -7
- package/dist/{device-lease-D83nqeMX.mjs → device-lease-CL9dZf73.mjs} +55 -6
- package/dist/{device-pool-DHj7cdPy.mjs → device-pool-MuoiyRcu.mjs} +4 -4
- package/dist/{device-remote-BQZSnnmE.mjs → device-remote-DT9ngZiD.mjs} +254 -579
- package/dist/{doctor-D8QdMRMf.mjs → doctor-D2iysNNH.mjs} +50 -20
- package/dist/{error-diagnostics-UYFSQwRy.mjs → error-diagnostics-BoiOhck8.mjs} +7 -7
- package/dist/{gc-COZteyiq.mjs → gc-eA-PRBX8.mjs} +1022 -131
- package/dist/{guide-NDvTmMi-.mjs → guide-BfZ0OmiP.mjs} +909 -132
- package/dist/{guide-status-CWvti2Lg.mjs → guide-status-BaMYu55M.mjs} +2 -2
- package/dist/{idle-NETbeNiQ.mjs → idle-DxLi5OkN.mjs} +43 -10
- package/dist/{in-use-DVhlSoyn.mjs → in-use-wYEaKQM9.mjs} +11 -7
- package/dist/{ios-CQLKpNfr.mjs → ios-CcfCeZ19.mjs} +103 -58
- package/dist/{ios-DArbmwer.mjs → ios-CmEzZiOj.mjs} +31 -22
- package/dist/{ios-device-CwDZo0X4.mjs → ios-device-BW-t_Lkv.mjs} +1 -1
- package/dist/{launch-verify-CuPDGXdr.mjs → launch-verify-CuJaBpEa.mjs} +33 -20
- package/dist/{logs-B44a1UeU.mjs → logs-DcSRbC2t.mjs} +12 -11
- package/dist/{merge-state-DDxS-XIZ.mjs → merge-state-ytFQCeh4.mjs} +38 -8
- package/dist/{metro-BWLOJ_Wr.mjs → metro-BPeY1YMh.mjs} +5 -5
- package/dist/{named-ports-Dc7neAlc.mjs → named-ports-n7tnJsI8.mjs} +78 -6
- package/dist/native-runtime-CiiFHt0e.mjs +74 -0
- package/dist/{ownership-D2xbLVoz.mjs → ownership-Bz2kETAS.mjs} +2 -2
- package/dist/{ownership-BiojdtAp.mjs → ownership-PUP4Dwai.mjs} +175 -217
- package/dist/page-BTu7I60K.mjs +29 -0
- package/dist/{ports-DwBaRFJs.mjs → ports-C5UutqpJ.mjs} +5 -5
- package/dist/{project-BXZzOABt.mjs → project-DGeclhvO.mjs} +55 -87
- package/dist/{reload-By9Dwsrt.mjs → reload-C-cV5yy_.mjs} +80 -21
- package/dist/remote-cache--aehr4Pr.mjs +526 -0
- package/dist/{server-bare-D_RK1RCj.mjs → server-bare-Bq8BpSIo.mjs} +1 -1
- package/dist/server-expo-BeYwP_Xy.mjs +2 -0
- package/dist/{server-expo-o6H5gcBc.mjs → server-expo-DUCg52-K.mjs} +2 -2
- package/dist/{settings-C0ugALSC.mjs → settings-ssVBToDR.mjs} +29 -17
- package/dist/{settings-B5oFZEri.mjs → settings-xB0Frkg6.mjs} +13 -4
- package/dist/settings.schema.json +74 -2
- package/dist/{simslim-CIR3IYJO.mjs → simslim-Bbg9UWrt.mjs} +13 -6
- package/dist/{slot-launch-rggO1Esw.mjs → slot-launch-BIWEW-wI.mjs} +4 -4
- package/dist/{spawn-claims-BJwXTMPt.mjs → spawn-claims-PT8aQpCa.mjs} +1 -1
- package/dist/{start-BSsf2tUs.mjs → start-BZhItkiL.mjs} +110 -18
- package/dist/{start-i8zc9VmS.mjs → start-Rw2GD3nV.mjs} +1 -1
- package/dist/{state-WGdIbi_d.mjs → state-DwAU2_-D.mjs} +15 -3
- package/dist/state-l1pYwOre.mjs +148 -0
- package/dist/{stats-DBYJ5bxL.mjs → stats-CaSwmsSZ.mjs} +4 -4
- package/dist/{stats-BAgJaLA4.mjs → stats-D8rUTPHd.mjs} +1 -1
- package/dist/{status-CitcBRsC.mjs → status-TJlL0HWo.mjs} +649 -116
- package/dist/{status-Q-WDEGCB.mjs → status-sszP4XKU.mjs} +130 -28
- package/dist/stim-desktop-CXt8-Z3J.mjs +40 -0
- package/dist/{stop-DIhrvu0B.mjs → stop-BZ2qMiBP.mjs} +50 -18
- package/dist/{stop-rst5mL14.mjs → stop-DskDKNTU.mjs} +2 -2
- package/dist/supervisor-run.mjs +11 -11
- package/dist/web-run.d.mts +22 -0
- package/dist/web-run.mjs +481 -0
- package/dist/web-sWXd5iek.mjs +394 -0
- package/dist/{worktree-DhxDTK44.mjs → worktree-3cBa-bQt.mjs} +1 -1
- package/dist/{worktree-KOjMX3zA.mjs → worktree-D4Do_XiQ.mjs} +229 -21
- package/dist/{worktree-BlizKDpZ.mjs → worktree-DeBQSVtR.mjs} +3 -1
- package/dist/{xcode-B5SPD--o.mjs → xcode-LM6A8cNZ.mjs} +7 -7
- package/package.json +5 -5
- package/dist/budget-De3P2rq_.mjs +0 -620
- package/dist/native-run-D7yB_AsB.mjs +0 -143
- package/dist/server-expo-B-CCUJZj.mjs +0 -2
- package/dist/workspace-process-lock-Cx74RSDZ.mjs +0 -57
- package/dist/workspaces-COD2xCz-.mjs +0 -1013
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { A as ownership_claim_exports, B as deviceSlotPlatforms, E as withConfigLock, O as process_identity_exports, U as projectDeviceSlots, _ as loadConfig, d as getConfigDir, k as acquireAvdClaim, p as getProject } from "./config-0jCcMM0E.mjs";
|
|
2
2
|
import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
|
|
3
|
-
import {
|
|
4
|
-
import { r as
|
|
3
|
+
import { a as pidExists, k as workspaceDir, l as signalProcessTree } from "./metro-BPeY1YMh.mjs";
|
|
4
|
+
import { a as withWorkspaceProcessLock, r as releaseBrowserPort, s as workspaceProcessLockPath } from "./named-ports-n7tnJsI8.mjs";
|
|
5
5
|
import { t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
|
|
6
|
-
import { l as phaseLine } from "./command-output-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
6
|
+
import { l as phaseLine } from "./command-output-BIL7rlX7.mjs";
|
|
7
|
+
import { a as readAgentDeviceRecords } from "./activity-DfVKZTEB.mjs";
|
|
8
|
+
import { F as ownedAvdDirectory, I as ownedAvdMatchesConfiguration, Q as waitForAndroidEmulatorShutdown, X as sleepSync, Y as shutdownAndroidEmulator, b as deleteAvd, d as assertOwnedAvdStopped, et as wipeAvdUserData, g as avdStorageRoots, j as listOrphanedAvdDirectories, lt as readParked, m as avdPathExists, q as resolveOwnedAvdSerial, st as parkSim, tt as isStimOwnedAvd, ut as removeParkedAfter, z as ownedAvdSystemImage } from "./android-qx_31wJo.mjs";
|
|
9
|
+
import { n as readCreatedDevices, r as recordCreatedDevice, t as forgetCreatedDevice } from "./created-devices-Cd558h31.mjs";
|
|
10
|
+
import { a as readWebRecord, d as webProfileDir, g as removeSingletonFiles, h as liveProfileHolder, i as clearWebRecord, m as chromeProcessState, s as webClaimRoot, t as CDP_PORT_LABEL } from "./state-l1pYwOre.mjs";
|
|
11
|
+
import { S as resolveOwnedIosSim, a as deleteParkedIosSim, d as listIosDeviceTypes, g as parseRuntimeVersion, h as parkedSimName, i as deleteIosSim, o as eraseIosSim, v as renameIosSim, w as shutdownIosSim } from "./ios-CmEzZiOj.mjs";
|
|
12
|
+
import { closeSync, existsSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, rmSync, unlinkSync } from "node:fs";
|
|
13
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
11
14
|
import { createHash, randomUUID } from "node:crypto";
|
|
15
|
+
import { homedir } from "node:os";
|
|
12
16
|
import { isDeepStrictEqual } from "node:util";
|
|
13
17
|
import { resolve4 } from "node:dns/promises";
|
|
14
18
|
import { request } from "node:https";
|
|
@@ -22,17 +26,41 @@ function parseAgentDeviceSessions(output) {
|
|
|
22
26
|
const id = row.platform === "ios" ? row.device_udid : row.id;
|
|
23
27
|
if (row.platform !== "ios" && row.platform !== "android" || typeof row.name !== "string" || !row.name.trim() || typeof id !== "string" || !id || typeof row.createdAt !== "number" || !Number.isFinite(row.createdAt) || row.platform === "ios" && row.id !== void 0 && row.id !== id) return [];
|
|
24
28
|
return [{
|
|
25
|
-
name: row.name,
|
|
29
|
+
name: typeof row.address === "string" && row.address.trim() ? row.address : row.name,
|
|
26
30
|
platform: row.platform,
|
|
27
31
|
id,
|
|
32
|
+
device: typeof row.device === "string" ? row.device : null,
|
|
28
33
|
createdAt: row.createdAt
|
|
29
34
|
}];
|
|
30
35
|
});
|
|
31
36
|
}
|
|
37
|
+
function isOwnDeviceSession(session, device, owner) {
|
|
38
|
+
if (session.platform !== device.platform || session.id !== device.id) return false;
|
|
39
|
+
if (device.platform === "android" && session.device !== device.avdName.replaceAll("_", " ")) return false;
|
|
40
|
+
if (!owner) return true;
|
|
41
|
+
return owner.claims.some((claim) => claim.session === session.name && claim.workspace !== null && within(owner.workspace, claim.workspace));
|
|
42
|
+
}
|
|
43
|
+
function within(root, path) {
|
|
44
|
+
const rest = relative(root, path);
|
|
45
|
+
return rest === "" || !isAbsolute(rest) && rest.split(sep)[0] !== "..";
|
|
46
|
+
}
|
|
47
|
+
function canonical(path) {
|
|
48
|
+
try {
|
|
49
|
+
return realpathSync(path);
|
|
50
|
+
} catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function deviceClaims(device) {
|
|
55
|
+
return readAgentDeviceRecords(homedir()).filter((record) => record.kind === "claim" && record.deviceId === device.id).map((record) => ({
|
|
56
|
+
session: record.session,
|
|
57
|
+
workspace: record.workspace && canonical(record.workspace)
|
|
58
|
+
}));
|
|
59
|
+
}
|
|
32
60
|
function report(message) {
|
|
33
61
|
process.stderr.write(`${phaseLine("device", message)}\n`);
|
|
34
62
|
}
|
|
35
|
-
function closeOwnedDeviceSessions(device, stillOwned) {
|
|
63
|
+
function closeOwnedDeviceSessions(device, stillOwned, workspace) {
|
|
36
64
|
const exec = getExecutor();
|
|
37
65
|
const deadline = Date.now() + 15e3;
|
|
38
66
|
const run = (args) => {
|
|
@@ -56,7 +84,13 @@ function closeOwnedDeviceSessions(device, stillOwned) {
|
|
|
56
84
|
]));
|
|
57
85
|
try {
|
|
58
86
|
if (!exec.findExecutable("agent-device")) return;
|
|
59
|
-
const
|
|
87
|
+
const root = workspace === void 0 ? void 0 : canonical(workspace);
|
|
88
|
+
if (root === null) return;
|
|
89
|
+
const owner = root === void 0 ? void 0 : {
|
|
90
|
+
workspace: root,
|
|
91
|
+
claims: deviceClaims(device)
|
|
92
|
+
};
|
|
93
|
+
const sessions = list().filter((session) => isOwnDeviceSession(session, device, owner));
|
|
60
94
|
for (const session of sessions) try {
|
|
61
95
|
if (!list().some((current) => current.name === session.name && current.platform === session.platform && current.id === session.id && current.createdAt === session.createdAt)) continue;
|
|
62
96
|
if (!stillOwned()) return;
|
|
@@ -79,202 +113,6 @@ function closeOwnedDeviceSessions(device, stillOwned) {
|
|
|
79
113
|
report(`could not list agent-device sessions for ${device.id}: ${error.message}`);
|
|
80
114
|
}
|
|
81
115
|
}
|
|
82
|
-
const POOL_SETTING_REMEDY = "Run `stim guide settings` for the device pool bounds and where it can be set.";
|
|
83
|
-
const MAX_SETTING = {
|
|
84
|
-
ios: {
|
|
85
|
-
key: "iosParkedMax",
|
|
86
|
-
env: "STIM_POOL_IOS_PARKED_MAX"
|
|
87
|
-
},
|
|
88
|
-
android: {
|
|
89
|
-
key: "androidParkedMax",
|
|
90
|
-
env: "STIM_POOL_ANDROID_PARKED_MAX"
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
function parseMax(raw, strings) {
|
|
94
|
-
const value = strings && typeof raw === "string" ? /^\d+$/.test(raw.trim()) ? Number(raw.trim()) : NaN : raw;
|
|
95
|
-
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) return null;
|
|
96
|
-
return value;
|
|
97
|
-
}
|
|
98
|
-
function parkedMaxSetting(platform, { config, env = process.env } = {}) {
|
|
99
|
-
const { key, env: envKey } = MAX_SETTING[platform];
|
|
100
|
-
const fromEnv = env[envKey];
|
|
101
|
-
const explicit = fromEnv !== void 0 && fromEnv !== "";
|
|
102
|
-
const pool = (config === void 0 ? loadConfig() : config)?.pool;
|
|
103
|
-
if (!explicit && pool !== void 0 && (pool === null || typeof pool !== "object" || Array.isArray(pool))) return {
|
|
104
|
-
max: 0,
|
|
105
|
-
error: "Invalid pool value. Expected an object with simulator and emulator bounds."
|
|
106
|
-
};
|
|
107
|
-
const fromConfig = pool !== null && typeof pool === "object" && !Array.isArray(pool) ? pool[key] : void 0;
|
|
108
|
-
const raw = explicit ? fromEnv : fromConfig;
|
|
109
|
-
if (raw === void 0) return {
|
|
110
|
-
max: env.STIM_HOME ? 0 : 3,
|
|
111
|
-
error: null
|
|
112
|
-
};
|
|
113
|
-
const parsed = parseMax(raw, explicit);
|
|
114
|
-
if (parsed === null) return {
|
|
115
|
-
max: 0,
|
|
116
|
-
error: `Invalid ${explicit ? envKey : `pool.${key}`} value ${JSON.stringify(raw)}. Expected a whole number of parked ${platform === "ios" ? "simulators" : "emulators"}, 0 or more.`
|
|
117
|
-
};
|
|
118
|
-
return {
|
|
119
|
-
max: explicit ? parsed : env.STIM_HOME ? 0 : parsed,
|
|
120
|
-
error: null
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
function isParkedRecord(value, platform) {
|
|
124
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
125
|
-
const record = value;
|
|
126
|
-
return typeof record.udid === "string" && typeof record.name === "string" && record.name.startsWith("stim-") && typeof record.parkedAt === "string" && (platform === "android" ? record.udid === record.name && /^stim-[A-Za-z0-9._-]+$/.test(record.name) && typeof record.systemImage === "string" && typeof record.configuration === "string" : typeof record.deviceTypeIdentifier === "string" && typeof record.runtimeIdentifier === "string" && typeof record.simslimManaged === "boolean" && (record.bundleId === void 0 || typeof record.bundleId === "string") && (record.cacheKey === void 0 || typeof record.cacheKey === "string"));
|
|
127
|
-
}
|
|
128
|
-
function poolBlock(config) {
|
|
129
|
-
const raw = config?.parked;
|
|
130
|
-
const block = raw !== null && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
|
|
131
|
-
const read = (platform) => {
|
|
132
|
-
const list = block[platform];
|
|
133
|
-
return Array.isArray(list) ? list.filter((record) => isParkedRecord(record, platform)) : [];
|
|
134
|
-
};
|
|
135
|
-
return {
|
|
136
|
-
ios: read("ios"),
|
|
137
|
-
android: read("android")
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
function readParked(platform, { config } = {}) {
|
|
141
|
-
return poolBlock(config === void 0 ? loadConfig() : config)[platform];
|
|
142
|
-
}
|
|
143
|
-
function writeParked(config, platform, records) {
|
|
144
|
-
config.parked = {
|
|
145
|
-
...poolBlock(config),
|
|
146
|
-
[platform]: records
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
function oldestFirst(records) {
|
|
150
|
-
return records.toSorted((a, b) => String(a.parkedAt).localeCompare(String(b.parkedAt)));
|
|
151
|
-
}
|
|
152
|
-
function isLegacyDeletionClaim(value) {
|
|
153
|
-
if (value === void 0) return false;
|
|
154
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return true;
|
|
155
|
-
const marker = value;
|
|
156
|
-
return marker.kind !== "ownership-claim" || typeof marker.claimId !== "string" || marker.claimId.length === 0;
|
|
157
|
-
}
|
|
158
|
-
function selectParked(records, { deviceTypeIdentifier, runtimeIdentifier }) {
|
|
159
|
-
return oldestFirst(records.filter((r) => !isLegacyDeletionClaim(r.deletionClaim) && r.deviceTypeIdentifier === deviceTypeIdentifier && r.runtimeIdentifier === runtimeIdentifier));
|
|
160
|
-
}
|
|
161
|
-
function evictOverflow(records, max) {
|
|
162
|
-
if (records.length <= max) return {
|
|
163
|
-
keep: [...records],
|
|
164
|
-
evicted: []
|
|
165
|
-
};
|
|
166
|
-
const ordered = oldestFirst(records);
|
|
167
|
-
const evicted = ordered.slice(0, ordered.length - max);
|
|
168
|
-
const dropped = new Set(evicted.map((r) => r.udid));
|
|
169
|
-
return {
|
|
170
|
-
keep: records.filter((r) => !dropped.has(r.udid)),
|
|
171
|
-
evicted
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
function parkSim({ platform, projectPath, slot = "default", record, max }) {
|
|
175
|
-
return withConfigLock(() => {
|
|
176
|
-
const cfg = ensureConfig();
|
|
177
|
-
const current = deviceSlotPlatforms(cfg.projects[projectPath], slot)?.[platform];
|
|
178
|
-
const currentId = platform === "ios" ? current?.deviceUdid : current?.avdName;
|
|
179
|
-
if (!current?.owned || currentId !== record.udid) throw new Error(`The ${platform === "ios" ? "simulator" : "emulator"} assignment changed before parking.`);
|
|
180
|
-
const { keep, evicted } = evictOverflow([...readParked(platform, { config: cfg }).filter((r) => r.udid !== record.udid), record], max);
|
|
181
|
-
writeParked(cfg, platform, [...keep, ...evicted]);
|
|
182
|
-
const project = cfg.projects[projectPath];
|
|
183
|
-
if (project) removeSlotDevice(project, platform, slot);
|
|
184
|
-
saveConfig(cfg);
|
|
185
|
-
return evicted;
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
function adoptParked({ platform, projectPath, slot = "default", udid, device }) {
|
|
189
|
-
const claim = claimParkedOperation(platform, udid);
|
|
190
|
-
if (!claim) return null;
|
|
191
|
-
try {
|
|
192
|
-
return withConfigLock(() => {
|
|
193
|
-
const cfg = ensureConfig();
|
|
194
|
-
const records = readParked(platform, { config: cfg });
|
|
195
|
-
const taken = records.find((r) => r.udid === udid);
|
|
196
|
-
if (!taken || isLegacyDeletionClaim(taken.deletionClaim)) return null;
|
|
197
|
-
if (deviceSlotPlatforms(cfg.projects[projectPath], slot)?.[platform]) return null;
|
|
198
|
-
writeParked(cfg, platform, records.filter((r) => r.udid !== udid));
|
|
199
|
-
const project = cfg.projects[projectPath];
|
|
200
|
-
if (!project) throw new Error(`Project not registered: ${projectPath}`);
|
|
201
|
-
assignSlotDevice(project, platform, device, slot);
|
|
202
|
-
saveConfig(cfg);
|
|
203
|
-
const adopted = { ...taken };
|
|
204
|
-
delete adopted.deletionClaim;
|
|
205
|
-
return adopted;
|
|
206
|
-
});
|
|
207
|
-
} finally {
|
|
208
|
-
(0, ownership_claim_exports.releaseClaim)(claim);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
function claimParkedOperation(platform, udid) {
|
|
212
|
-
const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
|
|
213
|
-
root: join(getConfigDir(), "pool-locks", platform, `${encodeURIComponent(udid.toLowerCase())}.lock`),
|
|
214
|
-
mode: "exclusive",
|
|
215
|
-
label: `parked ${platform} device ${udid}`
|
|
216
|
-
});
|
|
217
|
-
if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
|
|
218
|
-
return attempt.acquired ?? null;
|
|
219
|
-
}
|
|
220
|
-
function removeParkedAfter(platform, udid, beforeRemove) {
|
|
221
|
-
const claim = claimParkedOperation(platform, udid);
|
|
222
|
-
if (!claim) return null;
|
|
223
|
-
try {
|
|
224
|
-
const claimed = withConfigLock(() => {
|
|
225
|
-
const cfg = loadConfig();
|
|
226
|
-
if (!cfg) return null;
|
|
227
|
-
const records = readParked(platform, { config: cfg });
|
|
228
|
-
const current = records.find((candidate) => candidate.udid === udid);
|
|
229
|
-
if (!current) return null;
|
|
230
|
-
if (isLegacyDeletionClaim(current.deletionClaim)) throw new Error(`Parked ${platform} device ${udid} has a legacy deletionClaim in ${getConfigPath()}. Its PID does not establish whether the original operation finished. Keep the device and pool record; after verifying that neither the old Stim process nor its native child is using it, remove only that record's deletionClaim field and retry.`);
|
|
231
|
-
const record = { ...current };
|
|
232
|
-
delete record.deletionClaim;
|
|
233
|
-
const marked = {
|
|
234
|
-
...record,
|
|
235
|
-
deletionClaim: {
|
|
236
|
-
kind: "ownership-claim",
|
|
237
|
-
claimId: claim.claimId
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
writeParked(cfg, platform, records.map((candidate) => candidate.udid === udid ? marked : candidate));
|
|
241
|
-
saveConfig(cfg);
|
|
242
|
-
return {
|
|
243
|
-
record,
|
|
244
|
-
marked
|
|
245
|
-
};
|
|
246
|
-
});
|
|
247
|
-
if (!claimed) return null;
|
|
248
|
-
let completed = false;
|
|
249
|
-
let removed = null;
|
|
250
|
-
try {
|
|
251
|
-
(0, ownership_claim_exports.markClaimChildPending)(claim);
|
|
252
|
-
try {
|
|
253
|
-
beforeRemove(claimed.record);
|
|
254
|
-
} finally {
|
|
255
|
-
(0, ownership_claim_exports.clearClaimChild)(claim);
|
|
256
|
-
}
|
|
257
|
-
completed = true;
|
|
258
|
-
} finally {
|
|
259
|
-
withConfigLock(() => {
|
|
260
|
-
const cfg = loadConfig();
|
|
261
|
-
if (!cfg) return;
|
|
262
|
-
const records = readParked(platform, { config: cfg });
|
|
263
|
-
const current = records.find((candidate) => candidate.udid === udid);
|
|
264
|
-
if (!isDeepStrictEqual(current, claimed.marked)) return;
|
|
265
|
-
writeParked(cfg, platform, completed ? records.filter((candidate) => candidate.udid !== udid) : records.map((candidate) => candidate.udid === udid ? claimed.record : candidate));
|
|
266
|
-
saveConfig(cfg);
|
|
267
|
-
if (completed) removed = claimed.record;
|
|
268
|
-
});
|
|
269
|
-
}
|
|
270
|
-
return removed;
|
|
271
|
-
} finally {
|
|
272
|
-
(0, ownership_claim_exports.releaseClaim)(claim);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
function dropParked(platform, udid) {
|
|
276
|
-
return removeParkedAfter(platform, udid, () => {}) !== null;
|
|
277
|
-
}
|
|
278
116
|
//#endregion
|
|
279
117
|
//#region src/devices/teardown.ts
|
|
280
118
|
function teardownParkedAvd(avdName) {
|
|
@@ -376,7 +214,7 @@ function parkOwnedIosSim(settled, udid, park) {
|
|
|
376
214
|
})
|
|
377
215
|
};
|
|
378
216
|
}
|
|
379
|
-
function teardownOwnedIosSim(udid, { del = false, label, park, shutdownClock = {} } = {}) {
|
|
217
|
+
function teardownOwnedIosSim(udid, { del = false, label, park, workspace, shutdownClock = {} } = {}) {
|
|
380
218
|
let parkFallback;
|
|
381
219
|
try {
|
|
382
220
|
const resolved = resolveOwnedIosSim(udid);
|
|
@@ -389,7 +227,7 @@ function teardownOwnedIosSim(udid, { del = false, label, park, shutdownClock = {
|
|
|
389
227
|
closeOwnedDeviceSessions({
|
|
390
228
|
platform: "ios",
|
|
391
229
|
id: udid
|
|
392
|
-
}, () => Boolean(resolveOwnedIosSim(udid).sim));
|
|
230
|
+
}, () => Boolean(resolveOwnedIosSim(udid).sim), workspace);
|
|
393
231
|
const current = resolveOwnedIosSim(udid);
|
|
394
232
|
if (current.missing) return { status: "missing" };
|
|
395
233
|
if (current.notOwned) return {
|
|
@@ -468,8 +306,16 @@ function removeOrphanedAvdDirectory(candidate, owner) {
|
|
|
468
306
|
const current = lstatSync(candidate.directory);
|
|
469
307
|
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.`);
|
|
470
308
|
assertAvdReferences(candidate.name, owner);
|
|
471
|
-
const
|
|
472
|
-
|
|
309
|
+
const name = `stim-gc-${randomUUID()}`;
|
|
310
|
+
recordCreatedDevice("android", name);
|
|
311
|
+
const destination = join(dirname(candidate.directory), `${name}.avd`);
|
|
312
|
+
try {
|
|
313
|
+
renameSync(candidate.directory, destination);
|
|
314
|
+
} catch (error) {
|
|
315
|
+
forgetCreatedDevice("android", name);
|
|
316
|
+
throw error;
|
|
317
|
+
}
|
|
318
|
+
forgetCreatedDevice("android", candidate.name);
|
|
473
319
|
return destination;
|
|
474
320
|
});
|
|
475
321
|
const current = lstatSync(detached);
|
|
@@ -479,6 +325,7 @@ function removeOrphanedAvdDirectory(candidate, owner) {
|
|
|
479
325
|
} catch (error) {
|
|
480
326
|
throw new Error(`Could not remove AVD data at ${detached}; retry gc --delete: ${error.message}`, { cause: error });
|
|
481
327
|
}
|
|
328
|
+
forgetCreatedDevice("android", basename(detached, ".avd"));
|
|
482
329
|
}
|
|
483
330
|
function teardownUnregisteredAvd(avdName, del, owner, surveyed) {
|
|
484
331
|
const candidates = surveyed ? [surveyed] : listOrphanedAvdDirectories(avdName);
|
|
@@ -502,7 +349,7 @@ function teardownUnregisteredAvd(avdName, del, owner, surveyed) {
|
|
|
502
349
|
function teardownOwnedAvd(avdName, options = {}) {
|
|
503
350
|
let claim;
|
|
504
351
|
try {
|
|
505
|
-
if (!
|
|
352
|
+
if (!isStimOwnedAvd(avdName)) return {
|
|
506
353
|
status: "skipped",
|
|
507
354
|
kind: "not-owned",
|
|
508
355
|
reason: `AVD ${avdName} is not Stim-owned: Stim has no record of creating it`
|
|
@@ -520,7 +367,7 @@ function teardownOwnedAvd(avdName, options = {}) {
|
|
|
520
367
|
(0, ownership_claim_exports.releaseClaim)(claim);
|
|
521
368
|
}
|
|
522
369
|
}
|
|
523
|
-
function teardownClaimedAvd(avdName, claim, { del = false, park, owner, orphanedDirectory, onlyIfMissing = false, waitForShutdown = waitForAndroidEmulatorShutdown, assertStopped = assertOwnedAvdStopped, resolveAvd = resolveOwnedAvdSerial }) {
|
|
370
|
+
function teardownClaimedAvd(avdName, claim, { del = false, park, owner, workspace, orphanedDirectory, onlyIfMissing = false, waitForShutdown = waitForAndroidEmulatorShutdown, assertStopped = assertOwnedAvdStopped, resolveAvd = resolveOwnedAvdSerial }) {
|
|
524
371
|
let parkFallback;
|
|
525
372
|
try {
|
|
526
373
|
if (del) withConfigLock(() => assertAvdReferences(avdName, owner));
|
|
@@ -547,8 +394,9 @@ function teardownClaimedAvd(avdName, claim, { del = false, park, owner, orphaned
|
|
|
547
394
|
};
|
|
548
395
|
closeOwnedDeviceSessions({
|
|
549
396
|
platform: "android",
|
|
550
|
-
id: serial
|
|
551
|
-
|
|
397
|
+
id: serial,
|
|
398
|
+
avdName
|
|
399
|
+
}, stillOwned, workspace);
|
|
552
400
|
if (!stillOwned()) throw new Error(`Owned AVD ${avdName} changed before shutdown; it was kept.`);
|
|
553
401
|
waitForShutdown(avdName, (timeoutMs) => {
|
|
554
402
|
(0, ownership_claim_exports.markClaimChildPending)(claim);
|
|
@@ -558,7 +406,7 @@ function teardownClaimedAvd(avdName, claim, { del = false, park, owner, orphaned
|
|
|
558
406
|
(0, ownership_claim_exports.clearClaimChild)(claim);
|
|
559
407
|
}
|
|
560
408
|
});
|
|
561
|
-
} else
|
|
409
|
+
} else waitForShutdown(avdName, null);
|
|
562
410
|
if (del) {
|
|
563
411
|
const current = resolveAvd(avdName);
|
|
564
412
|
if (current.notOwned) return {
|
|
@@ -639,6 +487,116 @@ function teardownClaimedAvd(avdName, claim, { del = false, park, owner, orphaned
|
|
|
639
487
|
};
|
|
640
488
|
}
|
|
641
489
|
}
|
|
490
|
+
const BROWSER_EXIT_WAIT_MS = 15e3;
|
|
491
|
+
const CHROME_KILL_WAIT_MS = 5e3;
|
|
492
|
+
async function stopOwnedChrome(record) {
|
|
493
|
+
for (const signal of ["SIGTERM", "SIGKILL"]) {
|
|
494
|
+
const state = chromeProcessState(record);
|
|
495
|
+
if (state === "gone") return true;
|
|
496
|
+
if (state === "unknown") return false;
|
|
497
|
+
try {
|
|
498
|
+
signalProcessTree(record.pid, signal, { group: true });
|
|
499
|
+
} catch {}
|
|
500
|
+
const deadline = Date.now() + CHROME_KILL_WAIT_MS;
|
|
501
|
+
while (chromeProcessState(record) !== "gone" && Date.now() < deadline) await sleepAsync(50);
|
|
502
|
+
}
|
|
503
|
+
return chromeProcessState(record) === "gone";
|
|
504
|
+
}
|
|
505
|
+
const sleepAsync = (ms) => new Promise((done) => setTimeout(done, ms));
|
|
506
|
+
/**
|
|
507
|
+
* Closes the workspace's Stim-owned Chrome and its supervisor, after verifying both process identities. With
|
|
508
|
+
* `deleteProfile`, it also deletes the profile directory, only when the created-devices ledger lists it.
|
|
509
|
+
*/
|
|
510
|
+
async function teardownOwnedBrowser(root, options = {}) {
|
|
511
|
+
try {
|
|
512
|
+
return await withWorkspaceProcessLock(workspaceDir(root), "web", () => teardownBrowserHeld(root, options), {
|
|
513
|
+
external: true,
|
|
514
|
+
ownerPurpose: "browser teardown"
|
|
515
|
+
});
|
|
516
|
+
} catch (error) {
|
|
517
|
+
return {
|
|
518
|
+
status: "failed",
|
|
519
|
+
reason: String(error?.message || error)
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
/** {@link teardownOwnedBrowser} for a caller that already holds the {@link BROWSER_LOCK} lock. */
|
|
524
|
+
async function teardownBrowserHeld(root, { deleteProfile = false } = {}) {
|
|
525
|
+
try {
|
|
526
|
+
const record = readWebRecord(root);
|
|
527
|
+
const profile = record?.profile ?? webProfileDir(root);
|
|
528
|
+
if (record) {
|
|
529
|
+
const supervisor = (0, process_identity_exports.inspectProcessIdentity)(record);
|
|
530
|
+
const chrome = record.chromeProcess ? (0, process_identity_exports.inspectProcessIdentity)(record.chromeProcess) : "gone";
|
|
531
|
+
if (supervisor === "unknown" || chrome === "unknown") return {
|
|
532
|
+
status: "skipped",
|
|
533
|
+
kind: "not-verified",
|
|
534
|
+
reason: `the browser supervisor (pid ${record.pid}) or its Chrome could not be verified; its record was kept`
|
|
535
|
+
};
|
|
536
|
+
if (supervisor === "same") {
|
|
537
|
+
try {
|
|
538
|
+
process.kill(record.pid, "SIGTERM");
|
|
539
|
+
} catch {}
|
|
540
|
+
if (!await (0, process_identity_exports.waitForProcessExit)(record, BROWSER_EXIT_WAIT_MS)) return {
|
|
541
|
+
status: "failed",
|
|
542
|
+
reason: `browser supervisor pid ${record.pid} did not exit`
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
if (record.chromeProcess && !await stopOwnedChrome(record.chromeProcess)) return {
|
|
546
|
+
status: "failed",
|
|
547
|
+
reason: `Chrome pid ${record.chromeProcess.pid} did not exit`
|
|
548
|
+
};
|
|
549
|
+
const holder = liveProfileHolder(profile);
|
|
550
|
+
if (holder !== null) return {
|
|
551
|
+
status: "skipped",
|
|
552
|
+
kind: "not-verified",
|
|
553
|
+
reason: `pid ${holder} still holds ${profile}, and the browser record does not name it; it was left running`
|
|
554
|
+
};
|
|
555
|
+
const claims = (0, ownership_claim_exports.clearFreeClaimSet)({
|
|
556
|
+
root: webClaimRoot(root),
|
|
557
|
+
label: "browser supervisor"
|
|
558
|
+
});
|
|
559
|
+
if (claims.status !== "cleared") return {
|
|
560
|
+
status: "skipped",
|
|
561
|
+
kind: "not-verified",
|
|
562
|
+
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))}`
|
|
563
|
+
};
|
|
564
|
+
clearWebRecord(root, record);
|
|
565
|
+
await releaseBrowserPort(root, record.cdpPort);
|
|
566
|
+
} else {
|
|
567
|
+
const holder = liveProfileHolder(profile);
|
|
568
|
+
if (holder !== null) return {
|
|
569
|
+
status: "skipped",
|
|
570
|
+
kind: "not-verified",
|
|
571
|
+
reason: `pid ${holder} holds ${profile} but no browser supervisor is recorded; it was left running`
|
|
572
|
+
};
|
|
573
|
+
const port = getProject(root)?.ports?.[CDP_PORT_LABEL];
|
|
574
|
+
if (typeof port === "number") await releaseBrowserPort(root, port);
|
|
575
|
+
}
|
|
576
|
+
if (existsSync(profile)) removeSingletonFiles(profile);
|
|
577
|
+
if (deleteProfile && existsSync(profile)) {
|
|
578
|
+
if (!readCreatedDevices().web.has(profile)) return {
|
|
579
|
+
status: "skipped",
|
|
580
|
+
kind: "not-owned",
|
|
581
|
+
reason: `${profile} is not in Stim's ledger; it was kept`
|
|
582
|
+
};
|
|
583
|
+
rmSync(profile, {
|
|
584
|
+
recursive: true,
|
|
585
|
+
force: true
|
|
586
|
+
});
|
|
587
|
+
forgetCreatedDevice("web", profile);
|
|
588
|
+
}
|
|
589
|
+
return record ? {
|
|
590
|
+
status: "torn-down",
|
|
591
|
+
label: `Chrome (${profile})`
|
|
592
|
+
} : { status: "missing" };
|
|
593
|
+
} catch (error) {
|
|
594
|
+
return {
|
|
595
|
+
status: "failed",
|
|
596
|
+
reason: String(error?.message || error)
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
}
|
|
642
600
|
//#endregion
|
|
643
601
|
//#region src/engine/public-http-probe.ts
|
|
644
602
|
const DNS_TIMEOUT_MS = 5e3;
|
|
@@ -1177,4 +1135,4 @@ function numberOrNull(v) {
|
|
|
1177
1135
|
return typeof v === "number" && Number.isSafeInteger(v) && v > 0 ? v : null;
|
|
1178
1136
|
}
|
|
1179
1137
|
//#endregion
|
|
1180
|
-
export {
|
|
1138
|
+
export { terminateChild as a, withManagedTunnelLock as c, teardownBrowserHeld as d, teardownOwnedAvd as f, teardownParkedIosSim as g, teardownParkedAvd as h, stopTunnel as i, withManagedTunnelRemovalLock as l, teardownOwnedIosSim as m, managedLockHolders as n, withManagedRemoteWorktreeLock as o, teardownOwnedBrowser as p, startTunnelSequence as r, withManagedRemoteWorktreeRemovalLock as s, resolveSupervisorTarget as t, probePublicHttp as u };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { O as process_identity_exports } from "./config-0jCcMM0E.mjs";
|
|
2
|
+
import { t as connectOwnedBrowser } from "./cdp-Dq_ypAlJ.mjs";
|
|
3
|
+
//#region src/web/page.ts
|
|
4
|
+
/** The record of a verified live browser: its supervisor and Chrome are the processes the record names. */
|
|
5
|
+
function liveWebRecord(record) {
|
|
6
|
+
if (!record?.targetId || !record.chromeProcess) return null;
|
|
7
|
+
if ((0, process_identity_exports.inspectProcessIdentity)(record) !== "same" || (0, process_identity_exports.inspectProcessIdentity)(record.chromeProcess) !== "same") return null;
|
|
8
|
+
return record;
|
|
9
|
+
}
|
|
10
|
+
const NAVIGATE_ACK_MS = 1e3;
|
|
11
|
+
/** Sends one page-level DevTools command to the owned page, through a connection verified to reach its Chrome. */
|
|
12
|
+
async function sendToOwnedPage(record, method, params = {}) {
|
|
13
|
+
const cdp = await connectOwnedBrowser(record.cdpPort, record.chromeProcess.pid);
|
|
14
|
+
try {
|
|
15
|
+
const { sessionId } = await cdp.send("Target.attachToTarget", {
|
|
16
|
+
targetId: record.targetId,
|
|
17
|
+
flatten: true
|
|
18
|
+
});
|
|
19
|
+
const reply = cdp.send(method, params, sessionId);
|
|
20
|
+
if (method === "Page.navigate") {
|
|
21
|
+
reply.catch(() => {});
|
|
22
|
+
await Promise.race([reply, new Promise((resolve) => setTimeout(resolve, NAVIGATE_ACK_MS))]);
|
|
23
|
+
} else await reply;
|
|
24
|
+
} finally {
|
|
25
|
+
cdp.close();
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
//#endregion
|
|
29
|
+
export { sendToOwnedPage as n, liveWebRecord as t };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { n as getNamedPort, t as clearNamedPorts } from "./named-ports-
|
|
1
|
+
import { p as getProject } from "./config-0jCcMM0E.mjs";
|
|
2
|
+
import { i as findCommandWorkspace } from "./project-DGeclhvO.mjs";
|
|
3
|
+
import { n as getNamedPort, t as clearNamedPorts } from "./named-ports-n7tnJsI8.mjs";
|
|
4
4
|
//#region src/commands/ports.ts
|
|
5
5
|
async function inProject(action) {
|
|
6
6
|
try {
|
|
7
|
-
const root =
|
|
7
|
+
const root = findCommandWorkspace(process.cwd());
|
|
8
8
|
if (!root) throw new Error("No package.json found. Run stim ports from the workspace that owns the server.");
|
|
9
9
|
await action(root);
|
|
10
10
|
} catch (error) {
|
|
@@ -16,7 +16,7 @@ function portsCommand(program) {
|
|
|
16
16
|
const ports = program.command("ports").description("Reserve workspace ports for servers Stim does not manage");
|
|
17
17
|
ports.action(() => inProject(async (root) => {
|
|
18
18
|
const project = getProject(root);
|
|
19
|
-
const rows = Object.entries(project?.ports ?? {});
|
|
19
|
+
const rows = Object.entries(project?.ports ?? {}).map(([name, port]) => [name === "web-cdp" ? "web-cdp (managed)" : name, port]);
|
|
20
20
|
if (typeof project?.metroPort === "number") rows.push(["metro (managed)", project.metroPort]);
|
|
21
21
|
if (!rows.length) {
|
|
22
22
|
console.log("No ports allocated. Use stim ports get <label>.");
|