stim 1.9.0 → 1.10.1
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/dist/{android-_y4Ohi5z.mjs → android-787HhP5w.mjs} +119 -29
- package/dist/{android-BPGizezh.mjs → android-Bu4mbDzc.mjs} +168 -95
- package/dist/{android-DEgo4oCo.mjs → android-DPov-fv2.mjs} +2 -2
- package/dist/{android-cas-eiExNLf7.mjs → android-cas-CITmIeWY.mjs} +9 -8
- package/dist/android-cas-compiler.mjs +1 -1
- package/dist/{app-install-Uf3a83Tv.mjs → app-install-Cfmqr3TQ.mjs} +2 -2
- package/dist/budget-nDCEwtJv.mjs +620 -0
- package/dist/build-progress-BXx-ZBe4.mjs +512 -0
- package/dist/{cache-manifest-Dg7OnMrp.mjs → cache-manifest-Pubfp8wV.mjs} +1 -1
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cli.mjs +16 -16
- package/dist/collector-run.d.mts +5 -1
- package/dist/collector-run.mjs +7 -7
- package/dist/{command-output-gByVmIjy.mjs → command-output-D5z2_bcF.mjs} +1 -1
- package/dist/{config-Cp9lM3wX.mjs → config-BEq0Qw_a.mjs} +14 -131
- package/dist/{dependency-state-Bq5DcLGn.mjs → dependency-state-r9UesA9a.mjs} +2 -2
- package/dist/{deps-Cip5vUm1.mjs → deps-DP4eWqea.mjs} +3 -3
- package/dist/{detached-entry-KEu0_AXL.mjs → detached-entry-DJHoca49.mjs} +1 -1
- package/dist/{dev-client-HyqISM0h.mjs → dev-client-DExzPiOa.mjs} +137 -250
- package/dist/{device-D4U-xLty.mjs → device-Czh5kO8A.mjs} +7 -7
- package/dist/{build-lock-DPXPLgTc.mjs → device-lease-CbEaIk7b.mjs} +370 -12
- package/dist/{device-pool-CnTAz8X1.mjs → device-pool-BBWLAr55.mjs} +4 -4
- package/dist/{device-remote-AQ5rQPqr.mjs → device-remote-powUHdbK.mjs} +547 -468
- package/dist/{doctor-B_Ei3bZu.mjs → doctor-CmqK1LcQ.mjs} +58 -22
- package/dist/{error-diagnostics-Vfz83GCF.mjs → error-diagnostics-xxWKNO3-.mjs} +11 -12
- package/dist/{exec-jIAlgilk.mjs → exec-DK8KaVHK.mjs} +3 -2
- package/dist/{gc-Ckw8RQHi.mjs → gc-zCDpmdHU.mjs} +500 -1114
- package/dist/{guide-BiAbhs1J.mjs → guide-DwmaD_Wa.mjs} +882 -528
- package/dist/guide-status-CWvti2Lg.mjs +125 -0
- package/dist/idle--jn7pBNe.mjs +157 -0
- package/dist/in-use-CNIWU-r-.mjs +245 -0
- package/dist/{ios-BL2NFckr.mjs → ios-COGcGEcN.mjs} +39 -65
- package/dist/{ios-BzUJWk1K.mjs → ios-CbdqTqOc.mjs} +130 -89
- package/dist/{ios-device-DPsSm0v_.mjs → ios-device-BiOsSVzd.mjs} +1 -1
- package/dist/{ios-device-D972i9wf.mjs → ios-device-DNjPXBnb.mjs} +3 -3
- package/dist/{launch-verify-Q_OtW1Rf.mjs → launch-verify-BhgkYAws.mjs} +5 -5
- package/dist/{logs-CI9NQSK7.mjs → logs-MARIy6m9.mjs} +9 -9
- package/dist/{metro-DrLHaMn2.mjs → metro-BWLOJ_Wr.mjs} +116 -10
- package/dist/{named-ports-CmLQSIRj.mjs → named-ports-DiTfSz6p.mjs} +4 -4
- package/dist/{ndjson-9uFNRwfe.mjs → ndjson-CDY9IkVC.mjs} +3 -56
- package/dist/{ndjson-DvHSsNU9.d.mts → ndjson-Du3j0AQ9.d.mts} +1 -0
- package/dist/ownership-BfPNRf3-.mjs +1180 -0
- package/dist/{ownership-D15Op8vN.mjs → ownership-D2xbLVoz.mjs} +2 -3
- package/dist/{ports-BNKoWVWZ.mjs → ports-CLSSFOuz.mjs} +3 -3
- package/dist/{project-DS112DYB.mjs → project-BXZzOABt.mjs} +2 -2
- package/dist/{reload-ZNJUo1pl.mjs → reload-CymyF6Kk.mjs} +9 -10
- package/dist/{server-bare-DWkkcIxV.mjs → server-bare-DVbb7kXH.mjs} +1 -1
- package/dist/{server-expo-B_lctBR3.mjs → server-expo-BvOCeakp.mjs} +4 -4
- package/dist/server-expo-DBVTdult.mjs +2 -0
- package/dist/{settings-D030k0t4.mjs → settings-DOugPlPz.mjs} +16 -428
- package/dist/{settings-BKNh5rl4.mjs → settings-EWYNwe72.mjs} +16 -14
- package/dist/settings.schema.json +75 -0
- package/dist/{simslim-B_LsGLuJ.mjs → simslim-DZUxzsjq.mjs} +182 -5
- package/dist/{slot-launch-DU9A-gYw.mjs → slot-launch-Dk4oTREM.mjs} +4 -4
- package/dist/{spawn-claims-Dn3C_N6w.mjs → spawn-claims-CUleC2cM.mjs} +1 -1
- package/dist/{start-DYkkKF0s.mjs → start-RbmFPeZ7.mjs} +147 -98
- package/dist/start-qWapeB4W.mjs +2 -0
- package/dist/{state-NcJzMEBF.mjs → state-WGdIbi_d.mjs} +2 -2
- package/dist/{stats-DG9e66Rz.mjs → stats-BAgJaLA4.mjs} +1 -1
- package/dist/{stats-B63-JVAY.mjs → stats-DBYJ5bxL.mjs} +4 -4
- package/dist/status-Dr4fRkym.mjs +216 -0
- package/dist/{status-S6ehL4Ui.mjs → status-Zp8kvexe.mjs} +63 -231
- package/dist/{guide-status-BAMVU0ZA.mjs → stim-installations-CINXy_su.mjs} +3 -124
- package/dist/{stop-T1R-cZRz.mjs → stop-DI4BG4Os.mjs} +2 -2
- package/dist/{stop-BO28HGqF.mjs → stop-DcEv5sk-.mjs} +22 -18
- package/dist/supervisor-run.d.mts +11 -2
- package/dist/supervisor-run.mjs +185 -17
- package/dist/{workspace-process-lock-DWaiRD0e.mjs → workspace-process-lock-BXA96myD.mjs} +2 -2
- package/dist/workspaces-wrH6dXfu.mjs +1003 -0
- package/dist/{worktree-LGB-UwgG.mjs → worktree-Bd4dRvx7.mjs} +40 -122
- package/dist/{worktree-R_cUlE7s.mjs → worktree-BlizKDpZ.mjs} +143 -18
- package/dist/{worktree-DKhSF0UX.mjs → worktree-DEG2o9F8.mjs} +1 -1
- package/dist/{xcode-BZqyxh3e.mjs → xcode-B2tBkf0m.mjs} +9 -158
- package/package.json +6 -5
- package/dist/build-progress-CVqkfPC6.mjs +0 -188
- package/dist/device-lease-Crs-HkJY.mjs +0 -366
- package/dist/logs-query-Cqma3WIu.mjs +0 -298
- package/dist/ownership-DCQPuWEc.mjs +0 -489
- package/dist/public-http-probe-CyMDrQ3c.mjs +0 -61
- package/dist/server-expo-CeILEu86.mjs +0 -2
- package/dist/sim-pool-KaIsp2Ft.mjs +0 -723
- package/dist/spawn-entry-nCE6m770.mjs +0 -14
- package/dist/workspace-state-DF2gCFhx.mjs +0 -169
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { f as getProject, u as getConfigDir, w as upsertProject } from "./config-BEq0Qw_a.mjs";
|
|
2
|
+
import { n as appProjectProblem } from "./project-BXZzOABt.mjs";
|
|
3
|
+
import { t as compareStimVersions } from "./stim-installations-CINXy_su.mjs";
|
|
4
|
+
import { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
//#region src/guide-status.ts
|
|
7
|
+
const DAY_MS = 864e5;
|
|
8
|
+
const DOCTOR_STALE_MS = 7 * DAY_MS;
|
|
9
|
+
const UPDATE_CHECK_INTERVAL_MS = DAY_MS;
|
|
10
|
+
const UPDATE_CHECK_TIMEOUT_MS = 2e3;
|
|
11
|
+
const LATEST_URL = "https://registry.npmjs.org/stim/latest";
|
|
12
|
+
const PLATFORMS = ["ios", "android"];
|
|
13
|
+
function recordDoctorRun(projectRoot, platform, version, now = /* @__PURE__ */ new Date()) {
|
|
14
|
+
const run = {
|
|
15
|
+
at: now.toISOString(),
|
|
16
|
+
version
|
|
17
|
+
};
|
|
18
|
+
try {
|
|
19
|
+
if (appProjectProblem(projectRoot)) return;
|
|
20
|
+
upsertProject(projectRoot, (existing) => {
|
|
21
|
+
const doctorRuns = { ...existing.doctorRuns };
|
|
22
|
+
for (const target of platform ? [platform] : PLATFORMS) doctorRuns[target] = run;
|
|
23
|
+
return { doctorRuns };
|
|
24
|
+
});
|
|
25
|
+
} catch {}
|
|
26
|
+
}
|
|
27
|
+
function doctorDueReason(record, running, now) {
|
|
28
|
+
const at = record ? Date.parse(record.at) : NaN;
|
|
29
|
+
if (!record || Number.isNaN(at)) return "never run";
|
|
30
|
+
if (record.version !== running) return `last run with stim ${record.version}`;
|
|
31
|
+
const age = now.getTime() - at;
|
|
32
|
+
if (age > DOCTOR_STALE_MS) return `last run ${Math.floor(age / DAY_MS)} days ago`;
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function updateCacheFile() {
|
|
36
|
+
return join(getConfigDir(), "update-check.json");
|
|
37
|
+
}
|
|
38
|
+
function readUpdateCache() {
|
|
39
|
+
try {
|
|
40
|
+
const parsed = JSON.parse(readFileSync(updateCacheFile(), "utf-8"));
|
|
41
|
+
if (typeof parsed?.checkedAt !== "string") return null;
|
|
42
|
+
return {
|
|
43
|
+
checkedAt: parsed.checkedAt,
|
|
44
|
+
latest: typeof parsed.latest === "string" ? parsed.latest : null
|
|
45
|
+
};
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function writeUpdateCache(cache) {
|
|
51
|
+
const target = updateCacheFile();
|
|
52
|
+
const tmp = `${target}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
53
|
+
try {
|
|
54
|
+
mkdirSync(getConfigDir(), { recursive: true });
|
|
55
|
+
writeFileSync(tmp, `${JSON.stringify(cache)}\n`);
|
|
56
|
+
renameSync(tmp, target);
|
|
57
|
+
} catch {
|
|
58
|
+
try {
|
|
59
|
+
unlinkSync(tmp);
|
|
60
|
+
} catch {}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async function fetchLatest(fetchImpl) {
|
|
64
|
+
const res = await fetchImpl(LATEST_URL, { signal: AbortSignal.timeout(UPDATE_CHECK_TIMEOUT_MS) });
|
|
65
|
+
if (!res.ok) return null;
|
|
66
|
+
const body = await res.json();
|
|
67
|
+
return typeof body.version === "string" ? body.version : null;
|
|
68
|
+
}
|
|
69
|
+
async function checkForUpdate(running, { now = /* @__PURE__ */ new Date(), fetch: fetchImpl = fetch } = {}) {
|
|
70
|
+
if (process.env.STIM_NO_UPDATE_CHECK) return null;
|
|
71
|
+
const cached = readUpdateCache();
|
|
72
|
+
const checkedAt = cached ? Date.parse(cached.checkedAt) : NaN;
|
|
73
|
+
let latest = cached?.latest ?? null;
|
|
74
|
+
if (Number.isNaN(checkedAt) || now.getTime() - checkedAt > UPDATE_CHECK_INTERVAL_MS) {
|
|
75
|
+
try {
|
|
76
|
+
latest = await fetchLatest(fetchImpl);
|
|
77
|
+
} catch {
|
|
78
|
+
latest = null;
|
|
79
|
+
}
|
|
80
|
+
writeUpdateCache({
|
|
81
|
+
checkedAt: now.toISOString(),
|
|
82
|
+
latest
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (!latest) return null;
|
|
86
|
+
const comparison = compareStimVersions(latest, running);
|
|
87
|
+
return comparison !== null && comparison > 0 ? latest : null;
|
|
88
|
+
}
|
|
89
|
+
function renderGuideStatus({ running, doctor, latest }) {
|
|
90
|
+
const lines = [];
|
|
91
|
+
if (doctor.length) {
|
|
92
|
+
const named = doctor.map((due) => `${due.platform} (${due.reason})`).join(" and ");
|
|
93
|
+
const command = doctor.length === 1 ? `stim doctor --platform ${doctor[0]?.platform}` : "stim doctor";
|
|
94
|
+
lines.push(` Doctor is due for ${named}.`, ` Run before native work: ${command}`);
|
|
95
|
+
}
|
|
96
|
+
if (latest) lines.push(` stim ${latest} is available (running ${running}): npm install -g stim@latest`);
|
|
97
|
+
if (!lines.length) return null;
|
|
98
|
+
return ["STATUS", ...lines].join("\n");
|
|
99
|
+
}
|
|
100
|
+
async function guideStatus({ projectRoot, running, now = /* @__PURE__ */ new Date(), fetch: fetchImpl = fetch }) {
|
|
101
|
+
const doctor = [];
|
|
102
|
+
if (projectRoot && !appProjectProblem(projectRoot)) {
|
|
103
|
+
let runs = null;
|
|
104
|
+
try {
|
|
105
|
+
runs = getProject(projectRoot)?.doctorRuns ?? {};
|
|
106
|
+
} catch {}
|
|
107
|
+
if (runs) for (const platform of PLATFORMS) {
|
|
108
|
+
const reason = doctorDueReason(runs[platform], running, now);
|
|
109
|
+
if (reason) doctor.push({
|
|
110
|
+
platform,
|
|
111
|
+
reason
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return renderGuideStatus({
|
|
116
|
+
running,
|
|
117
|
+
doctor,
|
|
118
|
+
latest: await checkForUpdate(running, {
|
|
119
|
+
now,
|
|
120
|
+
fetch: fetchImpl
|
|
121
|
+
})
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
//#endregion
|
|
125
|
+
export { recordDoctorRun as n, guideStatus as t };
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { H as projectDeviceSlots } from "./config-BEq0Qw_a.mjs";
|
|
2
|
+
import { m as readWorkspaceState } from "./metro-BWLOJ_Wr.mjs";
|
|
3
|
+
import { o as formatLongDuration } from "./command-output-D5z2_bcF.mjs";
|
|
4
|
+
import { c as createActivityReader, l as idleForMs, o as parseActiveBuild, r as activeBuildState, t as ACTIVE_BUILD_KEY } from "./build-progress-BXx-ZBe4.mjs";
|
|
5
|
+
import { N as ownedAvdSerialResolver } from "./android-787HhP5w.mjs";
|
|
6
|
+
import { d as teardownOwnedAvd, f as teardownOwnedIosSim } from "./ownership-BfPNRf3-.mjs";
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
//#region src/commands/gc/idle.ts
|
|
9
|
+
function findOwnedDeviceActivity({ config, sims, androidSerial, readActivity, deadProjects = [] }) {
|
|
10
|
+
const booted = new Map(sims.filter((sim) => sim.state === "Booted").map((sim) => [sim.udid, sim.name]));
|
|
11
|
+
const dead = new Set(deadProjects);
|
|
12
|
+
const found = [];
|
|
13
|
+
for (const [project, record] of Object.entries(config?.projects ?? {})) {
|
|
14
|
+
if (dead.has(project)) continue;
|
|
15
|
+
for (const { slot, platforms } of projectDeviceSlots(record)) {
|
|
16
|
+
const ios = platforms.ios;
|
|
17
|
+
if (ios?.owned && ios.deviceUdid && booted.has(ios.deviceUdid)) found.push({
|
|
18
|
+
kind: "ios",
|
|
19
|
+
id: ios.deviceUdid,
|
|
20
|
+
name: booted.get(ios.deviceUdid) ?? ios.deviceUdid,
|
|
21
|
+
project,
|
|
22
|
+
slot,
|
|
23
|
+
activity: readActivity({
|
|
24
|
+
platform: "ios",
|
|
25
|
+
id: ios.deviceUdid,
|
|
26
|
+
slot,
|
|
27
|
+
workspace: project
|
|
28
|
+
})
|
|
29
|
+
});
|
|
30
|
+
const android = platforms.android;
|
|
31
|
+
const serial = android?.owned && android.avdName ? androidSerial(android.avdName) : null;
|
|
32
|
+
if (android?.avdName && serial) found.push({
|
|
33
|
+
kind: "android",
|
|
34
|
+
id: android.avdName,
|
|
35
|
+
name: android.avdName,
|
|
36
|
+
project,
|
|
37
|
+
slot,
|
|
38
|
+
activity: readActivity({
|
|
39
|
+
platform: "android",
|
|
40
|
+
id: serial,
|
|
41
|
+
slot,
|
|
42
|
+
workspace: project
|
|
43
|
+
})
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return found;
|
|
48
|
+
}
|
|
49
|
+
function findIdleDevices({ buildInProgress, now, ...inputs }) {
|
|
50
|
+
return findOwnedDeviceActivity(inputs).filter((device) => device.activity.state === "idle").map(({ kind, id, name, project, slot, activity }) => ({
|
|
51
|
+
kind,
|
|
52
|
+
id,
|
|
53
|
+
name,
|
|
54
|
+
project,
|
|
55
|
+
slot,
|
|
56
|
+
lastActivityAt: activity.lastActivityAt ?? null,
|
|
57
|
+
idleForMs: idleForMs(activity, now),
|
|
58
|
+
buildInProgress: buildInProgress(project)
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
function idleShutdownCandidates(devices, idleMs) {
|
|
62
|
+
return devices.filter((d) => !d.buildInProgress && d.idleForMs !== null && d.idleForMs >= idleMs);
|
|
63
|
+
}
|
|
64
|
+
function parseIdleDuration(value) {
|
|
65
|
+
const match = /^([1-9][0-9]*)(m|h|d)$/.exec(value.trim());
|
|
66
|
+
if (!match) return null;
|
|
67
|
+
const unit = {
|
|
68
|
+
m: 6e4,
|
|
69
|
+
h: 36e5,
|
|
70
|
+
d: 864e5
|
|
71
|
+
}[match[2]];
|
|
72
|
+
return Number(match[1]) * unit;
|
|
73
|
+
}
|
|
74
|
+
function workspaceBuildInProgress(project) {
|
|
75
|
+
const record = parseActiveBuild(readWorkspaceState(project)?.[ACTIVE_BUILD_KEY]);
|
|
76
|
+
if (!record) return false;
|
|
77
|
+
try {
|
|
78
|
+
return activeBuildState(record.claim) !== "stale";
|
|
79
|
+
} catch {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function deviceActivityInputs(config, sims, now) {
|
|
84
|
+
const resolve = ownedAvdSerialResolver({ timeoutMs: 5e3 });
|
|
85
|
+
return {
|
|
86
|
+
config,
|
|
87
|
+
sims,
|
|
88
|
+
readActivity: createActivityReader({ now }),
|
|
89
|
+
androidSerial: (avdName) => {
|
|
90
|
+
try {
|
|
91
|
+
return resolve(avdName).serial ?? null;
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function collectOwnedDeviceActivity(config, sims, now = Date.now()) {
|
|
99
|
+
return findOwnedDeviceActivity(deviceActivityInputs(config, sims, now));
|
|
100
|
+
}
|
|
101
|
+
function collectIdleDevices(config, sims, deadProjects, now = Date.now()) {
|
|
102
|
+
return findIdleDevices({
|
|
103
|
+
...deviceActivityInputs(config, sims, now),
|
|
104
|
+
deadProjects,
|
|
105
|
+
now,
|
|
106
|
+
buildInProgress: workspaceBuildInProgress
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
function idleDeviceLines(devices) {
|
|
110
|
+
if (devices.length === 0) return [];
|
|
111
|
+
return [`Idle owned devices (${devices.length}) - booted with no driver, claim, or recent activity:`, ...devices.map((d) => {
|
|
112
|
+
const slot = d.slot === "default" ? "" : ` [${d.slot}]`;
|
|
113
|
+
const idle = d.idleForMs === null ? "idle for an unknown time" : `idle ${formatLongDuration(d.idleForMs)}`;
|
|
114
|
+
const build = d.buildInProgress ? ", build in progress" : "";
|
|
115
|
+
return ` ${d.kind}${slot} ${d.name} (${d.project}) -- ${idle}${build}`;
|
|
116
|
+
})];
|
|
117
|
+
}
|
|
118
|
+
function shutDownIdleDevices(devices, idleMs, recollect) {
|
|
119
|
+
const candidates = idleShutdownCandidates(devices, idleMs);
|
|
120
|
+
const shutDown = [];
|
|
121
|
+
if (candidates.length === 0) {
|
|
122
|
+
console.log(chalk.dim(`No owned device has been idle for ${formatLongDuration(idleMs)} or more.`));
|
|
123
|
+
return {
|
|
124
|
+
failures: 0,
|
|
125
|
+
shutDown
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
const fresh = idleShutdownCandidates(recollect(), idleMs);
|
|
129
|
+
let failures = 0;
|
|
130
|
+
for (const device of candidates) {
|
|
131
|
+
const what = `${device.kind} ${device.name}`;
|
|
132
|
+
const current = fresh.find((d) => d.kind === device.kind && d.id === device.id && d.project === device.project && d.slot === device.slot);
|
|
133
|
+
if (!current) {
|
|
134
|
+
console.log(chalk.dim(`Kept ${what}: it is no longer idle for ${formatLongDuration(idleMs)}.`));
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const outcome = device.kind === "ios" ? teardownOwnedIosSim(device.id, { label: device.name }) : teardownOwnedAvd(device.id, { owner: {
|
|
138
|
+
projectPath: device.project,
|
|
139
|
+
slot: device.slot
|
|
140
|
+
} });
|
|
141
|
+
if (outcome.status === "torn-down") {
|
|
142
|
+
shutDown.push(device);
|
|
143
|
+
console.log(chalk.green(`Shut down ${what}, idle ${formatLongDuration(current.idleForMs)}`));
|
|
144
|
+
} else if (outcome.status === "missing") console.log(chalk.dim(`${what} is already gone; nothing to shut down.`));
|
|
145
|
+
else if (outcome.status === "skipped") console.log(chalk.yellow(`Skipped ${what}: ${outcome.reason}`));
|
|
146
|
+
else {
|
|
147
|
+
failures++;
|
|
148
|
+
console.log(chalk.red(`Failed to shut down ${what}: ${outcome.reason}`));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
failures,
|
|
153
|
+
shutDown
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
//#endregion
|
|
157
|
+
export { parseIdleDuration as a, idleShutdownCandidates as i, collectOwnedDeviceActivity as n, shutDownIdleDevices as o, idleDeviceLines as r, workspaceBuildInProgress as s, collectIdleDevices as t };
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { f as getProject, k as ownership_claim_exports, u as getConfigDir } from "./config-BEq0Qw_a.mjs";
|
|
2
|
+
import { O as workspaceDir, m as readWorkspaceState } from "./metro-BWLOJ_Wr.mjs";
|
|
3
|
+
import { r as stopDeclaringSpawnsOn, t as declareSpawnsOn } from "./spawn-claims-CUleC2cM.mjs";
|
|
4
|
+
import { n as workspaceProcessLockError, r as workspaceProcessLockPath, t as withWorkspaceProcessLock } from "./workspace-process-lock-BXA96myD.mjs";
|
|
5
|
+
import { a as formatElapsed } from "./command-output-D5z2_bcF.mjs";
|
|
6
|
+
import { h as listBuildLocks } from "./device-lease-CbEaIk7b.mjs";
|
|
7
|
+
import { n as managedLockHolders, t as resolveSupervisorTarget } from "./ownership-BfPNRf3-.mjs";
|
|
8
|
+
import { lstatSync, readdirSync, rmSync, rmdirSync, statSync, unlinkSync } from "node:fs";
|
|
9
|
+
import { realpathSync as realpathSync$1 } from "fs";
|
|
10
|
+
import { resolve } from "path";
|
|
11
|
+
import { join as join$1 } from "node:path";
|
|
12
|
+
//#region src/engine/build-slots.ts
|
|
13
|
+
const SLOT_PREFIX = "slot-";
|
|
14
|
+
const SLOT_LABEL = "build slot";
|
|
15
|
+
const SLOT_POLL_MS = 1e3;
|
|
16
|
+
const SLOT_PROGRESS_MS = 3e4;
|
|
17
|
+
const SLOT_CEILING_MS = 54e5;
|
|
18
|
+
function sleepAsync(ms) {
|
|
19
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
20
|
+
}
|
|
21
|
+
function buildSlotsDir() {
|
|
22
|
+
return join$1(getConfigDir(), "build-slots");
|
|
23
|
+
}
|
|
24
|
+
function buildSlotPath(index) {
|
|
25
|
+
return join$1(buildSlotsDir(), `${SLOT_PREFIX}${index}`);
|
|
26
|
+
}
|
|
27
|
+
function toRecord(holder) {
|
|
28
|
+
const details = holder.details;
|
|
29
|
+
return {
|
|
30
|
+
pid: holder.owner.pid,
|
|
31
|
+
index: typeof details.index === "number" ? details.index : null,
|
|
32
|
+
projectRoot: typeof details.projectRoot === "string" ? details.projectRoot : null,
|
|
33
|
+
startedAt: holder.startedAt || null,
|
|
34
|
+
logFile: typeof details.logFile === "string" ? details.logFile : null
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function tryAcquireBuildSlot({ max, root = null, logFile = null }) {
|
|
38
|
+
if (!max || max <= 0) return {
|
|
39
|
+
acquired: true,
|
|
40
|
+
unlimited: true
|
|
41
|
+
};
|
|
42
|
+
let refusal = null;
|
|
43
|
+
let busy = false;
|
|
44
|
+
for (let index = 0; index < max; index++) {
|
|
45
|
+
const path = buildSlotPath(index);
|
|
46
|
+
let attempt;
|
|
47
|
+
try {
|
|
48
|
+
attempt = (0, ownership_claim_exports.tryAcquireClaim)({
|
|
49
|
+
root: path,
|
|
50
|
+
mode: "exclusive",
|
|
51
|
+
label: SLOT_LABEL,
|
|
52
|
+
details: {
|
|
53
|
+
index,
|
|
54
|
+
projectRoot: root,
|
|
55
|
+
logFile
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
} catch (err) {
|
|
59
|
+
if (!(0, ownership_claim_exports.isClaimRefusal)(err)) throw err;
|
|
60
|
+
refusal ??= err;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
|
|
64
|
+
if (!attempt.acquired) {
|
|
65
|
+
busy = true;
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
declareSpawnsOn(attempt.acquired);
|
|
69
|
+
return {
|
|
70
|
+
acquired: true,
|
|
71
|
+
path,
|
|
72
|
+
index,
|
|
73
|
+
slot: {
|
|
74
|
+
pid: attempt.acquired.owner.pid,
|
|
75
|
+
index,
|
|
76
|
+
projectRoot: root,
|
|
77
|
+
startedAt: attempt.acquired.startedAt,
|
|
78
|
+
logFile
|
|
79
|
+
},
|
|
80
|
+
claim: attempt.acquired
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (refusal && !busy) throw refusal;
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
function slotWaitingLine({ max, elapsedMs }) {
|
|
87
|
+
return `${"build".padEnd(11)} waiting for a build slot (all ${max} in use, ${formatElapsed(elapsedMs)} elapsed) -- stim guide lifecycle concurrency`;
|
|
88
|
+
}
|
|
89
|
+
async function acquireBuildSlot({ max, root = null, logFile = null, now = Date.now, out = () => {}, sleep = sleepAsync, intervalMs = SLOT_POLL_MS, progressMs = SLOT_PROGRESS_MS, ceilingMs = SLOT_CEILING_MS }) {
|
|
90
|
+
if (!max || max <= 0) return {
|
|
91
|
+
acquired: true,
|
|
92
|
+
unlimited: true
|
|
93
|
+
};
|
|
94
|
+
const started = now();
|
|
95
|
+
let lastProgress = started;
|
|
96
|
+
for (;;) {
|
|
97
|
+
const got = tryAcquireBuildSlot({
|
|
98
|
+
max,
|
|
99
|
+
root,
|
|
100
|
+
logFile
|
|
101
|
+
});
|
|
102
|
+
if (got) return got;
|
|
103
|
+
const elapsed = now() - started;
|
|
104
|
+
if (elapsed >= ceilingMs) {
|
|
105
|
+
const err = /* @__PURE__ */ new Error(`Waited ${formatElapsed(elapsed)} for one of ${max} build slots, and every slot is held by a process that is still running, or by a holder Stim cannot identify. Slots live under ` + buildSlotsDir() + "; remove a slot directory whose builder is not really building, or raise concurrency.maxBuilds.");
|
|
106
|
+
err.code = "STIM_BUILD_SLOT_TIMEOUT";
|
|
107
|
+
throw err;
|
|
108
|
+
}
|
|
109
|
+
if (now() - lastProgress >= progressMs) {
|
|
110
|
+
lastProgress = now();
|
|
111
|
+
out(slotWaitingLine({
|
|
112
|
+
max,
|
|
113
|
+
elapsedMs: elapsed
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
await sleep(intervalMs);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function releaseBuildSlot(handle) {
|
|
120
|
+
if (!handle || handle.unlimited) return false;
|
|
121
|
+
stopDeclaringSpawnsOn(handle.claim);
|
|
122
|
+
return (0, ownership_claim_exports.releaseClaim)(handle.claim);
|
|
123
|
+
}
|
|
124
|
+
function listBuildSlots() {
|
|
125
|
+
const dir = buildSlotsDir();
|
|
126
|
+
let names;
|
|
127
|
+
try {
|
|
128
|
+
names = readdirSync(dir);
|
|
129
|
+
} catch {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
const slots = [];
|
|
133
|
+
for (const name of names) {
|
|
134
|
+
if (!name.startsWith(SLOT_PREFIX)) continue;
|
|
135
|
+
const path = join$1(dir, name);
|
|
136
|
+
try {
|
|
137
|
+
if (!statSync(path).isDirectory()) continue;
|
|
138
|
+
} catch {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
const survey = (0, ownership_claim_exports.readClaimSet)(path);
|
|
142
|
+
const holder = survey.live[0] ?? survey.dead[0];
|
|
143
|
+
const info = holder ? toRecord(holder) : null;
|
|
144
|
+
const index = Number(name.slice(5));
|
|
145
|
+
slots.push({
|
|
146
|
+
path,
|
|
147
|
+
name,
|
|
148
|
+
index: Number.isFinite(index) ? index : null,
|
|
149
|
+
pid: info?.pid ?? null,
|
|
150
|
+
projectRoot: info?.projectRoot ?? null,
|
|
151
|
+
startedAt: info?.startedAt ?? null,
|
|
152
|
+
logFile: info?.logFile ?? null,
|
|
153
|
+
alive: survey.live.length > 0,
|
|
154
|
+
unresolved: survey.unresolved.length > 0
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return slots;
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/commands/gc/paths.ts
|
|
161
|
+
function canonicalPath(p) {
|
|
162
|
+
try {
|
|
163
|
+
return realpathSync$1(p);
|
|
164
|
+
} catch {
|
|
165
|
+
return resolve(p);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/workspace/in-use.ts
|
|
170
|
+
const NATIVE_RUN = "native-run";
|
|
171
|
+
const NATIVE_RUN_HELD = "a stim ios, android or stop run holds its native-run.lock";
|
|
172
|
+
function workspaceInUse(root, { supervisor = true, managedLocks = true, nativeRun = true } = {}) {
|
|
173
|
+
const reasons = [];
|
|
174
|
+
if (supervisor) {
|
|
175
|
+
const project = getProject(root);
|
|
176
|
+
const target = resolveSupervisorTarget({
|
|
177
|
+
state: readWorkspaceState(root)?.supervisor,
|
|
178
|
+
record: project?.supervisor,
|
|
179
|
+
reservedPort: project?.metroPort
|
|
180
|
+
});
|
|
181
|
+
if (target.status === "ours") reasons.push(`its dev server supervisor (pid ${target.pid}) is running`);
|
|
182
|
+
else if (target.status === "unverified") reasons.push(`its dev server supervisor cannot be verified: ${target.reason ?? "unknown identity"}`);
|
|
183
|
+
}
|
|
184
|
+
if (nativeRun) {
|
|
185
|
+
const claims = (0, ownership_claim_exports.readClaimSet)(workspaceProcessLockPath(workspaceDir(root), NATIVE_RUN, true));
|
|
186
|
+
if (claims.live.length) reasons.push(NATIVE_RUN_HELD);
|
|
187
|
+
else if (claims.unresolved[0]) reasons.push(`its native-run.lock cannot be resolved: ${claims.unresolved[0].reason}`);
|
|
188
|
+
}
|
|
189
|
+
const self = canonicalPath(root);
|
|
190
|
+
for (const [kind, entries] of [["build lock", listBuildLocks()], ["build slot", listBuildSlots()]]) for (const entry of entries) {
|
|
191
|
+
if (!entry.alive && !entry.unresolved) continue;
|
|
192
|
+
if (entry.projectRoot !== null && canonicalPath(entry.projectRoot) === self) reasons.push(`a ${entry.alive ? "live" : "unresolvable"} ${kind} at ${entry.path} names it`);
|
|
193
|
+
}
|
|
194
|
+
if (managedLocks) reasons.push(...managedLockHolders(root));
|
|
195
|
+
return reasons;
|
|
196
|
+
}
|
|
197
|
+
function emptyWorkspaceDir(dir) {
|
|
198
|
+
if (lstatSync(dir).isSymbolicLink()) {
|
|
199
|
+
unlinkSync(dir);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
for (const name of readdirSync(dir)) if (name !== `${NATIVE_RUN}.lock`) rmSync(join$1(dir, name), {
|
|
203
|
+
recursive: true,
|
|
204
|
+
force: true
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
async function withIdleWorkspace(root, fn, { purpose, ...checks }) {
|
|
208
|
+
try {
|
|
209
|
+
return await withWorkspaceProcessLock(workspaceDir(root), NATIVE_RUN, async () => {
|
|
210
|
+
const reasons = workspaceInUse(root, {
|
|
211
|
+
...checks,
|
|
212
|
+
nativeRun: false
|
|
213
|
+
});
|
|
214
|
+
if (reasons.length) return {
|
|
215
|
+
ran: false,
|
|
216
|
+
reasons
|
|
217
|
+
};
|
|
218
|
+
return {
|
|
219
|
+
ran: true,
|
|
220
|
+
value: await fn()
|
|
221
|
+
};
|
|
222
|
+
}, {
|
|
223
|
+
external: true,
|
|
224
|
+
waitMs: 0,
|
|
225
|
+
ownerPurpose: purpose
|
|
226
|
+
});
|
|
227
|
+
} catch (error) {
|
|
228
|
+
if (workspaceProcessLockError(error)) return {
|
|
229
|
+
ran: false,
|
|
230
|
+
reasons: [NATIVE_RUN_HELD]
|
|
231
|
+
};
|
|
232
|
+
if ((0, ownership_claim_exports.isClaimRefusal)(error) || (0, ownership_claim_exports.isClaimUnavailable)(error)) return {
|
|
233
|
+
ran: false,
|
|
234
|
+
reasons: [`its native-run.lock cannot be taken: ${error.reason}`]
|
|
235
|
+
};
|
|
236
|
+
throw error;
|
|
237
|
+
} finally {
|
|
238
|
+
try {
|
|
239
|
+
const dir = workspaceDir(root);
|
|
240
|
+
if (!lstatSync(dir).isSymbolicLink()) rmdirSync(dir);
|
|
241
|
+
} catch {}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
//#endregion
|
|
245
|
+
export { acquireBuildSlot as a, canonicalPath as i, withIdleWorkspace as n, listBuildSlots as o, workspaceInUse as r, releaseBuildSlot as s, emptyWorkspaceDir as t };
|