stim 1.8.0 → 1.10.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 +23 -155
- package/dist/{android-DWssqHQ8.mjs → android-787HhP5w.mjs} +168 -39
- package/dist/{android-BRgBYzU0.mjs → android-DBkeHwpm.mjs} +250 -200
- package/dist/{android-DeYdjjBP.mjs → android-DPov-fv2.mjs} +2 -2
- package/dist/{android-cas-SWIjzeuL.mjs → android-cas-CITmIeWY.mjs} +22 -14
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/app-install-CrJ6t_bv.mjs +819 -0
- package/dist/budget-BP4jZZL4.mjs +620 -0
- package/dist/build-progress-BXx-ZBe4.mjs +512 -0
- package/dist/{cache-manifest-CYJi8-Hx.mjs → cache-manifest-Pubfp8wV.mjs} +4 -4
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cli.mjs +22 -19
- package/dist/collector-run.d.mts +7 -1
- package/dist/collector-run.mjs +10 -9
- package/dist/{command-output-B3ycpsYv.mjs → command-output-D5z2_bcF.mjs} +11 -2
- package/dist/{config-B7iyKWkL.mjs → config-BEq0Qw_a.mjs} +134 -121
- package/dist/{dependency-state-D0M7oKA7.mjs → dependency-state-r9UesA9a.mjs} +2 -2
- package/dist/{deps-oCXVNEqQ.mjs → deps-DP4eWqea.mjs} +8 -5
- package/dist/{detached-entry-Bpo6ZY_I.mjs → detached-entry-DJHoca49.mjs} +1 -1
- package/dist/{device-BUoZyMoR.mjs → dev-client-92U593KT.mjs} +1090 -660
- package/dist/{device-RdujjHLB.mjs → device-DhTZe7L4.mjs} +13 -20
- package/dist/{build-lock-CgtsB6TV.mjs → device-lease-CbEaIk7b.mjs} +481 -27
- package/dist/{device-pool-BxRwdufO.mjs → device-pool-Di8Q-9Ro.mjs} +7 -7
- package/dist/{device-remote-ByN8P6CB.mjs → device-remote-Dy1jGj3U.mjs} +645 -119
- package/dist/{doctor-BMt156qe.mjs → doctor-B-inAJmt.mjs} +137 -113
- package/dist/{error-diagnostics-D1Kaf-eT.mjs → error-diagnostics-xxWKNO3-.mjs} +22 -17
- package/dist/exec-DK8KaVHK.mjs +133 -0
- package/dist/{gc-v2YcoJvm.mjs → gc-ChPFZMRC.mjs} +836 -813
- package/dist/{guide-BTAlD1_y.mjs → guide-DwmaD_Wa.mjs} +953 -209
- package/dist/guide-status-CWvti2Lg.mjs +125 -0
- package/dist/idle-DpRLp5lj.mjs +157 -0
- package/dist/{build-slots-C-oAHRT1.mjs → in-use-CjUluqIX.mjs} +102 -8
- package/dist/{ios-BcCiI4TS.mjs → ios-B2Fpqvlb.mjs} +60 -71
- package/dist/{ios-BP04pY5f.mjs → ios-Dd-hYJBh.mjs} +241 -234
- package/dist/{ios-device-CFXA46ez.mjs → ios-device-BiOsSVzd.mjs} +3 -2
- package/dist/{ios-device-CauFJiYx.mjs → ios-device-CtRdEVjx.mjs} +149 -52
- package/dist/launch-verify-CQS_Kg-C.mjs +365 -0
- package/dist/{logs-CakdBTtm.mjs → logs-MARIy6m9.mjs} +20 -15
- package/dist/metro-BWLOJ_Wr.mjs +409 -0
- package/dist/{named-ports-DzMOlmaX.mjs → named-ports-DiTfSz6p.mjs} +4 -4
- package/dist/{ndjson-BZwI1NBv.mjs → ndjson-CDY9IkVC.mjs} +23 -43
- package/dist/{ndjson-DvHSsNU9.d.mts → ndjson-Du3j0AQ9.d.mts} +1 -0
- package/dist/{ownership-Cp52QAK1.mjs → ownership-D2xbLVoz.mjs} +2 -3
- package/dist/{teardown-BJqEzPo_.mjs → ownership-DZjdx8Gq.mjs} +664 -612
- package/dist/{ports-BIbJaKNW.mjs → ports-CLSSFOuz.mjs} +3 -3
- package/dist/{process-output-Dl5uKE03.mjs → process-output-Fpl8PYi8.mjs} +1 -1
- package/dist/{project-DDZWf_16.mjs → project-BXZzOABt.mjs} +8 -3
- package/dist/{reload-BdypPbjM.mjs → reload-DoETJ3rl.mjs} +11 -13
- package/dist/{server-bare-BQkyxa_b.mjs → server-bare-DVbb7kXH.mjs} +2 -2
- package/dist/{server-expo-BVAA1OqJ.mjs → server-expo-BvOCeakp.mjs} +8 -7
- package/dist/server-expo-DBVTdult.mjs +2 -0
- package/dist/{settings-BXXorVM2.mjs → settings-DOugPlPz.mjs} +59 -159
- package/dist/settings-EWYNwe72.mjs +378 -0
- package/dist/settings.schema.json +1064 -0
- package/dist/simslim-Ca3487H6.mjs +436 -0
- package/dist/{slot-launch-ZUUe0TQP.mjs → slot-launch-C5cg0fmk.mjs} +4 -4
- package/dist/spawn-claims-CUleC2cM.mjs +49 -0
- package/dist/{start-CmxtOKjU.mjs → start-F9gv5dcC.mjs} +159 -114
- package/dist/start-VoFsPSes.mjs +2 -0
- package/dist/{state-DZobzxLW.mjs → state-WGdIbi_d.mjs} +5 -3
- package/dist/{stats-BYcZ6n1h.mjs → stats-BAgJaLA4.mjs} +66 -5
- package/dist/{stats-CLg5RuDS.mjs → stats-DBYJ5bxL.mjs} +4 -4
- package/dist/status-DLCD-o3s.mjs +764 -0
- package/dist/status-Dr4fRkym.mjs +216 -0
- package/dist/{guide-status-Craq9z0t.mjs → stim-installations-CINXy_su.mjs} +3 -122
- package/dist/{stop-C8JW6kKp.mjs → stop-Dj4pfmyv.mjs} +111 -31
- package/dist/{stop-B1kOpYei.mjs → stop-ZvOeTmKb.mjs} +2 -2
- package/dist/supervisor-run.d.mts +11 -2
- package/dist/supervisor-run.mjs +228 -42
- package/dist/{workspace-process-lock-BYfZz_S-.mjs → workspace-process-lock-BXA96myD.mjs} +14 -9
- package/dist/workspaces-BTUyFfkv.mjs +1003 -0
- package/dist/worktree-4aZg2NnI.mjs +2 -0
- package/dist/{worktree-CFLA-6MX.mjs → worktree-BMS7GeqB.mjs} +523 -61
- package/dist/{worktree-FG_ZKKdb.mjs → worktree-BlizKDpZ.mjs} +200 -54
- package/dist/{xcode-B6YaWeg0.mjs → xcode-B2tBkf0m.mjs} +66 -57
- package/package.json +6 -6
- package/skill/SKILL.md +2 -0
- package/dist/app-install-BDbsSgCl.mjs +0 -1175
- package/dist/dev-client-BmT-ioFI.mjs +0 -797
- package/dist/device-lease-Ffz_M5t0.mjs +0 -359
- package/dist/exec-B29XNuhP.mjs +0 -77
- package/dist/logs-query-BGL-Pl12.mjs +0 -290
- package/dist/metro-C7bcJ0uo.mjs +0 -233
- package/dist/ownership-wolE-0Do.mjs +0 -470
- package/dist/public-http-probe-CyMDrQ3c.mjs +0 -61
- package/dist/reclaim-KJ-0uWEF.mjs +0 -361
- package/dist/server-expo-YDDG3eP7.mjs +0 -2
- package/dist/spawn-entry-nCE6m770.mjs +0 -14
- package/dist/status-DdkLki47.mjs +0 -426
- package/dist/workspace-state-BT5E3SA2.mjs +0 -146
- package/dist/{metro-store-CDDZHtPl.mjs → metro-store-DzeWkfqW.mjs} +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { d as waitForDevice, s as parseDeviceWait, t as selectFromPool } from "./device-pool-
|
|
7
|
-
import { c as listIosDevices, o as
|
|
1
|
+
import { V as parseDeviceSlotOption } from "./config-BEq0Qw_a.mjs";
|
|
2
|
+
import { o as findProjectRoot, t as NO_PROJECT_REFUSAL } from "./project-BXZzOABt.mjs";
|
|
3
|
+
import { r as clockTime } from "./command-output-D5z2_bcF.mjs";
|
|
4
|
+
import { n as fileLeaseIo, o as parseLeaseDuration, p as takeLease, u as releaseWorkspaceLeases } from "./device-lease-CbEaIk7b.mjs";
|
|
5
|
+
import { B as resolvePhysicalDevice, F as physicalDeviceModel, L as probeEmulatorSerial, O as memoizeEmulatorProbe, a as androidPoolNoCandidatesRefusal, i as androidPoolCandidates, w as listAdbDevices } from "./android-787HhP5w.mjs";
|
|
6
|
+
import { d as waitForDevice, s as parseDeviceWait, t as selectFromPool } from "./device-pool-Di8Q-9Ro.mjs";
|
|
7
|
+
import { a as iosPoolCandidates, c as listIosDevices, o as iosPoolNoCandidatesRefusal, s as isWirelessIosDevice, u as resolveIosPhysicalDevice } from "./ios-device-CtRdEVjx.mjs";
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
//#region src/commands/device.ts
|
|
10
10
|
const DEFAULT_FOR = "5m";
|
|
@@ -69,7 +69,8 @@ async function poolDevice(platform, idLabel, waitSeconds, deadline, root, d, slo
|
|
|
69
69
|
idLabel,
|
|
70
70
|
list: () => platform === "ios" ? iosPoolCandidates(d.listIosDevices()).map((entry) => ({
|
|
71
71
|
id: entry.udid,
|
|
72
|
-
name: entry.name
|
|
72
|
+
name: entry.name,
|
|
73
|
+
fallback: isWirelessIosDevice(entry)
|
|
73
74
|
})) : androidPoolCandidates(d.listAdbDevices(), isEmulator).map((entry) => ({ id: entry.serial })),
|
|
74
75
|
noCandidates: () => {
|
|
75
76
|
const resolved = platform === "ios" ? iosPoolNoCandidatesRefusal(d.listIosDevices()) : androidPoolNoCandidatesRefusal(d.listAdbDevices(), isEmulator);
|
|
@@ -121,11 +122,7 @@ async function runLock(platformArg, idArg, opts = {}, overrides = {}) {
|
|
|
121
122
|
remedy: "Run `stim device lock ios` or `stim device lock android`."
|
|
122
123
|
});
|
|
123
124
|
const root = d.findProjectRoot(process.cwd());
|
|
124
|
-
if (!root) return report(
|
|
125
|
-
code: "STIM_NO_PROJECT",
|
|
126
|
-
message: "Not in a React Native project (no package.json found).",
|
|
127
|
-
remedy: "Run this from the app directory -- the one holding package.json."
|
|
128
|
-
});
|
|
125
|
+
if (!root) return report(NO_PROJECT_REFUSAL);
|
|
129
126
|
const durationText = String(opts.for ?? DEFAULT_FOR).trim();
|
|
130
127
|
const duration = parseLeaseDuration(durationText);
|
|
131
128
|
if ("error" in duration) return report({
|
|
@@ -224,11 +221,7 @@ async function runUnlock(platformArg, opts = {}, overrides = {}) {
|
|
|
224
221
|
remedy: "Run `stim device unlock` for every lease this workspace holds, or name one platform."
|
|
225
222
|
});
|
|
226
223
|
const root = d.findProjectRoot(process.cwd());
|
|
227
|
-
if (!root) return report(
|
|
228
|
-
code: "STIM_NO_PROJECT",
|
|
229
|
-
message: "Not in a React Native project (no package.json found).",
|
|
230
|
-
remedy: "Run this from the app directory -- the one holding package.json."
|
|
231
|
-
});
|
|
224
|
+
if (!root) return report(NO_PROJECT_REFUSAL);
|
|
232
225
|
const released = d.releaseLeases(root, {
|
|
233
226
|
platform,
|
|
234
227
|
...opts.slot ? { slot: opts.slot } : {}
|
|
@@ -243,10 +236,10 @@ async function runUnlock(platformArg, opts = {}, overrides = {}) {
|
|
|
243
236
|
}
|
|
244
237
|
function registerDevice(program, deps = {}) {
|
|
245
238
|
const device = program.command("device").description("Lease a connected physical device to this workspace, and give it back");
|
|
246
|
-
device.command("lock").argument("<platform>", "ios or android").argument("[id]", "the UDID or serial to lease; without one, the first free connected device is used").description("Lease a connected physical device to this workspace for a declared time, so another workspace's `--device` run waits instead of installing over it. Nothing but this command and a `--device` run moves the expiry.").option("--for <duration>", `How long to hold it: a whole number of seconds or minutes from 10s to 30m (default ${DEFAULT_FOR})`).option("--wait <seconds>", "How long to wait for a device another workspace holds, before refusing with STIM_DEVICE_BUSY (default 60, 0 refuses at once)").option("--slot <name>", "Use this workspace device slot",
|
|
239
|
+
device.command("lock").argument("<platform>", "ios or android").argument("[id]", "the UDID or serial to lease; without one, the first free connected device is used").description("Lease a connected physical device to this workspace for a declared time, so another workspace's `--device` run waits instead of installing over it. Nothing but this command and a `--device` run moves the expiry.").option("--for <duration>", `How long to hold it: a whole number of seconds or minutes from 10s to 30m (default ${DEFAULT_FOR})`).option("--wait <seconds>", "How long to wait for a device another workspace holds, before refusing with STIM_DEVICE_BUSY (default 60, 0 refuses at once)").option("--slot <name>", "Use this workspace device slot", parseDeviceSlotOption).option("--json", "print the lease as JSON on stdout").action(async (platform, id, opts) => {
|
|
247
240
|
if ("code" in await runLock(platform, id, opts, deps)) process.exit(1);
|
|
248
241
|
});
|
|
249
|
-
device.command("unlock").argument("[platform]", "ios or android; without one, every lease this workspace holds is released").description("Release the device lease or leases this workspace holds. Releasing nothing is not an error.").option("--slot <name>", "Use this workspace device slot",
|
|
242
|
+
device.command("unlock").argument("[platform]", "ios or android; without one, every lease this workspace holds is released").description("Release the device lease or leases this workspace holds. Releasing nothing is not an error.").option("--slot <name>", "Use this workspace device slot", parseDeviceSlotOption).option("--json", "print the released leases as JSON on stdout").action(async (platform, opts) => {
|
|
250
243
|
const result = await runUnlock(platform, opts, deps);
|
|
251
244
|
if (!Array.isArray(result)) process.exit(1);
|
|
252
245
|
});
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { r as register } from "./cache-manifest-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { join } from "node:path";
|
|
1
|
+
import { B as parseDeviceSlotKey, R as deviceSlotKey, W as validateDeviceSlot, k as ownership_claim_exports, u as getConfigDir } from "./config-BEq0Qw_a.mjs";
|
|
2
|
+
import { t as getExecutor } from "./exec-DK8KaVHK.mjs";
|
|
3
|
+
import { r as register } from "./cache-manifest-Pubfp8wV.mjs";
|
|
4
|
+
import { P as withDirLock, S as sharedBuildCache, _ as withWorkspaceStateLock, m as readWorkspaceState, p as clearWorkspaceStateKeys, y as writeWorkspaceState } from "./metro-BWLOJ_Wr.mjs";
|
|
5
|
+
import { r as stopDeclaringSpawnsOn, t as declareSpawnsOn } from "./spawn-claims-CUleC2cM.mjs";
|
|
6
|
+
import { a as formatElapsed, i as formatDuration, m as shortHash } from "./command-output-D5z2_bcF.mjs";
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
9
8
|
import { existsSync as existsSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, rmSync as rmSync$1, writeFileSync as writeFileSync$1 } from "fs";
|
|
10
|
-
import { join
|
|
9
|
+
import { join } from "path";
|
|
11
10
|
import { buildCacheKey, resolveArtifact, storeArtifact } from "@stim-cli/core";
|
|
11
|
+
import { join as join$1 } from "node:path";
|
|
12
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
12
13
|
import * as expoFingerprint from "@expo/fingerprint";
|
|
13
14
|
import { buildUploadTimeoutMs } from "@stim-cli/cache";
|
|
14
15
|
const ASSET_MANIFEST_FILE = "assets-manifest.json";
|
|
@@ -34,7 +35,7 @@ function readAssetManifest(root) {
|
|
|
34
35
|
return;
|
|
35
36
|
}
|
|
36
37
|
for (const name of names) {
|
|
37
|
-
const child = join(abs, name);
|
|
38
|
+
const child = join$1(abs, name);
|
|
38
39
|
let stat;
|
|
39
40
|
try {
|
|
40
41
|
stat = statSync(child);
|
|
@@ -106,16 +107,16 @@ function pickGeneratedResDir(names, variant, reactChildren = []) {
|
|
|
106
107
|
return null;
|
|
107
108
|
}
|
|
108
109
|
function androidModuleDirs(root, { list = safeList } = {}) {
|
|
109
|
-
const android = join(root, "android");
|
|
110
|
-
return ["app", ...list(android).filter((name) => name !== "app" && existsSync(join(android, name, "build", "generated", "res"))).toSorted()];
|
|
110
|
+
const android = join$1(root, "android");
|
|
111
|
+
return ["app", ...list(android).filter((name) => name !== "app" && existsSync(join$1(android, name, "build", "generated", "res"))).toSorted()];
|
|
111
112
|
}
|
|
112
113
|
function findGeneratedResDir(root, variant, { list = safeList } = {}) {
|
|
113
114
|
for (const module of androidModuleDirs(root, { list })) {
|
|
114
|
-
const base = join(root, "android", module, "build", "generated", "res");
|
|
115
|
+
const base = join$1(root, "android", module, "build", "generated", "res");
|
|
115
116
|
const names = list(base);
|
|
116
117
|
if (names.length === 0) continue;
|
|
117
|
-
const picked = pickGeneratedResDir(names, variant, names.includes("react") ? list(join(base, "react")) : []);
|
|
118
|
-
if (picked) return join(base, ...picked.split("/"));
|
|
118
|
+
const picked = pickGeneratedResDir(names, variant, names.includes("react") ? list(join$1(base, "react")) : []);
|
|
119
|
+
if (picked) return join$1(base, ...picked.split("/"));
|
|
119
120
|
}
|
|
120
121
|
return null;
|
|
121
122
|
}
|
|
@@ -157,7 +158,7 @@ function safeList(dir) {
|
|
|
157
158
|
//#endregion
|
|
158
159
|
//#region src/cache/build-cache.ts
|
|
159
160
|
function entryDir(platform, key, root = sharedBuildCache()) {
|
|
160
|
-
return join
|
|
161
|
+
return join(root, platform, key);
|
|
161
162
|
}
|
|
162
163
|
const FINGERPRINT_PLATFORMS = /* @__PURE__ */ new Set(["ios", "android"]);
|
|
163
164
|
/**
|
|
@@ -209,17 +210,17 @@ function storeBuild(platform, key, buildPath, rootOrOptions = {}) {
|
|
|
209
210
|
overwrite: Boolean(options.overwrite),
|
|
210
211
|
writeMetadata: (staging) => {
|
|
211
212
|
if (Array.isArray(options.sources)) try {
|
|
212
|
-
writeFileSync$1(join
|
|
213
|
+
writeFileSync$1(join(staging, SOURCES_FILE), JSON.stringify(options.sources));
|
|
213
214
|
} catch {}
|
|
214
215
|
if (options.assetManifest) try {
|
|
215
|
-
writeFileSync$1(join
|
|
216
|
+
writeFileSync$1(join(staging, ASSET_MANIFEST_FILE), JSON.stringify(options.assetManifest));
|
|
216
217
|
} catch {}
|
|
217
218
|
}
|
|
218
219
|
});
|
|
219
220
|
}
|
|
220
221
|
const PROVIDER_DOWNLOAD_DIR = "cache-provider";
|
|
221
222
|
function providerDownloadPath(workspacePath) {
|
|
222
|
-
return join
|
|
223
|
+
return join(workspacePath, PROVIDER_DOWNLOAD_DIR);
|
|
223
224
|
}
|
|
224
225
|
/**
|
|
225
226
|
* Empties and registers the scratch directory a provider downloads into. It is
|
|
@@ -275,7 +276,7 @@ function providerUploadOutcome(result, name) {
|
|
|
275
276
|
const SOURCES_FILE = "fingerprint-sources.json";
|
|
276
277
|
function storedSources(platform, key, root = sharedBuildCache()) {
|
|
277
278
|
try {
|
|
278
|
-
const parsed = JSON.parse(readFileSync$1(join
|
|
279
|
+
const parsed = JSON.parse(readFileSync$1(join(entryDir(platform, key, root), SOURCES_FILE), "utf-8"));
|
|
279
280
|
return Array.isArray(parsed) ? parsed : null;
|
|
280
281
|
} catch {
|
|
281
282
|
return null;
|
|
@@ -283,7 +284,7 @@ function storedSources(platform, key, root = sharedBuildCache()) {
|
|
|
283
284
|
}
|
|
284
285
|
function storedAssetManifest(platform, key, root = sharedBuildCache()) {
|
|
285
286
|
try {
|
|
286
|
-
return parseAssetManifest(readFileSync$1(join
|
|
287
|
+
return parseAssetManifest(readFileSync$1(join(entryDir(platform, key, root), ASSET_MANIFEST_FILE), "utf-8"));
|
|
287
288
|
} catch {
|
|
288
289
|
return null;
|
|
289
290
|
}
|
|
@@ -424,7 +425,7 @@ function fingerprintDiffRecord({ changed, previousHash, hash }) {
|
|
|
424
425
|
}
|
|
425
426
|
//#endregion
|
|
426
427
|
//#region src/engine/build-lock.ts
|
|
427
|
-
const LOCK_SUFFIX = ".lock";
|
|
428
|
+
const LOCK_SUFFIX$1 = ".lock";
|
|
428
429
|
const WAIT_POLL_MS = 1e3;
|
|
429
430
|
const WAIT_PROGRESS_MS = 3e4;
|
|
430
431
|
const WAIT_CEILING_MS = 54e5;
|
|
@@ -435,10 +436,10 @@ function segment(value) {
|
|
|
435
436
|
return String(value).replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^\.+/, "") || "unknown";
|
|
436
437
|
}
|
|
437
438
|
function buildLocksDir() {
|
|
438
|
-
return join(getConfigDir(), "build-locks");
|
|
439
|
+
return join$1(getConfigDir(), "build-locks");
|
|
439
440
|
}
|
|
440
441
|
function buildLockPath(platform, key) {
|
|
441
|
-
return join(buildLocksDir(), `${segment(platform)}-${segment(key)}${LOCK_SUFFIX}`);
|
|
442
|
+
return join$1(buildLocksDir(), `${segment(platform)}-${segment(key)}${LOCK_SUFFIX$1}`);
|
|
442
443
|
}
|
|
443
444
|
function label(platform) {
|
|
444
445
|
return `${platform} build`;
|
|
@@ -482,6 +483,7 @@ function acquireBuildLock({ platform, key, root = null, logFile = null }) {
|
|
|
482
483
|
},
|
|
483
484
|
claim: attempt.acquired
|
|
484
485
|
};
|
|
486
|
+
declareSpawnsOn(attempt.acquired);
|
|
485
487
|
return reaped ? {
|
|
486
488
|
...handle,
|
|
487
489
|
tookOver: toRecord(reaped)
|
|
@@ -495,6 +497,7 @@ function acquireBuildLock({ platform, key, root = null, logFile = null }) {
|
|
|
495
497
|
} : { path };
|
|
496
498
|
}
|
|
497
499
|
function releaseBuildLock(handle) {
|
|
500
|
+
stopDeclaringSpawnsOn(handle?.claim);
|
|
498
501
|
return (0, ownership_claim_exports.releaseClaim)(handle?.claim);
|
|
499
502
|
}
|
|
500
503
|
function listBuildLocks() {
|
|
@@ -507,8 +510,8 @@ function listBuildLocks() {
|
|
|
507
510
|
}
|
|
508
511
|
const locks = [];
|
|
509
512
|
for (const name of names) {
|
|
510
|
-
if (!name.endsWith(LOCK_SUFFIX)) continue;
|
|
511
|
-
const path = join(dir, name);
|
|
513
|
+
if (!name.endsWith(LOCK_SUFFIX$1)) continue;
|
|
514
|
+
const path = join$1(dir, name);
|
|
512
515
|
try {
|
|
513
516
|
if (!statSync(path).isDirectory()) continue;
|
|
514
517
|
} catch {
|
|
@@ -600,5 +603,456 @@ async function waitForBuild({ platform, key, resolve = resolveBuild, intervalMs
|
|
|
600
603
|
await sleep(intervalMs);
|
|
601
604
|
}
|
|
602
605
|
}
|
|
606
|
+
async function waitForSharedBuild({ platform, key, fingerprint, root, logFile, command, acquire, wait, now, phase, warn, out }) {
|
|
607
|
+
const waitStarted = now();
|
|
608
|
+
let failedHolder;
|
|
609
|
+
let released = null;
|
|
610
|
+
for (;;) {
|
|
611
|
+
let attempt = null;
|
|
612
|
+
try {
|
|
613
|
+
attempt = acquire({
|
|
614
|
+
platform,
|
|
615
|
+
key,
|
|
616
|
+
root,
|
|
617
|
+
logFile
|
|
618
|
+
});
|
|
619
|
+
} catch (err) {
|
|
620
|
+
const refusal = (0, ownership_claim_exports.claimFailure)(err, command);
|
|
621
|
+
if (refusal) return { refusal };
|
|
622
|
+
warn(`could not take the build lock: ${err?.message || err}; building anyway`);
|
|
623
|
+
}
|
|
624
|
+
if (attempt?.acquired) {
|
|
625
|
+
const previous = attempt.tookOver ?? failedHolder;
|
|
626
|
+
if (previous) warn(takeoverLine(previous));
|
|
627
|
+
return {
|
|
628
|
+
refusal: null,
|
|
629
|
+
lock: attempt,
|
|
630
|
+
hit: null,
|
|
631
|
+
released
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
if (!attempt?.held) return {
|
|
635
|
+
refusal: null,
|
|
636
|
+
lock: null,
|
|
637
|
+
hit: null,
|
|
638
|
+
released
|
|
639
|
+
};
|
|
640
|
+
released = null;
|
|
641
|
+
const holder = attempt.held;
|
|
642
|
+
const who = holder.projectRoot || "another workspace";
|
|
643
|
+
phase(`${who} is already building ${shortHash(fingerprint)} (pid ${holder.pid})${holder.logFile ? ` -- tail ${holder.logFile}` : ""} -- stim guide lifecycle concurrency`);
|
|
644
|
+
let waited;
|
|
645
|
+
try {
|
|
646
|
+
const ceilingMs = WAIT_CEILING_MS - (now() - waitStarted);
|
|
647
|
+
if (ceilingMs <= 0) throw Object.assign(/* @__PURE__ */ new Error(`Waited ${formatDuration(now() - waitStarted)} for shared builds without an artifact; ${who} (pid ${holder.pid}) holds ${attempt.path}.`), {
|
|
648
|
+
code: "STIM_BUILD_WAIT_TIMEOUT",
|
|
649
|
+
lockPath: attempt.path
|
|
650
|
+
});
|
|
651
|
+
waited = await wait({
|
|
652
|
+
platform,
|
|
653
|
+
key,
|
|
654
|
+
out,
|
|
655
|
+
ceilingMs
|
|
656
|
+
});
|
|
657
|
+
} catch (err) {
|
|
658
|
+
const refusal = (0, ownership_claim_exports.claimFailure)(err, command);
|
|
659
|
+
if (refusal) return { refusal };
|
|
660
|
+
const timeout = err;
|
|
661
|
+
if (timeout?.code !== "STIM_BUILD_WAIT_TIMEOUT") throw err;
|
|
662
|
+
return { refusal: {
|
|
663
|
+
code: "STIM_BUILD_WAIT_TIMEOUT",
|
|
664
|
+
message: timeout.message,
|
|
665
|
+
remedy: `Check pid ${holder.pid}; if it is not really building, remove ${timeout.lockPath} and run \`${command}\` again.`
|
|
666
|
+
} };
|
|
667
|
+
}
|
|
668
|
+
if (waited.hit) {
|
|
669
|
+
phase(`waited ${formatDuration(waited.waitedMs)} for ${who}'s build -> installed from cache -- stim guide lifecycle concurrency`);
|
|
670
|
+
return {
|
|
671
|
+
refusal: null,
|
|
672
|
+
lock: null,
|
|
673
|
+
hit: {
|
|
674
|
+
path: waited.hit,
|
|
675
|
+
waited: {
|
|
676
|
+
pid: holder.pid,
|
|
677
|
+
ms: waited.waitedMs
|
|
678
|
+
}
|
|
679
|
+
},
|
|
680
|
+
released
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
if (waited.lockReleased) {
|
|
684
|
+
failedHolder = void 0;
|
|
685
|
+
released = {
|
|
686
|
+
facts: {
|
|
687
|
+
pid: holder.pid,
|
|
688
|
+
ms: waited.waitedMs
|
|
689
|
+
},
|
|
690
|
+
who
|
|
691
|
+
};
|
|
692
|
+
phase(`${who}'s build lock was released; rechecking this workspace before building`);
|
|
693
|
+
} else {
|
|
694
|
+
warn(`${who}'s build ended without an artifact (${waited.builderFailed}); retrying the build lock`);
|
|
695
|
+
failedHolder = holder;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
const DEFAULT_LEASE_MS = 3e5;
|
|
700
|
+
const LEASE_SUFFIX = ".json";
|
|
701
|
+
const LOCK_SUFFIX = ".lock";
|
|
702
|
+
const DURATION = /^[1-9][0-9]*(s|m)$/;
|
|
703
|
+
function deviceLocksDir() {
|
|
704
|
+
return join$1(getConfigDir(), "device-locks");
|
|
705
|
+
}
|
|
706
|
+
function leaseStem(platform, id) {
|
|
707
|
+
return `${segment(platform)}-${segment(id)}`;
|
|
708
|
+
}
|
|
709
|
+
function deviceLeasePath(platform, id) {
|
|
710
|
+
return join$1(deviceLocksDir(), `${leaseStem(platform, id)}${LEASE_SUFFIX}`);
|
|
711
|
+
}
|
|
712
|
+
function deviceLeaseLockPath(platform, id) {
|
|
713
|
+
return join$1(deviceLocksDir(), `${leaseStem(platform, id)}${LOCK_SUFFIX}`);
|
|
714
|
+
}
|
|
715
|
+
const fileLeaseIo = {
|
|
716
|
+
withHolderLock: withWorkspaceStateLock,
|
|
717
|
+
now: () => Date.now(),
|
|
718
|
+
readLease(path) {
|
|
719
|
+
try {
|
|
720
|
+
return readFileSync(path, "utf-8");
|
|
721
|
+
} catch {
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
writeLease(path, body) {
|
|
726
|
+
mkdirSync(deviceLocksDir(), { recursive: true });
|
|
727
|
+
const tmp = `${path}.tmp-${process.pid}-${randomBytes(4).toString("hex")}`;
|
|
728
|
+
writeFileSync(tmp, body);
|
|
729
|
+
try {
|
|
730
|
+
renameSync(tmp, path);
|
|
731
|
+
} catch (error) {
|
|
732
|
+
rmSync(tmp, { force: true });
|
|
733
|
+
throw error;
|
|
734
|
+
}
|
|
735
|
+
},
|
|
736
|
+
removeLease(path) {
|
|
737
|
+
rmSync(path, { force: true });
|
|
738
|
+
},
|
|
739
|
+
listLeaseNames() {
|
|
740
|
+
try {
|
|
741
|
+
return readdirSync(deviceLocksDir());
|
|
742
|
+
} catch {
|
|
743
|
+
return [];
|
|
744
|
+
}
|
|
745
|
+
},
|
|
746
|
+
withLeaseLock(lockPath, fn) {
|
|
747
|
+
return withDirLock(lockPath, fn, { ensureParent: () => mkdirSync(deviceLocksDir(), { recursive: true }) });
|
|
748
|
+
},
|
|
749
|
+
readHolder(root) {
|
|
750
|
+
return parseWorkspaceLeases(readWorkspaceState(root)?.deviceLeases);
|
|
751
|
+
},
|
|
752
|
+
writeHolder(root, leases) {
|
|
753
|
+
if (Object.keys(leases).length === 0) clearWorkspaceStateKeys(root, ["deviceLeases"]);
|
|
754
|
+
else writeWorkspaceState(root, { deviceLeases: leases });
|
|
755
|
+
}
|
|
756
|
+
};
|
|
757
|
+
function text(value) {
|
|
758
|
+
return typeof value === "string" && value.trim() !== "" ? value : null;
|
|
759
|
+
}
|
|
760
|
+
function isPlatform(value) {
|
|
761
|
+
return value === "ios" || value === "android";
|
|
762
|
+
}
|
|
763
|
+
function parseLease(raw) {
|
|
764
|
+
if (typeof raw !== "string") return null;
|
|
765
|
+
let parsed;
|
|
766
|
+
try {
|
|
767
|
+
parsed = JSON.parse(raw);
|
|
768
|
+
} catch {
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
772
|
+
const entry = parsed;
|
|
773
|
+
if (entry.version !== 1 || !isPlatform(entry.platform)) return null;
|
|
774
|
+
const id = text(entry.id);
|
|
775
|
+
const holder = text(entry.holder);
|
|
776
|
+
const token = text(entry.token);
|
|
777
|
+
const expiresAt = text(entry.expiresAt);
|
|
778
|
+
if (!id || !holder || !token || !expiresAt || !Number.isFinite(Date.parse(expiresAt))) return null;
|
|
779
|
+
if (entry.slot !== void 0) {
|
|
780
|
+
if (typeof entry.slot !== "string") return null;
|
|
781
|
+
try {
|
|
782
|
+
validateDeviceSlot(entry.slot);
|
|
783
|
+
} catch {
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
return {
|
|
788
|
+
version: 1,
|
|
789
|
+
...typeof entry.slot === "string" ? { slot: entry.slot } : {},
|
|
790
|
+
platform: entry.platform,
|
|
791
|
+
id,
|
|
792
|
+
deviceName: text(entry.deviceName),
|
|
793
|
+
holder,
|
|
794
|
+
token,
|
|
795
|
+
grantedAt: text(entry.grantedAt),
|
|
796
|
+
expiresAt
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
function parseWorkspaceLeases(value) {
|
|
800
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
801
|
+
const leases = {};
|
|
802
|
+
for (const [platform, entry] of Object.entries(value)) {
|
|
803
|
+
if (!parseDeviceSlotKey(platform) || !entry || typeof entry !== "object") continue;
|
|
804
|
+
const record = entry;
|
|
805
|
+
const id = text(record.id);
|
|
806
|
+
const token = text(record.token);
|
|
807
|
+
if (!id || !token) continue;
|
|
808
|
+
leases[platform] = {
|
|
809
|
+
id,
|
|
810
|
+
token,
|
|
811
|
+
kind: record.kind === "run" ? "run" : "declared"
|
|
812
|
+
};
|
|
813
|
+
}
|
|
814
|
+
return leases;
|
|
815
|
+
}
|
|
816
|
+
function leaseIsExpired(lease, now) {
|
|
817
|
+
return Date.parse(lease.expiresAt) <= now;
|
|
818
|
+
}
|
|
819
|
+
function parseLeaseDuration(value) {
|
|
820
|
+
const raw = String(value ?? "").trim();
|
|
821
|
+
const match = DURATION.exec(raw);
|
|
822
|
+
if (!match) return { error: `--for must be a whole number of seconds or minutes, such as 90s or 5m (got "${raw}").` };
|
|
823
|
+
const ms = Number(raw.slice(0, -1)) * (match[1] === "s" ? 1e3 : 6e4);
|
|
824
|
+
if (ms < 1e4 || ms > 18e5) return { error: `--for must be between 10s and 30m (got "${raw}").` };
|
|
825
|
+
return { ms };
|
|
826
|
+
}
|
|
827
|
+
function listLeaseFiles(io = fileLeaseIo) {
|
|
828
|
+
const dir = deviceLocksDir();
|
|
829
|
+
const entries = [];
|
|
830
|
+
for (const name of io.listLeaseNames().toSorted()) {
|
|
831
|
+
if (!name.endsWith(LEASE_SUFFIX)) continue;
|
|
832
|
+
const path = join$1(dir, name);
|
|
833
|
+
const lease = parseLease(io.readLease(path));
|
|
834
|
+
const stem = name.slice(0, -5);
|
|
835
|
+
const cut = stem.indexOf("-");
|
|
836
|
+
entries.push({
|
|
837
|
+
path,
|
|
838
|
+
name,
|
|
839
|
+
platform: lease?.platform ?? (cut > 0 ? stem.slice(0, cut) : stem),
|
|
840
|
+
id: lease?.id ?? (cut > 0 ? stem.slice(cut + 1) : null),
|
|
841
|
+
lease
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
return entries;
|
|
845
|
+
}
|
|
846
|
+
function writeLease(lease, io) {
|
|
847
|
+
io.writeLease(deviceLeasePath(lease.platform, lease.id), `${JSON.stringify(lease, null, 2)}\n`);
|
|
848
|
+
return lease;
|
|
849
|
+
}
|
|
850
|
+
function takeLease({ root, platform, slot = "default", id, deviceName = null, kind, durationMs = DEFAULT_LEASE_MS }, io = fileLeaseIo) {
|
|
851
|
+
return withHolderLock(io, root, () => {
|
|
852
|
+
const key = deviceSlotKey(platform, slot);
|
|
853
|
+
const previous = io.readHolder(root)[key];
|
|
854
|
+
if (previous && previous.id !== id) releaseHeld(root, platform, previous, io, slot);
|
|
855
|
+
for (const entry of listLeaseFiles(io)) {
|
|
856
|
+
const other = entry.lease;
|
|
857
|
+
if (!other || other.platform !== platform || other.holder !== root || (other.slot ?? "default") !== slot || other.id === id) continue;
|
|
858
|
+
releaseByRoot(root, platform, other.id, io);
|
|
859
|
+
}
|
|
860
|
+
const declared = previous?.kind === "declared" && previous.id === id;
|
|
861
|
+
const path = deviceLeasePath(platform, id);
|
|
862
|
+
const result = io.withLeaseLock(deviceLeaseLockPath(platform, id), () => {
|
|
863
|
+
const now = io.now();
|
|
864
|
+
const raw = io.readLease(path);
|
|
865
|
+
const current = parseLease(raw);
|
|
866
|
+
if (raw !== null && !current) return {
|
|
867
|
+
status: "unreadable",
|
|
868
|
+
path
|
|
869
|
+
};
|
|
870
|
+
const asked = now + durationMs;
|
|
871
|
+
if (current && !leaseIsExpired(current, now)) {
|
|
872
|
+
if (current.holder !== root || (current.slot ?? "default") !== slot) return {
|
|
873
|
+
status: "held",
|
|
874
|
+
lease: current
|
|
875
|
+
};
|
|
876
|
+
const floor = declared && kind === "run" ? Math.max(asked, Date.parse(current.expiresAt)) : asked;
|
|
877
|
+
return {
|
|
878
|
+
status: "set",
|
|
879
|
+
lease: writeLease({
|
|
880
|
+
...current,
|
|
881
|
+
deviceName: deviceName ?? current.deviceName,
|
|
882
|
+
expiresAt: new Date(floor).toISOString()
|
|
883
|
+
}, io)
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
const expiresAt = new Date(asked).toISOString();
|
|
887
|
+
return {
|
|
888
|
+
status: "taken",
|
|
889
|
+
lease: writeLease({
|
|
890
|
+
version: 1,
|
|
891
|
+
...slot === "default" ? {} : { slot },
|
|
892
|
+
platform,
|
|
893
|
+
id,
|
|
894
|
+
deviceName,
|
|
895
|
+
holder: root,
|
|
896
|
+
token: randomBytes(12).toString("hex"),
|
|
897
|
+
grantedAt: new Date(now).toISOString(),
|
|
898
|
+
expiresAt
|
|
899
|
+
}, io)
|
|
900
|
+
};
|
|
901
|
+
});
|
|
902
|
+
if (result.status === "taken" || result.status === "set") {
|
|
903
|
+
const recorded = result.status === "set" && declared ? "declared" : kind;
|
|
904
|
+
io.writeHolder(root, {
|
|
905
|
+
...io.readHolder(root),
|
|
906
|
+
[key]: {
|
|
907
|
+
id,
|
|
908
|
+
token: result.lease.token,
|
|
909
|
+
kind: recorded
|
|
910
|
+
}
|
|
911
|
+
});
|
|
912
|
+
}
|
|
913
|
+
return result;
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
function raiseLease({ root, platform, slot = "default", minMs }, io = fileLeaseIo) {
|
|
917
|
+
const record = io.readHolder(root)[deviceSlotKey(platform, slot)];
|
|
918
|
+
if (!record) return { status: "none" };
|
|
919
|
+
const path = deviceLeasePath(platform, record.id);
|
|
920
|
+
return io.withLeaseLock(deviceLeaseLockPath(platform, record.id), () => {
|
|
921
|
+
const current = parseLease(io.readLease(path));
|
|
922
|
+
if (!current || current.token !== record.token) return {
|
|
923
|
+
status: "lost",
|
|
924
|
+
lease: current
|
|
925
|
+
};
|
|
926
|
+
const floor = io.now() + minMs;
|
|
927
|
+
if (Date.parse(current.expiresAt) >= floor) return {
|
|
928
|
+
status: "raised",
|
|
929
|
+
lease: current
|
|
930
|
+
};
|
|
931
|
+
return {
|
|
932
|
+
status: "raised",
|
|
933
|
+
lease: writeLease({
|
|
934
|
+
...current,
|
|
935
|
+
expiresAt: new Date(floor).toISOString()
|
|
936
|
+
}, io)
|
|
937
|
+
};
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
function summarize(lease) {
|
|
941
|
+
return {
|
|
942
|
+
platform: lease.platform,
|
|
943
|
+
id: lease.id,
|
|
944
|
+
deviceName: lease.deviceName,
|
|
945
|
+
expiresAt: lease.expiresAt
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
function releaseHeld(root, platform, record, io, slot = "default") {
|
|
949
|
+
const key = deviceSlotKey(platform, slot);
|
|
950
|
+
const path = deviceLeasePath(platform, record.id);
|
|
951
|
+
const released = io.withLeaseLock(deviceLeaseLockPath(platform, record.id), () => {
|
|
952
|
+
const current = parseLease(io.readLease(path));
|
|
953
|
+
if (!current || current.token !== record.token) return null;
|
|
954
|
+
io.removeLease(path);
|
|
955
|
+
return current;
|
|
956
|
+
});
|
|
957
|
+
const held = io.readHolder(root);
|
|
958
|
+
if (held[key]?.token === record.token) {
|
|
959
|
+
delete held[key];
|
|
960
|
+
io.writeHolder(root, held);
|
|
961
|
+
}
|
|
962
|
+
return released;
|
|
963
|
+
}
|
|
964
|
+
function releaseByRoot(root, platform, id, io) {
|
|
965
|
+
const path = deviceLeasePath(platform, id);
|
|
966
|
+
return io.withLeaseLock(deviceLeaseLockPath(platform, id), () => {
|
|
967
|
+
const current = parseLease(io.readLease(path));
|
|
968
|
+
if (!current || current.holder !== root) return null;
|
|
969
|
+
io.removeLease(path);
|
|
970
|
+
return current;
|
|
971
|
+
});
|
|
972
|
+
}
|
|
973
|
+
function releaseRunLease({ root, platform, slot = "default" }, io = fileLeaseIo) {
|
|
974
|
+
return withHolderLock(io, root, () => {
|
|
975
|
+
const record = io.readHolder(root)[deviceSlotKey(platform, slot)];
|
|
976
|
+
if (!record || record.kind !== "run") return null;
|
|
977
|
+
const released = releaseHeld(root, platform, record, io, slot);
|
|
978
|
+
return released ? summarize(released) : null;
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
function releaseWorkspaceLeases(root, { platform = null, slot = null } = {}, io = fileLeaseIo) {
|
|
982
|
+
return withHolderLock(io, root, () => {
|
|
983
|
+
const released = [];
|
|
984
|
+
for (const [name, record] of Object.entries(io.readHolder(root))) {
|
|
985
|
+
const parsed = parseDeviceSlotKey(name);
|
|
986
|
+
if (!parsed || platform && parsed.platform !== platform || slot !== null && parsed.slot !== slot) continue;
|
|
987
|
+
const lease = releaseHeld(root, parsed.platform, record, io, parsed.slot);
|
|
988
|
+
if (lease) released.push(summarize(lease));
|
|
989
|
+
}
|
|
990
|
+
for (const entry of listLeaseFiles(io)) {
|
|
991
|
+
const lease = entry.lease;
|
|
992
|
+
if (!lease || lease.holder !== root) continue;
|
|
993
|
+
if (platform && lease.platform !== platform) continue;
|
|
994
|
+
if (slot !== null && (lease.slot ?? "default") !== slot) continue;
|
|
995
|
+
const byRoot = releaseByRoot(root, lease.platform, lease.id, io);
|
|
996
|
+
if (byRoot) released.push(summarize(byRoot));
|
|
997
|
+
}
|
|
998
|
+
return released;
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
function removeExpiredLease(entry, io = fileLeaseIo) {
|
|
1002
|
+
const lease = entry.lease;
|
|
1003
|
+
if (!lease) return false;
|
|
1004
|
+
return io.withLeaseLock(deviceLeaseLockPath(lease.platform, lease.id), () => {
|
|
1005
|
+
const current = parseLease(io.readLease(entry.path));
|
|
1006
|
+
if (!current || !leaseIsExpired(current, io.now())) return false;
|
|
1007
|
+
io.removeLease(entry.path);
|
|
1008
|
+
return true;
|
|
1009
|
+
});
|
|
1010
|
+
}
|
|
1011
|
+
function selectPoolDevice({ candidates, leases, held = null, now }) {
|
|
1012
|
+
const ordered = candidates.toSorted((a, b) => {
|
|
1013
|
+
const left = a.id.toLowerCase();
|
|
1014
|
+
const right = b.id.toLowerCase();
|
|
1015
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
1016
|
+
});
|
|
1017
|
+
if (held) {
|
|
1018
|
+
const mine = ordered.find((candidate) => candidate.id === held);
|
|
1019
|
+
return mine ? {
|
|
1020
|
+
status: "selected",
|
|
1021
|
+
candidate: mine
|
|
1022
|
+
} : {
|
|
1023
|
+
status: "held-disconnected",
|
|
1024
|
+
id: held
|
|
1025
|
+
};
|
|
1026
|
+
}
|
|
1027
|
+
const preferred = ordered.filter((candidate) => !candidate.fallback);
|
|
1028
|
+
const pool = preferred.length > 0 ? preferred : ordered;
|
|
1029
|
+
if (pool.length === 0) return { status: "none" };
|
|
1030
|
+
const byId = new Map(leases.map((lease) => [lease.id, lease]));
|
|
1031
|
+
const free = (candidate) => {
|
|
1032
|
+
const lease = byId.get(candidate.id);
|
|
1033
|
+
return !lease || leaseIsExpired(lease, now);
|
|
1034
|
+
};
|
|
1035
|
+
const holders = [];
|
|
1036
|
+
for (const candidate of pool) {
|
|
1037
|
+
if (free(candidate)) return {
|
|
1038
|
+
status: "selected",
|
|
1039
|
+
candidate
|
|
1040
|
+
};
|
|
1041
|
+
const lease = byId.get(candidate.id);
|
|
1042
|
+
holders.push({
|
|
1043
|
+
id: lease.id,
|
|
1044
|
+
holder: lease.holder,
|
|
1045
|
+
expiresAt: lease.expiresAt
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
return {
|
|
1049
|
+
status: "busy",
|
|
1050
|
+
holders,
|
|
1051
|
+
spare: ordered.filter((candidate) => candidate.fallback && free(candidate)).map((candidate) => candidate.id)
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
function withHolderLock(io, root, fn) {
|
|
1055
|
+
return io.withHolderLock ? io.withHolderLock(root, fn) : fn();
|
|
1056
|
+
}
|
|
603
1057
|
//#endregion
|
|
604
|
-
export {
|
|
1058
|
+
export { resolveBuild as A, fingerprintDiffRecord as C, providerDownloadPath as D, prepareProviderDownloadDir as E, assetDiffReason as F, captureAssetManifest as I, compareAssetManifests as L, storedAssetManifest as M, untrackedMissLine as N, providerUploadOutcome as O, untrackedNativeFiles as P, readAssetManifest as R, filesystemBuildCapability as S, fingerprintProject as T, waitForBuild as _, parseLease as a, describeFingerprintMiss as b, raiseLease as c, removeExpiredLease as d, selectPoolDevice as f, releaseBuildLock as g, listBuildLocks as h, listLeaseFiles as i, storeBuild as j, refingerprintAfterMutation as k, releaseRunLease as l, acquireBuildLock as m, fileLeaseIo as n, parseLeaseDuration as o, takeLease as p, leaseIsExpired as r, parseWorkspaceLeases as s, deviceLeasePath as t, releaseWorkspaceLeases as u, waitForSharedBuild as v, fingerprintDiffSuffix as w, diffFingerprintSources as x, buildCacheKey as y };
|