stim 1.0.0 → 1.2.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 +29 -10
- package/dist/android-BW8i_YxU.mjs +204 -0
- package/dist/android-Bcmev2s2.mjs +2334 -0
- package/dist/android-K63_0UfF.mjs +727 -0
- package/dist/android-cas-D6HPZ6UQ.mjs +1078 -0
- package/dist/android-cas-compiler.mjs +2 -2
- package/dist/app-install-NFxUahnH.mjs +1149 -0
- package/dist/build-lock-BsVoLDaq.mjs +645 -0
- package/dist/build-slots-D0b6sJyo.mjs +152 -0
- package/dist/{cache-manifest-4tH64LQ9.mjs → cache-manifest-Cu4_Znw9.mjs} +2 -2
- package/dist/cache-manifest.mjs +1 -1
- package/dist/cli.mjs +24 -27166
- package/dist/collector-run.d.mts +1 -0
- package/dist/collector-run.mjs +7 -5
- package/dist/command-output-8wHMEde0.mjs +289 -0
- package/dist/{config-7kmtuhO2.mjs → config-CMtIaTk9.mjs} +44 -13
- package/dist/deps-BLBsTNDS.mjs +435 -0
- package/dist/dev-client-RpvXxCXV.mjs +620 -0
- package/dist/device-CoCPmazS.mjs +250 -0
- package/dist/device-lease-Dpik41RY.mjs +335 -0
- package/dist/device-pool-B5pEVa3t.mjs +408 -0
- package/dist/device-remote-E81KAdXf.mjs +1209 -0
- package/dist/doctor-CqPeMsSm.mjs +1130 -0
- package/dist/doctor-D_CX7ib5.mjs +530 -0
- package/dist/error-diagnostics-Dw8Gn3yw.mjs +636 -0
- package/dist/{exec-bsN9MJXb.mjs → exec-CyylIdq9.mjs} +4 -2
- package/dist/gc-B7KkDinF.mjs +1665 -0
- package/dist/guide-BS2Xc24o.mjs +4503 -0
- package/dist/ios-B7KLgIp5.mjs +2733 -0
- package/dist/ios-D1h1Di39.mjs +451 -0
- package/dist/ios-device-D3pzTKQE.mjs +81 -0
- package/dist/ios-device-xNt0Lg6v.mjs +413 -0
- package/dist/logs-DcX9AvRF.mjs +176 -0
- package/dist/logs-query-oqn47Ffx.mjs +273 -0
- package/dist/metro-COslRK_F.mjs +184 -0
- package/dist/metro-store-CDDZHtPl.mjs +92 -0
- package/dist/ownership-CGPart6T.mjs +60 -0
- package/dist/ownership-CYOyqxUo.mjs +475 -0
- package/dist/ownership-claim-7zRxxTfZ.mjs +569 -0
- package/dist/{project-Dit1CckW.mjs → project-BBTVABUz.mjs} +12 -3
- package/dist/reclaim-C5Jmuahx.mjs +348 -0
- package/dist/reload-Z8nKifAk.mjs +332 -0
- package/dist/remote-cache-DDmJ1Roq.mjs +994 -0
- package/dist/{server-bare-BmTVVmBB.mjs → server-bare-DEk37Mz2.mjs} +8 -2
- package/dist/{server-expo-CHJe1r6j.mjs → server-expo-C3ktAwUG.mjs} +6 -5
- package/dist/server-expo-Drd7ksHu.mjs +2 -0
- package/dist/settings-DwU1_U_q.mjs +708 -0
- package/dist/spawn-entry-nCE6m770.mjs +14 -0
- package/dist/start-BrY4EuIg.mjs +822 -0
- package/dist/{state-2nush3MK.mjs → state-CZ-ER-8d.mjs} +21 -22
- package/dist/stats-BCWEVLrM.mjs +307 -0
- package/dist/stats-BsF15Bti.mjs +62 -0
- package/dist/status-DFI1Hfpg.mjs +393 -0
- package/dist/stop-B7DR4awK.mjs +3 -0
- package/dist/stop-Btg770vV.mjs +591 -0
- package/dist/supervisor-run.d.mts +0 -1
- package/dist/supervisor-run.mjs +4 -4
- package/dist/workspace-process-lock-BKfdys1q.mjs +47 -0
- package/dist/worktree-BPTbI-LN.mjs +1322 -0
- package/dist/worktree-C79xx_kM.mjs +543 -0
- package/dist/xcode-DeKCribl.mjs +1832 -0
- package/package.json +5 -5
- package/shim/bundle-response.cjs +5 -0
- package/dist/android-jc8MnUyL.mjs +0 -514
- package/dist/metro-store-COLl1pOk.mjs +0 -1249
- package/dist/server-expo-BZJm--wA.mjs +0 -2
|
@@ -0,0 +1,569 @@
|
|
|
1
|
+
import { E as inspectProcessIdentity, w as captureProcessIdentity } from "./config-CMtIaTk9.mjs";
|
|
2
|
+
import { d as quotedPath } from "./command-output-8wHMEde0.mjs";
|
|
3
|
+
import { lstatSync, mkdirSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
//#region src/ownership-claim.ts
|
|
7
|
+
const CLAIM_REFUSED = "STIM_CLAIM_REFUSED";
|
|
8
|
+
const CLAIM_UNAVAILABLE = "STIM_CLAIM_UNAVAILABLE";
|
|
9
|
+
/**
|
|
10
|
+
* The refusal reason for a claim store whose own path is occupied by a file. It names a filesystem
|
|
11
|
+
* state that predates any claim, so a caller that only reads can tell it apart from an unresolvable
|
|
12
|
+
* claim record and fall back to what it did before claims existed.
|
|
13
|
+
*/
|
|
14
|
+
const CLAIM_PATH_NOT_A_DIRECTORY = "the claim path is a file, not a claim directory";
|
|
15
|
+
const EXCLUSIVE_DIR = "exclusive";
|
|
16
|
+
const SHARED_DIR = "shared";
|
|
17
|
+
const CLAIM_SUFFIX = ".claim";
|
|
18
|
+
const CHILD_SUFFIX = ".child";
|
|
19
|
+
const STAGING_PREFIX = ".staging-";
|
|
20
|
+
const PUBLISH_ATTEMPTS = 64;
|
|
21
|
+
var ClaimRefusedError = class extends Error {
|
|
22
|
+
code = CLAIM_REFUSED;
|
|
23
|
+
claimPath;
|
|
24
|
+
reason;
|
|
25
|
+
removeCommand;
|
|
26
|
+
constructor({ claimPath, root, reason, label }) {
|
|
27
|
+
const removeCommand = claimRemoveCommand(claimPath.startsWith(root) ? claimPath : root);
|
|
28
|
+
super(`Stim cannot tell whether the ${label} claim at ${claimPath} is still held: ${reason}. It will not remove a claim it cannot prove is dead, and it will not wait on one either. If nothing is using it, remove the claim and run the command again:\n ${removeCommand}`);
|
|
29
|
+
this.claimPath = claimPath;
|
|
30
|
+
this.reason = reason;
|
|
31
|
+
this.removeCommand = removeCommand;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
var ClaimUnavailableError = class extends Error {
|
|
35
|
+
code = CLAIM_UNAVAILABLE;
|
|
36
|
+
reason;
|
|
37
|
+
constructor(reason) {
|
|
38
|
+
super(`Stim could not record a process identity, so it cannot take an ownership claim: ${reason}.`);
|
|
39
|
+
this.reason = reason;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* The command that clears one surveyed claim: targeted at the claim's own file when the path names one,
|
|
44
|
+
* and quoted, because this text is printed for a human to copy into a shell and a path with a space in
|
|
45
|
+
* it would otherwise name several targets.
|
|
46
|
+
*/
|
|
47
|
+
function claimRemoveCommand(path) {
|
|
48
|
+
return `rm ${path.endsWith(CLAIM_SUFFIX) || path.endsWith(CHILD_SUFFIX) ? "-f" : "-rf"} ${quotedPath(path)}`;
|
|
49
|
+
}
|
|
50
|
+
function isClaimRefusal(err) {
|
|
51
|
+
return err?.code === CLAIM_REFUSED;
|
|
52
|
+
}
|
|
53
|
+
function isClaimUnavailable(err) {
|
|
54
|
+
return err?.code === CLAIM_UNAVAILABLE;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The refusal a command reports for either error the claim primitive raises, and null for anything else.
|
|
58
|
+
* Both are refusals: a claim Stim cannot resolve and a process identity it cannot record are the two
|
|
59
|
+
* states in which it holds no claim, and running the operation a claim serializes without one gives a
|
|
60
|
+
* competing run no protection at all.
|
|
61
|
+
*/
|
|
62
|
+
function claimFailure(err, retryCommand) {
|
|
63
|
+
if (isClaimRefusal(err)) return {
|
|
64
|
+
code: CLAIM_REFUSED,
|
|
65
|
+
message: err.message,
|
|
66
|
+
remedy: `Run \`${err.removeCommand}\`, then run \`${retryCommand}\` again.`
|
|
67
|
+
};
|
|
68
|
+
if (isClaimUnavailable(err)) return {
|
|
69
|
+
code: CLAIM_UNAVAILABLE,
|
|
70
|
+
message: err.message,
|
|
71
|
+
remedy: `Reinstall Stim so the unique-pid native module for this platform is present, then run \`${retryCommand}\` again.`
|
|
72
|
+
};
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
function exclusiveClaimDir(root) {
|
|
76
|
+
return join(root, EXCLUSIVE_DIR);
|
|
77
|
+
}
|
|
78
|
+
function sharedClaimDir(root) {
|
|
79
|
+
return join(root, SHARED_DIR);
|
|
80
|
+
}
|
|
81
|
+
function childPath(claimPath) {
|
|
82
|
+
return `${claimPath.slice(0, -6)}${CHILD_SUFFIX}`;
|
|
83
|
+
}
|
|
84
|
+
function asOwner(record) {
|
|
85
|
+
const candidate = record;
|
|
86
|
+
if (!candidate || typeof candidate !== "object") return null;
|
|
87
|
+
if (typeof candidate.pid !== "number" || !Number.isSafeInteger(candidate.pid) || candidate.pid <= 0) return null;
|
|
88
|
+
if (typeof candidate.processToken !== "string" || !candidate.processToken) return null;
|
|
89
|
+
return {
|
|
90
|
+
pid: candidate.pid,
|
|
91
|
+
processToken: candidate.processToken
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function readChild(claimPath) {
|
|
95
|
+
let text;
|
|
96
|
+
try {
|
|
97
|
+
text = readFileSync(childPath(claimPath), "utf-8");
|
|
98
|
+
} catch (err) {
|
|
99
|
+
if (err?.code === "ENOENT") return null;
|
|
100
|
+
return "unreadable";
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
return { record: asOwner(JSON.parse(text)?.record) };
|
|
104
|
+
} catch {
|
|
105
|
+
return "unreadable";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function readClaim(path, mode) {
|
|
109
|
+
let text;
|
|
110
|
+
try {
|
|
111
|
+
text = readFileSync(path, "utf-8");
|
|
112
|
+
} catch (err) {
|
|
113
|
+
if (err?.code === "ENOENT") return null;
|
|
114
|
+
return "unreadable";
|
|
115
|
+
}
|
|
116
|
+
let parsed;
|
|
117
|
+
try {
|
|
118
|
+
parsed = JSON.parse(text);
|
|
119
|
+
} catch {
|
|
120
|
+
return "unreadable";
|
|
121
|
+
}
|
|
122
|
+
const owner = asOwner(parsed?.owner);
|
|
123
|
+
if (!owner || typeof parsed.claimId !== "string" || !parsed.claimId) return "unreadable";
|
|
124
|
+
const child = readChild(path);
|
|
125
|
+
if (child === "unreadable") return "unreadable";
|
|
126
|
+
return {
|
|
127
|
+
path,
|
|
128
|
+
claimId: parsed.claimId,
|
|
129
|
+
mode,
|
|
130
|
+
owner,
|
|
131
|
+
startedAt: typeof parsed.startedAt === "string" ? parsed.startedAt : "",
|
|
132
|
+
details: parsed.details && typeof parsed.details === "object" ? parsed.details : {},
|
|
133
|
+
child: child?.record ?? null,
|
|
134
|
+
childDeclared: child !== null
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Whether any member of the process group `pid` leads is alive. A recorded child is resolved through
|
|
139
|
+
* its group rather than through the one process, so a package manager's postinstall descendant keeps
|
|
140
|
+
* the claim after the manager exits. A pgid, unlike a ProcessRecord, carries no identity: a recycled
|
|
141
|
+
* one reads as alive, which holds a claim longer than it needs to be held and never frees a live one.
|
|
142
|
+
*/
|
|
143
|
+
function processGroupAlive(pid) {
|
|
144
|
+
if (!Number.isSafeInteger(pid) || pid <= 1) return false;
|
|
145
|
+
try {
|
|
146
|
+
process.kill(-pid, 0);
|
|
147
|
+
return true;
|
|
148
|
+
} catch (err) {
|
|
149
|
+
return err?.code === "EPERM";
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function childLiveness(claimPath) {
|
|
153
|
+
const child = readChild(claimPath);
|
|
154
|
+
if (child === null) return "none";
|
|
155
|
+
if (child === "unreadable" || !child.record) return "unknown";
|
|
156
|
+
const status = inspectProcessIdentity(child.record);
|
|
157
|
+
if (status === "same") return "live";
|
|
158
|
+
if (status === "gone") return processGroupAlive(child.record.pid) ? "live" : "dead";
|
|
159
|
+
return "unknown";
|
|
160
|
+
}
|
|
161
|
+
function claimLiveness(holder) {
|
|
162
|
+
const status = inspectProcessIdentity(holder.owner);
|
|
163
|
+
if (status === "same") return "live";
|
|
164
|
+
if (status === "unknown") return "unknown";
|
|
165
|
+
const child = childLiveness(holder.path);
|
|
166
|
+
return child === "none" ? "dead" : child;
|
|
167
|
+
}
|
|
168
|
+
function refuse(root, claimPath, label, reason) {
|
|
169
|
+
throw new ClaimRefusedError({
|
|
170
|
+
claimPath,
|
|
171
|
+
root,
|
|
172
|
+
label,
|
|
173
|
+
reason
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
function names(dir) {
|
|
177
|
+
try {
|
|
178
|
+
return readdirSync(dir).toSorted();
|
|
179
|
+
} catch (err) {
|
|
180
|
+
if (err?.code === "ENOENT") return [];
|
|
181
|
+
return "unreadable";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function survey(dir, mode, into) {
|
|
185
|
+
const listed = names(dir);
|
|
186
|
+
if (listed === "unreadable") {
|
|
187
|
+
into.unresolved.push({
|
|
188
|
+
path: dir,
|
|
189
|
+
reason: "its directory could not be read"
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
const claims = new Set(listed.filter((name) => name.endsWith(CLAIM_SUFFIX)));
|
|
194
|
+
const orphans = listed.filter((name) => name.endsWith(CHILD_SUFFIX) && !claims.has(`${name.slice(0, -6)}${CLAIM_SUFFIX}`));
|
|
195
|
+
for (const name of orphans) into.orphans.push(join(dir, name));
|
|
196
|
+
for (const name of listed) {
|
|
197
|
+
if (!name.endsWith(CLAIM_SUFFIX)) continue;
|
|
198
|
+
const path = join(dir, name);
|
|
199
|
+
const holder = readClaim(path, mode);
|
|
200
|
+
if (holder === null) continue;
|
|
201
|
+
if (holder === "unreadable") {
|
|
202
|
+
into.unresolved.push({
|
|
203
|
+
path,
|
|
204
|
+
reason: "its record is missing, truncated or not valid JSON"
|
|
205
|
+
});
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const liveness = claimLiveness(holder);
|
|
209
|
+
if (liveness === "live") into.live.push(holder);
|
|
210
|
+
else if (liveness === "dead") into.dead.push(holder);
|
|
211
|
+
else into.unresolved.push({
|
|
212
|
+
path,
|
|
213
|
+
reason: holder.childDeclared && !holder.child ? "it spawned the process that holds it and was killed before recording which one" : "its process identity token does not decode, so the holder cannot be identified"
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
if (listed.filter((name) => !orphans.includes(name)).length > 0 && claims.size === 0) into.unresolved.push({
|
|
217
|
+
path: dir,
|
|
218
|
+
reason: "it holds files Stim did not write"
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
/** Classify every claim in a claim set without removing anything and without refusing. */
|
|
222
|
+
function readClaimSet(root) {
|
|
223
|
+
const state = {
|
|
224
|
+
live: [],
|
|
225
|
+
dead: [],
|
|
226
|
+
unresolved: [],
|
|
227
|
+
orphans: []
|
|
228
|
+
};
|
|
229
|
+
survey(exclusiveClaimDir(root), "exclusive", state);
|
|
230
|
+
survey(sharedClaimDir(root), "shared", state);
|
|
231
|
+
return state;
|
|
232
|
+
}
|
|
233
|
+
function removeOrRefuse(path, root, claimPath, label) {
|
|
234
|
+
try {
|
|
235
|
+
unlinkSync(path);
|
|
236
|
+
} catch (err) {
|
|
237
|
+
const code = err?.code;
|
|
238
|
+
if (code === "ENOENT") return;
|
|
239
|
+
refuse(root, claimPath, label, `the claim left behind could not be removed (${code || err?.message})`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function reap(holder, root, label) {
|
|
243
|
+
const again = readClaim(holder.path, holder.mode);
|
|
244
|
+
if (again === null) return true;
|
|
245
|
+
if (again === "unreadable" || again.claimId !== holder.claimId) return false;
|
|
246
|
+
removeOrRefuse(childPath(holder.path), root, holder.path, label);
|
|
247
|
+
removeOrRefuse(holder.path, root, holder.path, label);
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
function tidy(dir) {
|
|
251
|
+
try {
|
|
252
|
+
rmdirSync(dir);
|
|
253
|
+
} catch {}
|
|
254
|
+
}
|
|
255
|
+
function inspectClaimSet(root, { label = "ownership" } = {}) {
|
|
256
|
+
const state = readClaimSet(root);
|
|
257
|
+
const unresolved = state.unresolved[0];
|
|
258
|
+
if (unresolved) refuse(root, unresolved.path, label, unresolved.reason);
|
|
259
|
+
const reaped = [];
|
|
260
|
+
for (const orphan of state.orphans) removeOrRefuse(orphan, root, orphan, label);
|
|
261
|
+
for (const holder of state.dead) if (reap(holder, root, label)) reaped.push(holder);
|
|
262
|
+
if (!state.live.some((holder) => holder.mode === "exclusive")) tidy(exclusiveClaimDir(root));
|
|
263
|
+
if (!state.live.some((holder) => holder.mode === "shared")) tidy(sharedClaimDir(root));
|
|
264
|
+
return {
|
|
265
|
+
exclusive: state.live.find((holder) => holder.mode === "exclusive") ?? null,
|
|
266
|
+
shared: state.live.filter((holder) => holder.mode === "shared"),
|
|
267
|
+
reaped
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
function tidySet(root) {
|
|
271
|
+
tidy(exclusiveClaimDir(root));
|
|
272
|
+
tidy(sharedClaimDir(root));
|
|
273
|
+
tidy(root);
|
|
274
|
+
}
|
|
275
|
+
function removeAbandonedStaging(root) {
|
|
276
|
+
const listed = names(root);
|
|
277
|
+
if (listed === "unreadable") return;
|
|
278
|
+
for (const name of listed) if (name.startsWith(STAGING_PREFIX)) rmSync(join(root, name), {
|
|
279
|
+
recursive: true,
|
|
280
|
+
force: true
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Remove a claim set that holds nothing. Takes the set's own exclusive claim first and removes the set
|
|
285
|
+
* only through the token-verified unlink and empty-directory removals that releasing a claim performs,
|
|
286
|
+
* so a claim published between the survey that reported the set stale and this call is kept, not
|
|
287
|
+
* deleted; a set whose state cannot be established is refused rather than removed.
|
|
288
|
+
*/
|
|
289
|
+
function clearFreeClaimSet({ root, label = "ownership" }) {
|
|
290
|
+
let attempt;
|
|
291
|
+
try {
|
|
292
|
+
attempt = tryAcquireClaim({
|
|
293
|
+
root,
|
|
294
|
+
mode: "exclusive",
|
|
295
|
+
label
|
|
296
|
+
});
|
|
297
|
+
} catch (err) {
|
|
298
|
+
if (isClaimRefusal(err) || isClaimUnavailable(err)) return {
|
|
299
|
+
status: "refused",
|
|
300
|
+
reason: err.reason
|
|
301
|
+
};
|
|
302
|
+
return {
|
|
303
|
+
status: "failed",
|
|
304
|
+
reason: err?.message ?? String(err)
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
if (!attempt.acquired) {
|
|
308
|
+
if (attempt.pending) releaseClaim(attempt.pending);
|
|
309
|
+
return {
|
|
310
|
+
status: "held",
|
|
311
|
+
holder: attempt.held ?? attempt.waitingFor?.[0] ?? null
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
try {
|
|
315
|
+
removeAbandonedStaging(root);
|
|
316
|
+
} catch (err) {
|
|
317
|
+
return {
|
|
318
|
+
status: "failed",
|
|
319
|
+
reason: err?.message ?? String(err)
|
|
320
|
+
};
|
|
321
|
+
} finally {
|
|
322
|
+
releaseClaim(attempt.acquired);
|
|
323
|
+
}
|
|
324
|
+
tidySet(root);
|
|
325
|
+
const remaining = names(root);
|
|
326
|
+
if (remaining === "unreadable") return {
|
|
327
|
+
status: "failed",
|
|
328
|
+
reason: "its directory could not be read"
|
|
329
|
+
};
|
|
330
|
+
const after = readClaimSet(root);
|
|
331
|
+
if (after.live.length > 0) return {
|
|
332
|
+
status: "held",
|
|
333
|
+
holder: after.live[0]
|
|
334
|
+
};
|
|
335
|
+
if (after.unresolved.length > 0) return {
|
|
336
|
+
status: "refused",
|
|
337
|
+
reason: after.unresolved[0].reason
|
|
338
|
+
};
|
|
339
|
+
if (remaining.length > 0) return {
|
|
340
|
+
status: "failed",
|
|
341
|
+
reason: `it still holds ${remaining.join(", ")}`
|
|
342
|
+
};
|
|
343
|
+
return { status: "cleared" };
|
|
344
|
+
}
|
|
345
|
+
function selfOwner() {
|
|
346
|
+
const captured = captureProcessIdentity(process.pid);
|
|
347
|
+
if (!captured.ok) throw new ClaimUnavailableError(captured.reason);
|
|
348
|
+
return {
|
|
349
|
+
pid: process.pid,
|
|
350
|
+
processToken: captured.token
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
function contended(error) {
|
|
354
|
+
if (error instanceof MissingClaimStoreError) return false;
|
|
355
|
+
const code = error?.code;
|
|
356
|
+
return code === "EEXIST" || code === "ENOTEMPTY" || code === "ENOENT" || code === "EINVAL";
|
|
357
|
+
}
|
|
358
|
+
var MissingClaimStoreError = class extends Error {
|
|
359
|
+
code = "ENOENT";
|
|
360
|
+
};
|
|
361
|
+
function createClaimDir(path) {
|
|
362
|
+
try {
|
|
363
|
+
mkdirSync(path, { recursive: true });
|
|
364
|
+
return;
|
|
365
|
+
} catch (error) {
|
|
366
|
+
if (error?.code !== "ENOENT") throw error;
|
|
367
|
+
}
|
|
368
|
+
const missing = [];
|
|
369
|
+
for (let level = path;; level = dirname(level)) {
|
|
370
|
+
let entry;
|
|
371
|
+
try {
|
|
372
|
+
entry = lstatSync(level);
|
|
373
|
+
} catch (error) {
|
|
374
|
+
if (error?.code !== "ENOENT") throw error;
|
|
375
|
+
missing.push(level);
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
if (entry.isSymbolicLink()) try {
|
|
379
|
+
statSync(level);
|
|
380
|
+
} catch (error) {
|
|
381
|
+
if (error?.code === "ENOENT") throw new MissingClaimStoreError(error.message, { cause: error });
|
|
382
|
+
throw error;
|
|
383
|
+
}
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
for (const level of missing.toReversed()) try {
|
|
387
|
+
mkdirSync(level);
|
|
388
|
+
} catch (error) {
|
|
389
|
+
if (error?.code !== "EEXIST") throw error;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function publishExclusive(root, payload, claimId, label) {
|
|
393
|
+
const staging = join(root, `${STAGING_PREFIX}${claimId}`);
|
|
394
|
+
const target = exclusiveClaimDir(root);
|
|
395
|
+
try {
|
|
396
|
+
createClaimDir(staging);
|
|
397
|
+
writeFileSync(join(staging, `${claimId}${CLAIM_SUFFIX}`), payload);
|
|
398
|
+
renameSync(staging, target);
|
|
399
|
+
return join(target, `${claimId}${CLAIM_SUFFIX}`);
|
|
400
|
+
} catch (err) {
|
|
401
|
+
rmSync(staging, {
|
|
402
|
+
recursive: true,
|
|
403
|
+
force: true
|
|
404
|
+
});
|
|
405
|
+
const code = err?.code;
|
|
406
|
+
if (contended(err)) return null;
|
|
407
|
+
if (code === "ENOTDIR") refuse(root, target, label, CLAIM_PATH_NOT_A_DIRECTORY);
|
|
408
|
+
throw err;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
function publishShared(root, payload, claimId) {
|
|
412
|
+
const staging = join(root, `${STAGING_PREFIX}${claimId}`);
|
|
413
|
+
const target = join(sharedClaimDir(root), `${claimId}${CLAIM_SUFFIX}`);
|
|
414
|
+
try {
|
|
415
|
+
createClaimDir(sharedClaimDir(root));
|
|
416
|
+
writeFileSync(staging, payload);
|
|
417
|
+
renameSync(staging, target);
|
|
418
|
+
return target;
|
|
419
|
+
} catch (err) {
|
|
420
|
+
rmSync(staging, { force: true });
|
|
421
|
+
if (contended(err)) return null;
|
|
422
|
+
throw err;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
function settleOrRelease(claim) {
|
|
426
|
+
try {
|
|
427
|
+
return inspectClaimSet(claim.root, { label: claim.label });
|
|
428
|
+
} catch (err) {
|
|
429
|
+
releaseClaim(claim);
|
|
430
|
+
throw err;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function tryAcquireClaim({ root, mode, details = {}, label = "ownership" }) {
|
|
434
|
+
const owner = selfOwner();
|
|
435
|
+
const reaped = [];
|
|
436
|
+
for (let attempt = 0; attempt < PUBLISH_ATTEMPTS; attempt++) {
|
|
437
|
+
try {
|
|
438
|
+
createClaimDir(root);
|
|
439
|
+
} catch (err) {
|
|
440
|
+
const code = err?.code;
|
|
441
|
+
if (code === "EEXIST" || code === "ENOTDIR") refuse(root, root, label, CLAIM_PATH_NOT_A_DIRECTORY);
|
|
442
|
+
if (contended(err)) continue;
|
|
443
|
+
throw err;
|
|
444
|
+
}
|
|
445
|
+
const state = inspectClaimSet(root, { label });
|
|
446
|
+
reaped.push(...state.reaped);
|
|
447
|
+
if (state.exclusive) return {
|
|
448
|
+
held: state.exclusive,
|
|
449
|
+
reaped
|
|
450
|
+
};
|
|
451
|
+
const claimId = randomUUID();
|
|
452
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
453
|
+
const payload = JSON.stringify({
|
|
454
|
+
claimId,
|
|
455
|
+
mode,
|
|
456
|
+
owner,
|
|
457
|
+
startedAt,
|
|
458
|
+
details
|
|
459
|
+
});
|
|
460
|
+
const handle = (path) => ({
|
|
461
|
+
root,
|
|
462
|
+
path,
|
|
463
|
+
claimId,
|
|
464
|
+
mode,
|
|
465
|
+
owner,
|
|
466
|
+
startedAt,
|
|
467
|
+
details,
|
|
468
|
+
label
|
|
469
|
+
});
|
|
470
|
+
if (mode === "shared") {
|
|
471
|
+
const published = publishShared(root, payload, claimId);
|
|
472
|
+
if (!published) continue;
|
|
473
|
+
const claim = handle(published);
|
|
474
|
+
const settled = settleOrRelease(claim);
|
|
475
|
+
reaped.push(...settled.reaped);
|
|
476
|
+
if (!settled.exclusive) return {
|
|
477
|
+
acquired: claim,
|
|
478
|
+
reaped
|
|
479
|
+
};
|
|
480
|
+
releaseClaim(claim);
|
|
481
|
+
return {
|
|
482
|
+
held: settled.exclusive,
|
|
483
|
+
reaped
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
const path = publishExclusive(root, payload, claimId, label);
|
|
487
|
+
if (!path) continue;
|
|
488
|
+
const claim = handle(path);
|
|
489
|
+
const settled = settleOrRelease(claim);
|
|
490
|
+
reaped.push(...settled.reaped);
|
|
491
|
+
if (settled.shared.length === 0) return {
|
|
492
|
+
acquired: claim,
|
|
493
|
+
reaped
|
|
494
|
+
};
|
|
495
|
+
return {
|
|
496
|
+
waitingFor: settled.shared,
|
|
497
|
+
pending: claim,
|
|
498
|
+
reaped
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
const contender = inspectClaimSet(root, { label });
|
|
502
|
+
reaped.push(...contender.reaped);
|
|
503
|
+
const holder = contender.exclusive ?? (mode === "exclusive" ? contender.shared[0] : void 0);
|
|
504
|
+
if (holder) return {
|
|
505
|
+
held: holder,
|
|
506
|
+
reaped
|
|
507
|
+
};
|
|
508
|
+
refuse(root, root, label, `another process took and gave up the claim on every one of ${PUBLISH_ATTEMPTS} attempts to take it`);
|
|
509
|
+
}
|
|
510
|
+
function settleClaim(pending) {
|
|
511
|
+
const state = inspectClaimSet(pending.root, { label: pending.label });
|
|
512
|
+
if (state.shared.length === 0) return {
|
|
513
|
+
acquired: pending,
|
|
514
|
+
reaped: state.reaped
|
|
515
|
+
};
|
|
516
|
+
return {
|
|
517
|
+
waitingFor: state.shared,
|
|
518
|
+
pending,
|
|
519
|
+
reaped: state.reaped
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
function releaseClaim(handle) {
|
|
523
|
+
if (!handle) return false;
|
|
524
|
+
const current = readClaim(handle.path, handle.mode);
|
|
525
|
+
if (current === null) return true;
|
|
526
|
+
if (current === "unreadable" || current.claimId !== handle.claimId) return false;
|
|
527
|
+
try {
|
|
528
|
+
rmSync(childPath(handle.path), { force: true });
|
|
529
|
+
rmSync(handle.path, { force: true });
|
|
530
|
+
} catch {
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
tidy(handle.mode === "exclusive" ? exclusiveClaimDir(handle.root) : sharedClaimDir(handle.root));
|
|
534
|
+
tidy(handle.root);
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Declare that this claim is about to spawn the process that does the real work, before the spawn.
|
|
539
|
+
* A claim marked this way and left without a child record reads as unresolvable rather than as free,
|
|
540
|
+
* so a parent killed between the spawn and the record cannot release a claim its child still needs.
|
|
541
|
+
*/
|
|
542
|
+
function markClaimChildPending(handle) {
|
|
543
|
+
writeChild(handle, { record: null });
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Record the spawned process that holds this claim. Spawn it with `detached: true` so it leads its own
|
|
547
|
+
* process group: the claim then reads as held while any member of that group is alive, which is what
|
|
548
|
+
* keeps a package manager's postinstall child covered after the manager itself exits.
|
|
549
|
+
*/
|
|
550
|
+
function setClaimChild(handle, record) {
|
|
551
|
+
const child = asOwner(record);
|
|
552
|
+
if (!child) throw new ClaimUnavailableError("the child process identity was not a pid and token pair");
|
|
553
|
+
writeChild(handle, { record: child });
|
|
554
|
+
}
|
|
555
|
+
function clearClaimChild(handle) {
|
|
556
|
+
rmSync(childPath(handle.path), { force: true });
|
|
557
|
+
}
|
|
558
|
+
function writeChild(handle, body) {
|
|
559
|
+
const staging = join(handle.root, `${STAGING_PREFIX}child-${handle.claimId}`);
|
|
560
|
+
try {
|
|
561
|
+
writeFileSync(staging, JSON.stringify(body));
|
|
562
|
+
renameSync(staging, childPath(handle.path));
|
|
563
|
+
} catch (err) {
|
|
564
|
+
rmSync(staging, { force: true });
|
|
565
|
+
throw err;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
//#endregion
|
|
569
|
+
export { clearClaimChild as a, isClaimUnavailable as c, readClaimSet as d, releaseClaim as f, tryAcquireClaim as h, claimRemoveCommand as i, markClaimChildPending as l, settleClaim as m, ClaimRefusedError as n, clearFreeClaimSet as o, setClaimChild as p, claimFailure as r, isClaimRefusal as s, CLAIM_PATH_NOT_A_DIRECTORY as t, processGroupAlive as u };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as findEnclosingWorktreeRoot, f as getProject } from "./config-CMtIaTk9.mjs";
|
|
2
|
+
import { g as repoRoot } from "./worktree-C79xx_kM.mjs";
|
|
2
3
|
import { existsSync, readFileSync, readdirSync, realpathSync } from "fs";
|
|
4
|
+
import { basename, dirname, join, resolve } from "path";
|
|
3
5
|
import { createRequire } from "module";
|
|
4
|
-
import { dirname, join, resolve } from "path";
|
|
5
6
|
//#region src/project.ts
|
|
6
7
|
const requireFromHere = createRequire(import.meta.url);
|
|
7
8
|
function resolvePackageJson(projectRoot, name) {
|
|
@@ -29,6 +30,14 @@ function projectShortcut(path, proj) {
|
|
|
29
30
|
if (rootPath && rootPath !== path) return `${projectShortcut(rootPath, getProject(rootPath))}/${path.split("/").pop() || path}`;
|
|
30
31
|
return path.split("/").pop() || path;
|
|
31
32
|
}
|
|
33
|
+
function ownedDeviceLabel(projectPath) {
|
|
34
|
+
const app = realpathSync(projectPath);
|
|
35
|
+
const root = repoRoot(app);
|
|
36
|
+
const appLabel = basename(app);
|
|
37
|
+
if (!root) return appLabel;
|
|
38
|
+
const worktreeLabel = basename(realpathSync(root));
|
|
39
|
+
return worktreeLabel === appLabel ? appLabel : `${worktreeLabel}-${appLabel}`;
|
|
40
|
+
}
|
|
32
41
|
function findProjectRoot(startDir) {
|
|
33
42
|
let dir;
|
|
34
43
|
try {
|
|
@@ -231,4 +240,4 @@ function detectAndroidPackageFromGradle(projectRoot) {
|
|
|
231
240
|
return null;
|
|
232
241
|
}
|
|
233
242
|
//#endregion
|
|
234
|
-
export { findProjectRoot as a,
|
|
243
|
+
export { findProjectRoot as a, projectShortcut as c, detectIsExpo as i, resolvePackageJson as l, detectAndroidPackage as n, isPackageResolvable as o, detectBundleId as r, ownedDeviceLabel as s, appProjectProblem as t };
|