stim 0.0.0 → 1.1.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/LICENSE +21 -0
- package/README.md +162 -0
- package/dist/android-CC5bUer7.mjs +204 -0
- package/dist/android-DnZlT3aU.mjs +2279 -0
- package/dist/android-cas-DGlTStWu.mjs +1078 -0
- package/dist/android-cas-compiler.d.mts +1 -0
- package/dist/android-cas-compiler.mjs +71 -0
- package/dist/android-xmyGBNy5.mjs +665 -0
- package/dist/app-install-oZIQRbdD.mjs +1141 -0
- package/dist/build-lock-vE45ZzJZ.mjs +645 -0
- package/dist/build-slots-CgacA9zQ.mjs +152 -0
- package/dist/cache-manifest-oeH18ERr.mjs +66 -0
- package/dist/cache-manifest.d.mts +27 -0
- package/dist/cache-manifest.mjs +2 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +40 -0
- package/dist/collector-run.d.mts +119 -0
- package/dist/collector-run.mjs +541 -0
- package/dist/command-output-8wHMEde0.mjs +289 -0
- package/dist/config-D7uu8Gbq.mjs +381 -0
- package/dist/deps-64wVnpZc.mjs +435 -0
- package/dist/dev-client-Bvky1hya.mjs +557 -0
- package/dist/device-DJKv5Qej.mjs +250 -0
- package/dist/device-lease-D-G3SgVe.mjs +335 -0
- package/dist/device-pool-BlHXnYYs.mjs +408 -0
- package/dist/device-remote-CCgkG82J.mjs +1209 -0
- package/dist/doctor-BRF9aAJ3.mjs +530 -0
- package/dist/doctor-DlVxTtMq.mjs +1137 -0
- package/dist/error-diagnostics-B5IJwv3-.mjs +636 -0
- package/dist/errors-B6W7FkxQ.mjs +15 -0
- package/dist/exec-bsN9MJXb.mjs +63 -0
- package/dist/gc-DhXdgSdt.mjs +1651 -0
- package/dist/guide-DrhrDBo5.mjs +4319 -0
- package/dist/ios-BZxKPuqP.mjs +382 -0
- package/dist/ios-CK1Vrk6Q.mjs +2721 -0
- package/dist/ios-device-CYbuRNXR.mjs +81 -0
- package/dist/ios-device-CdSm0GDw.mjs +413 -0
- package/dist/logs-Dxoy16Qv.mjs +176 -0
- package/dist/logs-query-oqn47Ffx.mjs +273 -0
- package/dist/metro-B01H3EuD.mjs +184 -0
- package/dist/metro-store-CDDZHtPl.mjs +92 -0
- package/dist/ndjson-DcAtEx_K.mjs +131 -0
- package/dist/ndjson-DvHSsNU9.d.mts +16 -0
- package/dist/ownership-B9QIdQOw.mjs +475 -0
- package/dist/ownership-D8HmSv2E.mjs +60 -0
- package/dist/ownership-claim-CeJLEpjD.mjs +569 -0
- package/dist/process-output-C7IlIzd7.mjs +40 -0
- package/dist/project-COj6nzzh.mjs +243 -0
- package/dist/reclaim-BPvN2HlP.mjs +347 -0
- package/dist/reload-DYZEBbZy.mjs +332 -0
- package/dist/remote-cache-DOi5-zLj.mjs +934 -0
- package/dist/server-bare-BYHqEeyw.mjs +352 -0
- package/dist/server-expo-BEMr_m6p.mjs +312 -0
- package/dist/server-expo-rb0k-0w7.mjs +2 -0
- package/dist/settings-BSwrY4FZ.mjs +687 -0
- package/dist/spawn-entry-nCE6m770.mjs +14 -0
- package/dist/start-ByCylYtB.mjs +822 -0
- package/dist/state-IYEY-zi8.mjs +268 -0
- package/dist/stats-DZ4Q5J-7.mjs +62 -0
- package/dist/stats-wqxPm7Tv.mjs +307 -0
- package/dist/status-Dia0XTqG.mjs +363 -0
- package/dist/stop-BApyaF21.mjs +591 -0
- package/dist/stop-D6o5zAdm.mjs +3 -0
- package/dist/supervisor-run.d.mts +84 -0
- package/dist/supervisor-run.mjs +210 -0
- package/dist/workspace-process-lock-CxOO9-pf.mjs +47 -0
- package/dist/worktree-Dx3ZC5lS.mjs +539 -0
- package/dist/worktree-DxEmUI_n.mjs +1322 -0
- package/dist/xcode-CDB6fytw.mjs +1705 -0
- package/package.json +61 -8
- package/shim/android-cas-pch.cmake +35 -0
- package/shim/android-cas.gradle +29 -0
- package/shim/android-cas.toolchain.cmake +12 -0
- package/shim/android-optimizations.gradle +30 -0
- package/shim/bundle-response.cjs +46 -0
- package/shim/bundle-response.d.cts +5 -0
- package/shim/expo-metro-config.cjs +188 -0
- package/shim/metro-cache-generation.cjs +20 -0
- package/shim/metro-cache-generation.d.cts +5 -0
- package/skill/SKILL.md +14 -0
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
import { C as withConfigLock, D as sameProcessRecord, E as inspectProcessIdentity, O as waitForProcessExit, S as upsertProject, a as clearSupervisor, f as getProject } from "./config-D7uu8Gbq.mjs";
|
|
2
|
+
import { _ as writeWorkspaceState, a as clearRemoteSession, c as readMetroTunnel, i as clearManagedMetroTunnel, m as withWorkspaceStateLock, o as clearWorkspaceStateKeys, p as readWorkspaceState, s as clearWorkspaceSupervisor, u as readRemoteSession } from "./state-IYEY-zi8.mjs";
|
|
3
|
+
import { a as findProjectRoot } from "./project-COj6nzzh.mjs";
|
|
4
|
+
import { i as resolveProjectMetro, n as killMetroTree, r as pidExists } from "./metro-B01H3EuD.mjs";
|
|
5
|
+
import { n as verifyCollectorOwnership } from "./ownership-D8HmSv2E.mjs";
|
|
6
|
+
import { d as resolveEasCliBin, h as teardownOwnedIosSim, m as teardownOwnedAvd } from "./remote-cache-DOi5-zLj.mjs";
|
|
7
|
+
import { f as releasedLeaseFact, l as phaseLine, u as plural } from "./command-output-8wHMEde0.mjs";
|
|
8
|
+
import { l as releaseWorkspaceLeases } from "./device-lease-D-G3SgVe.mjs";
|
|
9
|
+
import { i as endRecordedSession } from "./device-remote-CCgkG82J.mjs";
|
|
10
|
+
import { r as stopTunnel, t as resolveSupervisorTarget } from "./ownership-B9QIdQOw.mjs";
|
|
11
|
+
import chalk from "chalk";
|
|
12
|
+
//#region src/commands/stop.ts
|
|
13
|
+
const DEFAULT_WAIT_MS = 1e4;
|
|
14
|
+
function readSupervisorState(root) {
|
|
15
|
+
const sup = readWorkspaceState(root)?.supervisor;
|
|
16
|
+
return sup && typeof sup === "object" ? sup : null;
|
|
17
|
+
}
|
|
18
|
+
function readCollectorState(root) {
|
|
19
|
+
const collectors = readWorkspaceState(root)?.collectors;
|
|
20
|
+
return collectors && typeof collectors === "object" ? collectors : {};
|
|
21
|
+
}
|
|
22
|
+
function clearSupervisorState(root, expected) {
|
|
23
|
+
return withWorkspaceStateLock(root, () => {
|
|
24
|
+
if (!clearWorkspaceSupervisor(root, expected)) return false;
|
|
25
|
+
clearWorkspaceStateKeys(root, ["launches"]);
|
|
26
|
+
return true;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function clearCollectorState(root, expected) {
|
|
30
|
+
return withWorkspaceStateLock(root, () => {
|
|
31
|
+
const current = { ...readWorkspaceState(root)?.collectors };
|
|
32
|
+
if (expected === void 0) {
|
|
33
|
+
clearWorkspaceStateKeys(root, ["collectors"]);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
for (const [platform, record] of Object.entries(expected ?? {})) if (sameProcessRecord(current[platform], record)) delete current[platform];
|
|
37
|
+
if (Object.keys(current).length) writeWorkspaceState(root, { collectors: current });
|
|
38
|
+
else clearWorkspaceStateKeys(root, ["collectors"]);
|
|
39
|
+
return Object.keys(current).length === 0;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
function numberOrNull(v) {
|
|
43
|
+
return typeof v === "number" && Number.isSafeInteger(v) && v > 0 ? v : null;
|
|
44
|
+
}
|
|
45
|
+
function resolveCollectorTargets({ root, collectors, isAlive = pidExists, selfPid = process.pid, verify = verifyCollectorOwnership }) {
|
|
46
|
+
const targets = [];
|
|
47
|
+
for (const [platform, record] of Object.entries(collectors || {})) {
|
|
48
|
+
const pid = numberOrNull(Number(record?.pid));
|
|
49
|
+
if (!pid || pid === selfPid) {
|
|
50
|
+
targets.push({
|
|
51
|
+
platform,
|
|
52
|
+
pid: pid ?? null,
|
|
53
|
+
status: "invalid"
|
|
54
|
+
});
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (!isAlive(pid)) {
|
|
58
|
+
targets.push({
|
|
59
|
+
platform,
|
|
60
|
+
pid,
|
|
61
|
+
status: "stale"
|
|
62
|
+
});
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const ownership = verify({
|
|
66
|
+
pid,
|
|
67
|
+
platform,
|
|
68
|
+
root,
|
|
69
|
+
isAlive,
|
|
70
|
+
expected: record
|
|
71
|
+
});
|
|
72
|
+
if (ownership.status === "gone") {
|
|
73
|
+
targets.push({
|
|
74
|
+
platform,
|
|
75
|
+
pid,
|
|
76
|
+
status: "stale"
|
|
77
|
+
});
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (ownership.status === "unverified") {
|
|
81
|
+
targets.push({
|
|
82
|
+
platform,
|
|
83
|
+
pid,
|
|
84
|
+
status: "unverified",
|
|
85
|
+
reason: ownership.reason
|
|
86
|
+
});
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
targets.push({
|
|
90
|
+
platform,
|
|
91
|
+
pid,
|
|
92
|
+
status: "running"
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return targets;
|
|
96
|
+
}
|
|
97
|
+
function defaultTeardownRemoteSession(root, sessionId) {
|
|
98
|
+
return endRecordedSession({
|
|
99
|
+
root,
|
|
100
|
+
sessionId,
|
|
101
|
+
easBin: resolveEasCliBin(root)?.file ?? null
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
async function runStop({ root, project = void 0, state = void 0, collectors = void 0, signalCollector = (pid) => process.kill(pid, "SIGTERM"), verifyCollector = verifyCollectorOwnership, clearCollectors = clearCollectorState, isAlive = pidExists, killGroup = killMetroTree, inspectIdentity = inspectProcessIdentity, waitForDeath = void 0, waitMs = DEFAULT_WAIT_MS, resolveMetro = resolveProjectMetro, teardownIos = teardownOwnedIosSim, teardownAvd = teardownOwnedAvd, remoteDevice = void 0, teardownRemoteSession = defaultTeardownRemoteSession, metroTunnel = void 0, stopMetroTunnel = stopTunnel, releaseLeases = releaseWorkspaceLeases, freePort = defaultFreePort, clearRegistration = defaultClearRegistration, clearState = clearSupervisorState, report = (line) => console.error(line) }) {
|
|
105
|
+
const proj = project === void 0 ? getProject(root) : project;
|
|
106
|
+
const sup = state === void 0 ? readSupervisorState(root) : state;
|
|
107
|
+
const collectorRecords = collectors === void 0 ? readCollectorState(root) : collectors;
|
|
108
|
+
const reservedPort = typeof proj?.metroPort === "number" ? proj.metroPort : null;
|
|
109
|
+
const waiter = waitForDeath ?? ((pid, processToken) => waitForProcessExit({
|
|
110
|
+
pid,
|
|
111
|
+
processToken
|
|
112
|
+
}, waitMs));
|
|
113
|
+
const outcomes = {
|
|
114
|
+
supervisor: { status: "none" },
|
|
115
|
+
collectors: {
|
|
116
|
+
status: "none",
|
|
117
|
+
entries: []
|
|
118
|
+
},
|
|
119
|
+
metro: { status: "none" },
|
|
120
|
+
device: {
|
|
121
|
+
ios: null,
|
|
122
|
+
android: null
|
|
123
|
+
},
|
|
124
|
+
port: {
|
|
125
|
+
status: "none",
|
|
126
|
+
port: reservedPort
|
|
127
|
+
},
|
|
128
|
+
metroTunnel: { status: "none" },
|
|
129
|
+
releasedLeases: []
|
|
130
|
+
};
|
|
131
|
+
let ok = true;
|
|
132
|
+
let stillHolding = null;
|
|
133
|
+
const target = resolveSupervisorTarget({
|
|
134
|
+
state: sup,
|
|
135
|
+
record: proj?.supervisor ?? null,
|
|
136
|
+
reservedPort,
|
|
137
|
+
isAlive,
|
|
138
|
+
inspectIdentity
|
|
139
|
+
});
|
|
140
|
+
if (target.status === "none") report(chalk.dim(phaseLine("stop", "no supervisor recorded")));
|
|
141
|
+
else if (target.status === "stale") {
|
|
142
|
+
outcomes.supervisor = {
|
|
143
|
+
status: "already-stopped",
|
|
144
|
+
pid: target.pid,
|
|
145
|
+
reason: `recorded pid ${target.pid} is not running`
|
|
146
|
+
};
|
|
147
|
+
report(chalk.dim(phaseLine("stop", `supervisor pid ${target.pid} is already gone (stale record)`)));
|
|
148
|
+
} else if (target.status === "unverified") {
|
|
149
|
+
outcomes.supervisor = {
|
|
150
|
+
status: "unverified",
|
|
151
|
+
pid: target.pid,
|
|
152
|
+
reason: target.reason
|
|
153
|
+
};
|
|
154
|
+
report(chalk.yellow(phaseLine("stop", `refusing to signal supervisor pid ${target.pid}: ${target.reason}`)));
|
|
155
|
+
ok = false;
|
|
156
|
+
stillHolding = `supervisor pid ${target.pid} could not be verified`;
|
|
157
|
+
} else {
|
|
158
|
+
outcomes.supervisor = await stopSupervisor(target, {
|
|
159
|
+
killGroup,
|
|
160
|
+
waiter,
|
|
161
|
+
report
|
|
162
|
+
});
|
|
163
|
+
if (outcomes.supervisor.status !== "stopped") {
|
|
164
|
+
ok = false;
|
|
165
|
+
stillHolding = outcomes.supervisor.reason;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
outcomes.collectors = await reapCollectors(root, collectorRecords, {
|
|
169
|
+
isAlive,
|
|
170
|
+
signal: signalCollector,
|
|
171
|
+
report,
|
|
172
|
+
verify: verifyCollector,
|
|
173
|
+
waiter
|
|
174
|
+
});
|
|
175
|
+
const unverifiedCollectors = outcomes.collectors.entries.filter((e) => e.status === "unverified");
|
|
176
|
+
if (unverifiedCollectors.length) {
|
|
177
|
+
report(chalk.dim(phaseLine("stop", `keeping the collector records; ${plural(unverifiedCollectors.length, "pid")} could not be verified, and a later \`ios\` / \`android\` run replaces them`)));
|
|
178
|
+
ok = false;
|
|
179
|
+
stillHolding ??= "collector process identity could not be verified";
|
|
180
|
+
} else if (outcomes.collectors.entries.some((entry) => entry.status === "failed")) {
|
|
181
|
+
ok = false;
|
|
182
|
+
stillHolding ??= "a collector could not be stopped";
|
|
183
|
+
} else if (outcomes.collectors.entries.length && clearCollectors(root, collectorRecords) === false) {
|
|
184
|
+
ok = false;
|
|
185
|
+
stillHolding ??= "a replacement collector appeared during cleanup";
|
|
186
|
+
}
|
|
187
|
+
if (outcomes.supervisor.status === "stopped" || outcomes.supervisor.status === "timeout" || outcomes.supervisor.status === "unverified" || outcomes.supervisor.status === "failed") outcomes.metro = {
|
|
188
|
+
status: "skipped",
|
|
189
|
+
reason: "the supervisor owns the dev server on this port"
|
|
190
|
+
};
|
|
191
|
+
else if (reservedPort === null) report(chalk.dim(phaseLine("metro", "no port reserved")));
|
|
192
|
+
else {
|
|
193
|
+
outcomes.metro = await stopMetro(reservedPort, root, {
|
|
194
|
+
resolveMetro,
|
|
195
|
+
report
|
|
196
|
+
});
|
|
197
|
+
if (outcomes.metro.status === "refused" || outcomes.metro.status === "failed") {
|
|
198
|
+
ok = false;
|
|
199
|
+
stillHolding = outcomes.metro.reason;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
const releaseStoppedSupervisor = async () => {
|
|
203
|
+
const stateCleared = clearState(root, sup);
|
|
204
|
+
const registrationCleared = await clearRegistration(root, proj?.supervisor ?? null);
|
|
205
|
+
if (stateCleared === false || registrationCleared === false) {
|
|
206
|
+
ok = false;
|
|
207
|
+
outcomes.port = {
|
|
208
|
+
status: "kept",
|
|
209
|
+
port: reservedPort,
|
|
210
|
+
reason: "a replacement supervisor appeared during cleanup"
|
|
211
|
+
};
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (reservedPort !== null) {
|
|
215
|
+
if (freePort(root, reservedPort) === false) {
|
|
216
|
+
ok = false;
|
|
217
|
+
outcomes.port = {
|
|
218
|
+
status: "kept",
|
|
219
|
+
port: reservedPort,
|
|
220
|
+
reason: "the port reservation changed during cleanup"
|
|
221
|
+
};
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
outcomes.port = {
|
|
225
|
+
status: "freed",
|
|
226
|
+
port: reservedPort
|
|
227
|
+
};
|
|
228
|
+
report(chalk.dim(phaseLine("port", `released ${reservedPort}`)));
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
if (stillHolding) report(chalk.dim(phaseLine("device", "left alone (something is still running)")));
|
|
232
|
+
else {
|
|
233
|
+
outcomes.device = shutDownDevices(proj, {
|
|
234
|
+
teardownIos,
|
|
235
|
+
teardownAvd,
|
|
236
|
+
report
|
|
237
|
+
});
|
|
238
|
+
if ([outcomes.device.ios, outcomes.device.android].some((device) => device?.status === "failed")) ok = false;
|
|
239
|
+
}
|
|
240
|
+
const remote = remoteDevice === void 0 ? readRemoteSession(root) : remoteDevice;
|
|
241
|
+
const sessionId = typeof remote?.sessionId === "string" ? remote.sessionId : null;
|
|
242
|
+
if (sessionId) {
|
|
243
|
+
const result = teardownRemoteSession(root, sessionId);
|
|
244
|
+
outcomes.device.remote = {
|
|
245
|
+
status: result.status,
|
|
246
|
+
label: sessionId,
|
|
247
|
+
reason: result.reason
|
|
248
|
+
};
|
|
249
|
+
if (result.status === "failed") {
|
|
250
|
+
ok = false;
|
|
251
|
+
report(chalk.red(phaseLine("device", result.reason ?? `could not stop remote session ${sessionId}`)));
|
|
252
|
+
} else {
|
|
253
|
+
report(chalk.dim(phaseLine("device", `stopped remote session ${sessionId}`)));
|
|
254
|
+
if (result.reason) {
|
|
255
|
+
ok = false;
|
|
256
|
+
report(chalk.yellow(phaseLine("device", result.reason)));
|
|
257
|
+
} else clearRemoteSession(root, sessionId);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const tunnel = metroTunnel === void 0 ? readMetroTunnel(root) : metroTunnel;
|
|
261
|
+
let tunnelHolding = null;
|
|
262
|
+
if (tunnel?.kind === "managed") {
|
|
263
|
+
const result = await stopMetroTunnel(tunnel);
|
|
264
|
+
outcomes.metroTunnel = {
|
|
265
|
+
status: result.status,
|
|
266
|
+
provider: tunnel.provider,
|
|
267
|
+
reason: result.reason
|
|
268
|
+
};
|
|
269
|
+
if (result.status === "failed") {
|
|
270
|
+
ok = false;
|
|
271
|
+
tunnelHolding = result.reason ?? `could not stop the ${tunnel.provider} tunnel`;
|
|
272
|
+
if (!stillHolding) stillHolding = tunnelHolding;
|
|
273
|
+
report(chalk.red(phaseLine("lan", tunnelHolding)));
|
|
274
|
+
} else if (!clearManagedMetroTunnel(root, tunnel)) {
|
|
275
|
+
tunnelHolding = "a replacement managed tunnel record appeared during cleanup and remains active";
|
|
276
|
+
stillHolding = stillHolding ?? tunnelHolding;
|
|
277
|
+
ok = false;
|
|
278
|
+
outcomes.metroTunnel = {
|
|
279
|
+
status: "failed",
|
|
280
|
+
provider: tunnel.provider,
|
|
281
|
+
reason: tunnelHolding
|
|
282
|
+
};
|
|
283
|
+
report(chalk.red(phaseLine("lan", tunnelHolding)));
|
|
284
|
+
} else report(chalk.dim(phaseLine("lan", result.status === "missing" ? "tunnel already gone" : `stopped the ${tunnel.provider} tunnel`)));
|
|
285
|
+
} else if (tunnel?.kind === "expo") outcomes.metroTunnel = { status: "not-managed" };
|
|
286
|
+
outcomes.releasedLeases = releaseLeases(root);
|
|
287
|
+
for (const lease of outcomes.releasedLeases) report(chalk.dim(phaseLine("lease", releasedLeaseFact(lease))));
|
|
288
|
+
if (stillHolding) {
|
|
289
|
+
outcomes.port = {
|
|
290
|
+
status: "kept",
|
|
291
|
+
port: reservedPort,
|
|
292
|
+
reason: stillHolding
|
|
293
|
+
};
|
|
294
|
+
report(chalk.yellow(phaseLine("port", `keeping reservation ${reservedPort ?? "(none)"} -- ${stillHolding}`)));
|
|
295
|
+
const supervisorIsDown = outcomes.supervisor.status === "none" || outcomes.supervisor.status === "already-stopped" || outcomes.supervisor.status === "stopped";
|
|
296
|
+
if (tunnelHolding && supervisorIsDown) {
|
|
297
|
+
clearState(root, sup);
|
|
298
|
+
await clearRegistration(root, proj?.supervisor ?? null);
|
|
299
|
+
}
|
|
300
|
+
} else await releaseStoppedSupervisor();
|
|
301
|
+
return {
|
|
302
|
+
ok,
|
|
303
|
+
outcomes,
|
|
304
|
+
summary: summarize(root, outcomes, ok)
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
async function reapCollectors(root, collectors, { isAlive, signal, report, verify = verifyCollectorOwnership, waiter }) {
|
|
308
|
+
const targets = resolveCollectorTargets({
|
|
309
|
+
root,
|
|
310
|
+
collectors,
|
|
311
|
+
isAlive,
|
|
312
|
+
verify
|
|
313
|
+
});
|
|
314
|
+
const entries = [];
|
|
315
|
+
for (const target of targets) {
|
|
316
|
+
if (target.status === "invalid") {
|
|
317
|
+
entries.push({
|
|
318
|
+
platform: target.platform,
|
|
319
|
+
pid: target.pid,
|
|
320
|
+
status: "invalid"
|
|
321
|
+
});
|
|
322
|
+
report(chalk.dim(phaseLine("stop", `ignoring an unusable ${target.platform} collector record`)));
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (target.status === "stale") {
|
|
326
|
+
entries.push({
|
|
327
|
+
platform: target.platform,
|
|
328
|
+
pid: target.pid,
|
|
329
|
+
status: "already-stopped"
|
|
330
|
+
});
|
|
331
|
+
report(chalk.dim(phaseLine("stop", `collector ${target.platform} pid ${target.pid} is already gone`)));
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (target.status === "unverified") {
|
|
335
|
+
entries.push({
|
|
336
|
+
platform: target.platform,
|
|
337
|
+
pid: target.pid,
|
|
338
|
+
status: "unverified",
|
|
339
|
+
reason: target.reason
|
|
340
|
+
});
|
|
341
|
+
report(chalk.yellow(phaseLine("stop", `refusing to signal collector ${target.platform} pid ${target.pid}: ${target.reason}`)));
|
|
342
|
+
continue;
|
|
343
|
+
}
|
|
344
|
+
try {
|
|
345
|
+
const ownership = verify({
|
|
346
|
+
pid: target.pid,
|
|
347
|
+
platform: target.platform,
|
|
348
|
+
root,
|
|
349
|
+
isAlive,
|
|
350
|
+
expected: collectors?.[target.platform]
|
|
351
|
+
});
|
|
352
|
+
if (ownership.status !== "ours") {
|
|
353
|
+
entries.push({
|
|
354
|
+
platform: target.platform,
|
|
355
|
+
pid: target.pid,
|
|
356
|
+
status: ownership.status === "gone" ? "already-stopped" : "unverified",
|
|
357
|
+
...ownership.status === "unverified" ? { reason: ownership.reason } : {}
|
|
358
|
+
});
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
signal(target.pid);
|
|
362
|
+
if (!await waiter(target.pid, collectors?.[target.platform]?.processToken)) {
|
|
363
|
+
entries.push({
|
|
364
|
+
platform: target.platform,
|
|
365
|
+
pid: target.pid,
|
|
366
|
+
status: "failed",
|
|
367
|
+
reason: "collector exit could not be confirmed"
|
|
368
|
+
});
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
entries.push({
|
|
372
|
+
platform: target.platform,
|
|
373
|
+
pid: target.pid,
|
|
374
|
+
status: "stopped"
|
|
375
|
+
});
|
|
376
|
+
report(chalk.green(phaseLine("stop", `collector ${target.platform} pid ${target.pid}`)));
|
|
377
|
+
} catch (error) {
|
|
378
|
+
const gone = error.code === "ESRCH";
|
|
379
|
+
entries.push({
|
|
380
|
+
platform: target.platform,
|
|
381
|
+
pid: target.pid,
|
|
382
|
+
status: gone ? "already-stopped" : "failed"
|
|
383
|
+
});
|
|
384
|
+
report(chalk.dim(phaseLine("stop", gone ? `collector ${target.platform} pid ${target.pid} exited before it could be signalled` : `could not signal collector ${target.platform} pid ${target.pid}`)));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (entries.length === 0) {
|
|
388
|
+
report(chalk.dim(phaseLine("stop", "no collectors recorded")));
|
|
389
|
+
return {
|
|
390
|
+
status: "none",
|
|
391
|
+
entries
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
return {
|
|
395
|
+
status: "stopped",
|
|
396
|
+
entries
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
async function stopSupervisor(target, { killGroup, waiter, report }) {
|
|
400
|
+
report(chalk.dim(phaseLine("stop", `sending SIGTERM to supervisor process group ${target.pid}`)));
|
|
401
|
+
let signalled = false;
|
|
402
|
+
try {
|
|
403
|
+
signalled = killGroup(target.pid, target.processToken);
|
|
404
|
+
} catch (e) {
|
|
405
|
+
signalled = false;
|
|
406
|
+
report(chalk.red(phaseLine("stop", `could not signal supervisor pid ${target.pid}: ${String(e?.message || e)}`)));
|
|
407
|
+
}
|
|
408
|
+
if (!signalled) {
|
|
409
|
+
const reason = `could not signal supervisor pid ${target.pid}`;
|
|
410
|
+
report(chalk.red(phaseLine("stop", reason)));
|
|
411
|
+
return {
|
|
412
|
+
status: "failed",
|
|
413
|
+
pid: target.pid,
|
|
414
|
+
port: target.port ?? null,
|
|
415
|
+
reason
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
if (await waiter(target.pid, target.processToken)) {
|
|
419
|
+
report(chalk.green(phaseLine("stop", `supervisor pid ${target.pid}`)));
|
|
420
|
+
return {
|
|
421
|
+
status: "stopped",
|
|
422
|
+
pid: target.pid,
|
|
423
|
+
port: target.port ?? null,
|
|
424
|
+
mode: target.mode ?? null
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
const reason = `supervisor pid ${target.pid} did not exit within ${Math.round(DEFAULT_WAIT_MS / 1e3)}s of SIGTERM`;
|
|
428
|
+
report(chalk.red(phaseLine("stop", reason)));
|
|
429
|
+
report(chalk.dim(phaseLine("", `inspect it with \`ps -p ${target.pid}\`, or signal it yourself: kill -9 -${target.pid}`)));
|
|
430
|
+
return {
|
|
431
|
+
status: "timeout",
|
|
432
|
+
pid: target.pid,
|
|
433
|
+
port: target.port ?? null,
|
|
434
|
+
reason
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
async function stopMetro(port, root, { resolveMetro, report }) {
|
|
438
|
+
const resolution = await resolveMetro(port, root);
|
|
439
|
+
if (resolution.missing) {
|
|
440
|
+
report(chalk.dim(phaseLine("metro", `nothing listening on port ${port}`)));
|
|
441
|
+
return {
|
|
442
|
+
status: "missing",
|
|
443
|
+
port
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const reason = resolution.notOurs || "no recorded Stim supervisor owns this server; stop it with the tool that started it";
|
|
447
|
+
report(chalk.yellow(phaseLine("metro", `leaving port ${port} alone: ${reason}`)));
|
|
448
|
+
return {
|
|
449
|
+
status: "not-managed",
|
|
450
|
+
port,
|
|
451
|
+
reason
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
const OCCUPANCY_HINT = "often a UI-test runner or device tool still attached";
|
|
455
|
+
function occupiedSkipReason(reason, holders) {
|
|
456
|
+
const named = (holders || []).filter(Boolean);
|
|
457
|
+
return named.length ? `${reason} -- held by UI-test runner ${named.join(", ")}` : `${reason} -- ${OCCUPANCY_HINT}`;
|
|
458
|
+
}
|
|
459
|
+
function shutDownDevices(project, { teardownIos, teardownAvd, report }) {
|
|
460
|
+
const device = {
|
|
461
|
+
ios: null,
|
|
462
|
+
android: null
|
|
463
|
+
};
|
|
464
|
+
const ios = project?.platforms?.ios;
|
|
465
|
+
const iosUdid = ios?.deviceUdid;
|
|
466
|
+
const iosName = ios?.deviceName;
|
|
467
|
+
if (iosUdid) {
|
|
468
|
+
if (!ios?.owned) {
|
|
469
|
+
device.ios = {
|
|
470
|
+
status: "skipped",
|
|
471
|
+
kind: "not-owned",
|
|
472
|
+
label: iosUdid,
|
|
473
|
+
reason: "Stim does not own this device"
|
|
474
|
+
};
|
|
475
|
+
report(chalk.dim(phaseLine("device", `${iosUdid} is not Stim-owned, leaving it running`)));
|
|
476
|
+
} else device.ios = reportDevice(iosUdid, teardownIos(iosUdid, {
|
|
477
|
+
del: false,
|
|
478
|
+
label: iosName
|
|
479
|
+
}), report);
|
|
480
|
+
}
|
|
481
|
+
const android = project?.platforms?.android;
|
|
482
|
+
if (android?.avdName) {
|
|
483
|
+
if (!android.owned) {
|
|
484
|
+
device.android = {
|
|
485
|
+
status: "skipped",
|
|
486
|
+
kind: "not-owned",
|
|
487
|
+
label: android.avdName,
|
|
488
|
+
reason: "Stim does not own this device"
|
|
489
|
+
};
|
|
490
|
+
report(chalk.dim(phaseLine("device", `${android.avdName} is not Stim-owned, leaving it running`)));
|
|
491
|
+
} else device.android = reportDevice(android.avdName, teardownAvd(android.avdName, { del: false }), report);
|
|
492
|
+
}
|
|
493
|
+
return device;
|
|
494
|
+
}
|
|
495
|
+
function reportDevice(label, r, report) {
|
|
496
|
+
if (r.status === "torn-down") {
|
|
497
|
+
report(chalk.green(phaseLine("device", `shut down ${r.label ?? label}`)));
|
|
498
|
+
return {
|
|
499
|
+
status: "shut-down",
|
|
500
|
+
label: r.label ?? label
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
if (r.status === "missing") {
|
|
504
|
+
report(chalk.dim(phaseLine("device", `${label} is already gone`)));
|
|
505
|
+
return {
|
|
506
|
+
status: "missing",
|
|
507
|
+
label
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
if (r.status === "skipped") {
|
|
511
|
+
const reason = r.kind === "occupied" ? occupiedSkipReason(r.reason, r.holders) : r.reason;
|
|
512
|
+
report(chalk.yellow(phaseLine("device", `skipped ${label}: ${reason}`)));
|
|
513
|
+
return {
|
|
514
|
+
status: "skipped",
|
|
515
|
+
kind: r.kind ?? null,
|
|
516
|
+
label,
|
|
517
|
+
reason
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
report(chalk.red(phaseLine("device", `failed to shut down ${label}: ${r.reason}`)));
|
|
521
|
+
return {
|
|
522
|
+
status: "failed",
|
|
523
|
+
label,
|
|
524
|
+
reason: r.reason
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function summarize(root, outcomes, ok) {
|
|
528
|
+
const parts = [];
|
|
529
|
+
if (outcomes.supervisor.status === "stopped") parts.push(`supervisor pid ${outcomes.supervisor.pid} stopped`);
|
|
530
|
+
if (outcomes.supervisor.status === "already-stopped") parts.push("supervisor already stopped");
|
|
531
|
+
if (outcomes.supervisor.status === "timeout") parts.push(`supervisor pid ${outcomes.supervisor.pid} still running`);
|
|
532
|
+
if (outcomes.supervisor.status === "unverified") parts.push(`supervisor pid ${outcomes.supervisor.pid} unverified`);
|
|
533
|
+
if (outcomes.supervisor.status === "failed") parts.push(`supervisor pid ${outcomes.supervisor.pid} could not be signalled`);
|
|
534
|
+
const reaped = outcomes.collectors.entries.filter((e) => e.status === "stopped").length;
|
|
535
|
+
if (reaped) parts.push(`${reaped} collector${reaped === 1 ? "" : "s"} stopped`);
|
|
536
|
+
const unverified = outcomes.collectors.entries.filter((e) => e.status === "unverified").length;
|
|
537
|
+
if (unverified) parts.push(`${unverified} collector${unverified === 1 ? "" : "s"} left unsignalled`);
|
|
538
|
+
if (outcomes.metro.status === "stopped") parts.push(`metro on port ${outcomes.metro.port} stopped`);
|
|
539
|
+
if (outcomes.metro.status === "not-managed") parts.push(`external server on port ${outcomes.metro.port} left alone`);
|
|
540
|
+
if (outcomes.metro.status === "refused") parts.push(`port ${outcomes.metro.port} refused`);
|
|
541
|
+
if (outcomes.metro.status === "failed") parts.push(`port ${outcomes.metro.port} could not be freed`);
|
|
542
|
+
const devicesByPlatform = [["ios", outcomes.device.ios], ["android", outcomes.device.android]];
|
|
543
|
+
for (const [platform, o] of devicesByPlatform) {
|
|
544
|
+
if (!o) continue;
|
|
545
|
+
if (o.status === "shut-down") parts.push(`${platform} ${o.label} shut down`);
|
|
546
|
+
if (o.status === "skipped") parts.push(`${platform} ${o.label} skipped`);
|
|
547
|
+
if (o.status === "failed") parts.push(`${platform} ${o.label} failed`);
|
|
548
|
+
}
|
|
549
|
+
if (outcomes.port.status === "freed") parts.push(`port ${outcomes.port.port} freed`);
|
|
550
|
+
if (outcomes.port.status === "kept") parts.push(`port ${outcomes.port.port} kept`);
|
|
551
|
+
if (outcomes.metroTunnel.status === "stopped") parts.push(`${outcomes.metroTunnel.provider} tunnel stopped`);
|
|
552
|
+
if (outcomes.metroTunnel.status === "failed") parts.push(`${outcomes.metroTunnel.provider} tunnel could not be stopped`);
|
|
553
|
+
for (const lease of outcomes.releasedLeases) parts.push(`${lease.platform} lease on ${lease.id} released`);
|
|
554
|
+
const what = parts.length ? parts.join(", ") : "nothing was running";
|
|
555
|
+
return `${ok ? "Stopped" : "Stopped with problems"}: ${what} (${root})`;
|
|
556
|
+
}
|
|
557
|
+
function defaultFreePort(root, port) {
|
|
558
|
+
return withConfigLock(() => {
|
|
559
|
+
const current = getProject(root);
|
|
560
|
+
if (!current) return true;
|
|
561
|
+
if (current.supervisor || current.metroPort !== port) return false;
|
|
562
|
+
upsertProject(root, { metroPort: null });
|
|
563
|
+
return true;
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
async function defaultClearRegistration(root, expected) {
|
|
567
|
+
try {
|
|
568
|
+
return clearSupervisor(root, expected);
|
|
569
|
+
} catch {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
function stopCommand(program) {
|
|
574
|
+
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("--json", "print the per-step outcomes as JSON").action(async (opts) => {
|
|
575
|
+
const root = findProjectRoot(process.cwd());
|
|
576
|
+
if (!root) {
|
|
577
|
+
console.error(chalk.red("Not inside a project (no package.json found above the current directory)."));
|
|
578
|
+
process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
const { ok, outcomes, summary } = await runStop({ root });
|
|
581
|
+
if (opts.json) console.log(JSON.stringify({
|
|
582
|
+
root,
|
|
583
|
+
ok,
|
|
584
|
+
...outcomes
|
|
585
|
+
}));
|
|
586
|
+
else console.log(ok ? chalk.green(summary) : chalk.yellow(summary));
|
|
587
|
+
if (!ok) process.exit(1);
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
//#endregion
|
|
591
|
+
export { resolveCollectorTargets as a, readSupervisorState as i, clearSupervisorState as n, runStop as o, readCollectorState as r, stopCommand as s, clearCollectorState as t };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { t as resolveSupervisorTarget } from "./ownership-B9QIdQOw.mjs";
|
|
2
|
+
import { a as resolveCollectorTargets, i as readSupervisorState, n as clearSupervisorState, o as runStop, r as readCollectorState, s as stopCommand, t as clearCollectorState } from "./stop-BApyaF21.mjs";
|
|
3
|
+
export { clearCollectorState, clearSupervisorState, stopCommand as default, readCollectorState, readSupervisorState, resolveCollectorTargets, resolveSupervisorTarget, runStop };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { t as NdjsonWriter } from "./ndjson-DvHSsNU9.mjs";
|
|
2
|
+
//#region src/process-identity.d.ts
|
|
3
|
+
interface ProcessRecord {
|
|
4
|
+
pid?: unknown;
|
|
5
|
+
processToken?: unknown;
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
//#region src/supervisor/state.d.ts
|
|
9
|
+
declare const MODE_BARE = "bare-inproc";
|
|
10
|
+
declare const MODE_EXPO = "expo-child";
|
|
11
|
+
interface WorkspaceState {
|
|
12
|
+
metroCacheGeneration?: string;
|
|
13
|
+
supervisor?: Record<string, unknown>;
|
|
14
|
+
collectors?: Record<string, unknown>;
|
|
15
|
+
lastBuild?: Record<string, unknown>;
|
|
16
|
+
launches?: Partial<Record<WorkspaceLaunchPlatform, WorkspaceLaunchRecord>>;
|
|
17
|
+
remoteDevice?: Record<string, unknown>;
|
|
18
|
+
metroTunnel?: Record<string, unknown>;
|
|
19
|
+
[key: string]: unknown;
|
|
20
|
+
}
|
|
21
|
+
type WorkspaceLaunchPlatform = "ios" | "android";
|
|
22
|
+
interface WorkspaceLaunchRecord {
|
|
23
|
+
appId: string;
|
|
24
|
+
deviceId: string;
|
|
25
|
+
metroPort: number | null;
|
|
26
|
+
release: boolean;
|
|
27
|
+
launchedAt: string;
|
|
28
|
+
}
|
|
29
|
+
declare function readWorkspaceState(root: string): WorkspaceState | null;
|
|
30
|
+
declare function withWorkspaceStateLock<T>(root: string, fn: () => T): T;
|
|
31
|
+
declare function writeWorkspaceState(root: string, patch: WorkspaceState): WorkspaceState;
|
|
32
|
+
declare function clearWorkspaceSupervisor(root: string, expected?: ProcessRecord | null): boolean;
|
|
33
|
+
declare function clearExpoMetroTunnel(root: string): void;
|
|
34
|
+
declare function writePidFile(root: string, pid: number): string;
|
|
35
|
+
declare function readPidFile(root: string): number | null;
|
|
36
|
+
//#endregion
|
|
37
|
+
//#region src/supervisor/run.d.ts
|
|
38
|
+
interface ParsedSupervisorArgs {
|
|
39
|
+
root?: string;
|
|
40
|
+
port?: number;
|
|
41
|
+
tunnel?: boolean;
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
declare function parseArgs(argv: string[]): ParsedSupervisorArgs;
|
|
45
|
+
interface ServerExitInfo {
|
|
46
|
+
code?: number | null;
|
|
47
|
+
signal?: NodeJS.Signals | null;
|
|
48
|
+
reason?: string;
|
|
49
|
+
error?: Error;
|
|
50
|
+
}
|
|
51
|
+
interface ServerHandle {
|
|
52
|
+
close(): Promise<void> | void;
|
|
53
|
+
onExit?(cb: (info?: ServerExitInfo | null) => void): void;
|
|
54
|
+
serverPid?: number | null;
|
|
55
|
+
}
|
|
56
|
+
type ServerStarter = (opts: {
|
|
57
|
+
root: string;
|
|
58
|
+
port: number;
|
|
59
|
+
logsDir: string;
|
|
60
|
+
writer?: NdjsonWriter | null;
|
|
61
|
+
tunnel?: boolean;
|
|
62
|
+
onTunnelUrl?: ((url: string) => void) | null;
|
|
63
|
+
}) => Promise<ServerHandle>;
|
|
64
|
+
interface RunSupervisorOptions {
|
|
65
|
+
root: string;
|
|
66
|
+
port: number;
|
|
67
|
+
tunnel?: boolean;
|
|
68
|
+
isExpo?: (projectRoot: string) => boolean;
|
|
69
|
+
startBare?: ServerStarter | null;
|
|
70
|
+
startExpo?: ServerStarter | null;
|
|
71
|
+
now?: () => number;
|
|
72
|
+
onExit?: (code: number) => void;
|
|
73
|
+
attachSignals?: boolean;
|
|
74
|
+
stderr?: (line: string) => void;
|
|
75
|
+
}
|
|
76
|
+
declare function runSupervisor({ root, port, tunnel, isExpo, startBare, startExpo, now, onExit, attachSignals, stderr }: RunSupervisorOptions): Promise<{
|
|
77
|
+
mode: string;
|
|
78
|
+
server: ServerHandle | undefined;
|
|
79
|
+
shutdown: (code: number, event: string, msg: string) => Promise<void>;
|
|
80
|
+
startedAt: string;
|
|
81
|
+
} | null>;
|
|
82
|
+
declare function main(argv?: string[]): Promise<void>;
|
|
83
|
+
//#endregion
|
|
84
|
+
export { MODE_BARE, MODE_EXPO, RunSupervisorOptions, ServerExitInfo, ServerHandle, type WorkspaceState, clearExpoMetroTunnel, clearWorkspaceSupervisor, main, parseArgs, readPidFile, readWorkspaceState, runSupervisor, withWorkspaceStateLock, writePidFile, writeWorkspaceState };
|