@stim-cli/core 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/dist/index.mjs CHANGED
@@ -1,347 +1,3 @@
1
- import { S as quotedPath, _ as releaseClaim, r as ClaimRefusedError, x as tryAcquireClaim } from "./ownership-claim-Cn3NHqBS.mjs";
2
- import fs, { existsSync, lstatSync, mkdirSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, utimesSync, writeFileSync } from "node:fs";
3
- import os from "node:os";
4
- import path, { basename, dirname, join } from "node:path";
5
- import { createHash } from "node:crypto";
6
- //#region dir-lock.ts
7
- const DEFAULT_LOCK_WAIT_MS = 12e3;
8
- const DEFAULT_LOCK_POLL_MS = 25;
9
- const lockDepths = /* @__PURE__ */ new Map();
10
- const OWNER_MARKER = /^\.stim-claim-[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/;
11
- function sleepSync(ms) {
12
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
13
- }
14
- /**
15
- * Whether Windows refused a call on the lock directory because another process is removing it at that
16
- * moment. NTFS answers ERROR_ACCESS_DENIED, which Node reports as EPERM, until that removal completes,
17
- * where POSIX answers EEXIST or ENOENT: https://github.com/appandflow/stim/issues/883.
18
- */
19
- function removalInFlight(error) {
20
- return process.platform === "win32" && error?.code === "EPERM";
21
- }
22
- function createLockDirectory(lockPath) {
23
- try {
24
- mkdirSync(lockPath);
25
- return true;
26
- } catch (error) {
27
- if (error?.code === "EEXIST" || removalInFlight(error)) return false;
28
- throw error;
29
- }
30
- }
31
- function removeLockDirectory(lockPath, marker) {
32
- try {
33
- unlinkSync(marker);
34
- } catch (error) {
35
- if (error?.code === "ENOENT" || removalInFlight(error)) return false;
36
- throw error;
37
- }
38
- try {
39
- rmdirSync(lockPath);
40
- return true;
41
- } catch (error) {
42
- if ([
43
- "ENOENT",
44
- "ENOTEMPTY",
45
- "EEXIST"
46
- ].includes(error?.code ?? "")) return false;
47
- if (removalInFlight(error)) return false;
48
- throw error;
49
- }
50
- }
51
- function takeLockDirectory(lockPath, claim) {
52
- if (!createLockDirectory(lockPath)) {
53
- let entries;
54
- try {
55
- entries = readdirSync(lockPath);
56
- } catch (error) {
57
- if (error?.code === "ENOENT" || removalInFlight(error)) return null;
58
- throw error;
59
- }
60
- const name = entries.length === 1 ? entries[0] : void 0;
61
- if (!name || !OWNER_MARKER.test(name)) return null;
62
- const marker = join(lockPath, name);
63
- let entry;
64
- try {
65
- entry = lstatSync(marker);
66
- } catch (error) {
67
- if (error?.code === "ENOENT" || removalInFlight(error)) return null;
68
- throw error;
69
- }
70
- if (!entry.isFile() || entry.size !== 0) throw new ClaimRefusedError({
71
- root: lockPath,
72
- claimPath: lockPath,
73
- label: "directory lock",
74
- reason: `its compatibility marker ${name} is not an empty file`
75
- });
76
- if (!removeLockDirectory(lockPath, marker) || !createLockDirectory(lockPath)) return null;
77
- }
78
- const marker = join(lockPath, `.stim-claim-${claim.claimId}`);
79
- writeFileSync(marker, "", { flag: "wx" });
80
- return marker;
81
- }
82
- function acquireDirLock(lockPath, { waitMs, pollMs, ensureParent }) {
83
- ensureParent?.();
84
- statSync(dirname(lockPath));
85
- const deadline = Date.now() + waitMs;
86
- let claim;
87
- try {
88
- for (;;) {
89
- if (!claim) {
90
- const attempt = tryAcquireClaim({
91
- root: `${lockPath}.claims`,
92
- mode: "exclusive",
93
- label: "directory lock"
94
- });
95
- claim = attempt.acquired;
96
- if (attempt.pending) releaseClaim(attempt.pending);
97
- }
98
- if (claim) {
99
- const marker = takeLockDirectory(lockPath, claim);
100
- if (marker) return {
101
- claim,
102
- marker
103
- };
104
- }
105
- if (Date.now() >= deadline) {
106
- const error = /* @__PURE__ */ new Error(`Timed out waiting for the lock at ${lockPath}. Another Stim process may be holding it; if none is running, remove that directory.`);
107
- error.code = "STIM_LOCK_TIMEOUT";
108
- error.lockPath = lockPath;
109
- throw error;
110
- }
111
- sleepSync(pollMs);
112
- }
113
- } catch (error) {
114
- releaseClaim(claim);
115
- throw error;
116
- }
117
- }
118
- function withDirLock(lockPath, fn, { waitMs = DEFAULT_LOCK_WAIT_MS, pollMs = DEFAULT_LOCK_POLL_MS, ensureParent } = {}) {
119
- const depth = lockDepths.get(lockPath) || 0;
120
- if (depth > 0) {
121
- lockDepths.set(lockPath, depth + 1);
122
- try {
123
- return fn();
124
- } finally {
125
- lockDepths.set(lockPath, (lockDepths.get(lockPath) ?? 1) - 1);
126
- }
127
- }
128
- const { claim, marker } = acquireDirLock(lockPath, {
129
- waitMs,
130
- pollMs,
131
- ensureParent
132
- });
133
- lockDepths.set(lockPath, 1);
134
- try {
135
- return fn();
136
- } finally {
137
- lockDepths.set(lockPath, 0);
138
- try {
139
- removeLockDirectory(lockPath, marker);
140
- } catch {}
141
- releaseClaim(claim);
142
- }
143
- }
144
- //#endregion
145
- //#region artifact-store.ts
146
- function artifactIn(dir) {
147
- if (!existsSync(dir)) return null;
148
- let found;
149
- try {
150
- found = readdirSync(dir).find((f) => f.endsWith(".app") || f.endsWith(".apk"));
151
- } catch {
152
- return null;
153
- }
154
- return found ? join(dir, found) : null;
155
- }
156
- function resolveArtifact(dir) {
157
- const hit = artifactIn(dir);
158
- if (!hit) return null;
159
- try {
160
- utimesSync(dir, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date());
161
- } catch {}
162
- return hit;
163
- }
164
- function storeArtifact(dest, buildPath, { runFile, overwrite = false, writeMetadata, onRenameError }) {
165
- const existing = artifactIn(dest);
166
- if (existing && !overwrite) return existing;
167
- const staging = `${dest}.staging-${process.pid}`;
168
- rmSync(staging, {
169
- recursive: true,
170
- force: true
171
- });
172
- mkdirSync(staging, { recursive: true });
173
- try {
174
- runFile("cp", [
175
- "-c",
176
- "-R",
177
- buildPath,
178
- join(staging, basename(buildPath))
179
- ]);
180
- } catch {
181
- runFile("cp", [
182
- "-R",
183
- buildPath,
184
- join(staging, basename(buildPath))
185
- ]);
186
- }
187
- writeMetadata?.(staging);
188
- mkdirSync(dirname(dest), { recursive: true });
189
- rmSync(dest, {
190
- recursive: true,
191
- force: true
192
- });
193
- try {
194
- renameSync(staging, dest);
195
- } catch (error) {
196
- if (!onRenameError) throw error;
197
- onRenameError(staging);
198
- }
199
- return artifactIn(dest);
200
- }
201
- //#endregion
202
- //#region index.ts
203
- function configDir() {
204
- return process.env.STIM_HOME || path.join(os.homedir(), ".stim");
205
- }
206
- function workspaceSlug(projectRoot) {
207
- return path.basename(path.resolve(projectRoot)).normalize("NFKD").replace(/[^A-Za-z0-9]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase().slice(0, 48) || "workspace";
208
- }
209
- function workspaceId(projectRoot) {
210
- return createHash("sha256").update(path.resolve(projectRoot)).digest("hex").slice(0, 16);
211
- }
212
- function workspaceName(projectRoot) {
213
- return `${workspaceSlug(projectRoot)}--${workspaceId(projectRoot)}`;
214
- }
215
- function workspaceStateDir(projectRoot) {
216
- return path.join(configDir(), "workspaces", workspaceName(projectRoot));
217
- }
218
- function workspaceLogDir(projectRoot) {
219
- return path.join(workspaceStateDir(projectRoot), "logs");
220
- }
221
- function cachePathSetting(key) {
222
- try {
223
- const value = JSON.parse(fs.readFileSync(path.join(configDir(), "config.json"), "utf-8"))?.caches?.[key];
224
- return typeof value === "string" && path.isAbsolute(value) ? value : null;
225
- } catch {
226
- return null;
227
- }
228
- }
229
- function cacheNameSegment(name) {
230
- return String(name).replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^\.+/, "") || "app";
231
- }
232
- function buildCacheRoot() {
233
- return process.env.STIM_BUILD_CACHE || cachePathSetting("buildCache") || path.join(configDir(), "build-cache");
234
- }
235
- function metroCacheRoot(name) {
236
- const root = process.env.STIM_METRO_CACHE || cachePathSetting("metroCache") || path.join(configDir(), "metro-cache");
237
- return name === void 0 || name === null || name === "" ? root : path.join(root, cacheNameSegment(name));
238
- }
239
- const STORE_ROOT_TAG = "stimStoreRoot";
240
- function tagSharedStore(store, root) {
241
- try {
242
- Object.defineProperty(store, STORE_ROOT_TAG, {
243
- value: root,
244
- enumerable: false,
245
- configurable: true
246
- });
247
- } catch {}
248
- return store;
249
- }
250
- function sharedStoreRoot(store) {
251
- if (store === null || typeof store !== "object") return null;
252
- const tagged = store[STORE_ROOT_TAG];
253
- if (typeof tagged === "string") return tagged;
254
- const legacy = store._root;
255
- return typeof legacy === "string" ? legacy : null;
256
- }
257
- const SIMULATOR_UDID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
258
- const EMULATOR_SERIAL = /^emulator-\d+$/;
259
- function slug(value) {
260
- return String(value).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
261
- }
262
- function buildVariant(platform, options) {
263
- const raw = platform === "android" ? options.variant : options.configuration ?? options.buildConfiguration;
264
- return (typeof raw === "string" ? slug(raw) : "") || "debug";
265
- }
266
- function buildTarget(options) {
267
- if (typeof options.isSimulator === "boolean") return options.isSimulator ? "sim" : "device";
268
- const device = options.device;
269
- if (device === void 0 || device === null || device === false) return "sim";
270
- if (typeof device !== "string") return "prompted";
271
- const name = device.trim();
272
- if (name === "" || name === "generic") return "sim";
273
- if (SIMULATOR_UDID.test(name) || EMULATOR_SERIAL.test(name)) return "sim";
274
- return `on-${slug(name)}`;
275
- }
276
- function buildCacheKey(platform, fingerprintHash, options = {}) {
277
- const opts = options && typeof options === "object" ? options : {};
278
- const abi = platform === "android" && typeof opts.abi === "string" ? slug(opts.abi) : "";
279
- const compiler = typeof opts.compiler === "string" ? slug(opts.compiler) : "";
280
- const profile = typeof opts.buildProfile === "string" ? slug(opts.buildProfile) : "";
281
- const scheme = platform === "ios" && typeof opts.scheme === "string" && opts.scheme ? createHash("sha256").update(opts.scheme).digest("hex") : "";
282
- return `${fingerprintHash}-${buildVariant(platform, opts)}-${buildTarget(opts)}${abi ? `-${abi}` : ""}${compiler ? `-${compiler}` : ""}${profile ? `-${profile}` : ""}${scheme ? `-scheme-${scheme}` : ""}`;
283
- }
284
- const METRO_NAMED_CACHE_LAYOUT = "metro-named-v1";
285
- function cacheManifestLockPath(file) {
286
- return `${file}.lock`;
287
- }
288
- function readCacheManifest(file) {
289
- try {
290
- const parsed = JSON.parse(fs.readFileSync(file, "utf-8"));
291
- return {
292
- version: 1,
293
- caches: Array.isArray(parsed?.caches) ? parsed.caches : []
294
- };
295
- } catch {
296
- return {
297
- version: 1,
298
- caches: []
299
- };
300
- }
301
- }
302
- function updateCacheManifest(file, mutate) {
303
- const parent = path.dirname(file);
304
- return withDirLock(cacheManifestLockPath(file), () => {
305
- const next = {
306
- version: 1,
307
- caches: mutate(readCacheManifest(file).caches)
308
- };
309
- const temporary = path.join(parent, `.${path.basename(file)}.${process.pid}.tmp`);
310
- try {
311
- fs.writeFileSync(temporary, JSON.stringify(next, null, 2));
312
- fs.renameSync(temporary, file);
313
- } catch (error) {
314
- fs.rmSync(temporary, { force: true });
315
- throw error;
316
- }
317
- return next;
318
- }, { ensureParent: () => fs.mkdirSync(parent, { recursive: true }) });
319
- }
320
- function registerCache({ dir, name, prune, note, entriesDepth, layout, replaces = [] }) {
321
- try {
322
- updateCacheManifest(path.join(configDir(), "caches.json"), (caches) => {
323
- const others = caches.filter((cache) => cache.dir !== dir && !replaces.some((match) => matchesCache(cache, match)));
324
- const record = {
325
- dir,
326
- name,
327
- prune,
328
- note,
329
- registeredBy: process.cwd()
330
- };
331
- if (entriesDepth) record.entriesDepth = entriesDepth;
332
- if (layout) record.layout = layout;
333
- others.push(record);
334
- return others;
335
- });
336
- } catch {}
337
- }
338
- function matchesCache(cache, match) {
339
- if (cache.dir !== match.dir) return false;
340
- if (match.name !== void 0 && cache.name !== match.name) return false;
341
- if (match.prune !== void 0 && cache.prune !== match.prune) return false;
342
- if (match.entriesDepth !== void 0 && cache.entriesDepth !== match.entriesDepth) return false;
343
- if (match.layout === null) return !Object.hasOwn(cache, "layout");
344
- return match.layout === void 0 || cache.layout === match.layout;
345
- }
346
- //#endregion
347
- export { METRO_NAMED_CACHE_LAYOUT, STORE_ROOT_TAG, artifactIn, buildCacheKey, buildCacheRoot, cacheManifestLockPath, cacheNameSegment, cachePathSetting, configDir, metroCacheRoot, quotedPath, readCacheManifest, registerCache, resolveArtifact, sharedStoreRoot, storeArtifact, tagSharedStore, updateCacheManifest, withDirLock, workspaceId, workspaceLogDir, workspaceName, workspaceSlug, workspaceStateDir };
1
+ import { S as quotedPath } from "./ownership-claim-O31Y5iIb.mjs";
2
+ import { C as artifactIn, E as withDirLock, S as workspaceStateDir, T as storeArtifact, _ as updateCacheManifest, a as buildCacheRoot, b as workspaceName, c as cachePathSetting, d as readCacheManifest, f as registerCache, g as tagSharedStore, h as sharedStoreRoot, i as buildCacheKey, l as configDir, m as rotatedLogPath, n as METRO_NAMED_CACHE_LAYOUT, o as cacheManifestLockPath, p as rotateLog, r as STORE_ROOT_TAG, s as cacheNameSegment, t as LOG_ROTATE_BYTES, u as metroCacheRoot, v as workspaceId, w as resolveArtifact, x as workspaceSlug, y as workspaceLogDir } from "./core-CJWLJIr6.mjs";
3
+ export { LOG_ROTATE_BYTES, METRO_NAMED_CACHE_LAYOUT, STORE_ROOT_TAG, artifactIn, buildCacheKey, buildCacheRoot, cacheManifestLockPath, cacheNameSegment, cachePathSetting, configDir, metroCacheRoot, quotedPath, readCacheManifest, registerCache, resolveArtifact, rotateLog, rotatedLogPath, sharedStoreRoot, storeArtifact, tagSharedStore, updateCacheManifest, withDirLock, workspaceId, workspaceLogDir, workspaceName, workspaceSlug, workspaceStateDir };
@@ -18,7 +18,7 @@ const CLAIM_SUFFIX = ".claim";
18
18
  const CHILD_SUFFIX = ".child";
19
19
  const STAGING_PREFIX = ".staging-";
20
20
  const PUBLISH_ATTEMPTS = 64;
21
- const WIN32_SETTLE_MS = 250;
21
+ const WIN32_SETTLE_MS = 2e3;
22
22
  const WIN32_SETTLE_STEP_MS = 5;
23
23
  const debug = debuglog("stim:claim");
24
24
  var ClaimRefusedError = class extends Error {
@@ -99,61 +99,71 @@ function sleepSync(ms) {
99
99
  * Run a read, listing or removal and resolve the one answer Windows leaves ambiguous. NTFS answers
100
100
  * ERROR_ACCESS_DENIED, which Node reports as EPERM, for a name whose removal by another process is still
101
101
  * in flight, and for every name below it, where POSIX answers ENOENT; measured to settle within 134 ms
102
- * with 12 processes on 4 cores (https://github.com/appandflow/stim/issues/883). The call is reissued in
103
- * small steps for at most WIN32_SETTLE_MS: a result or ENOENT is the settled answer, and an EPERM that
104
- * outlasts the window is a real denial, thrown exactly as a POSIX EACCES is. This bounds the resolution
105
- * of an ambiguous OS answer; it never waits on a holder.
102
+ * with 12 processes on 4 cores (https://github.com/appandflow/stim/issues/883), and within 986 ms on an
103
+ * oversubscribed hosted windows-latest runner (https://github.com/appandflow/stim/issues/943). The call
104
+ * is reissued in small steps until a call issued WIN32_SETTLE_MS after the first EPERM still answers
105
+ * EPERM, so a call that was itself descheduled past the window never ends it: a result or ENOENT is the
106
+ * settled answer, and an EPERM that outlasts the window is a real denial, thrown exactly as a POSIX
107
+ * EACCES is. This bounds the resolution of an ambiguous OS answer; it never waits on a holder.
106
108
  */
107
109
  function settledAnswer(path, call) {
108
110
  if (process.platform !== "win32") return call();
109
- const started = Date.now();
111
+ let denied;
110
112
  for (;;) {
113
+ const asked = Date.now();
111
114
  try {
112
115
  const result = call();
113
- if (Date.now() > started) debug("%s settled after %d ms", path, Date.now() - started);
116
+ if (denied !== void 0) debug("%s settled after %d ms", path, Date.now() - denied);
114
117
  return result;
115
118
  } catch (err) {
116
- const code = err?.code;
117
- if (code !== "EPERM" || Date.now() - started >= WIN32_SETTLE_MS) {
118
- if (code === "EPERM") debug("%s still answers EPERM after %d ms", path, Date.now() - started);
119
+ if (err?.code !== "EPERM") throw err;
120
+ denied ??= Date.now();
121
+ if (asked - denied >= WIN32_SETTLE_MS) {
122
+ debug("%s still answers EPERM after %d ms", path, Date.now() - denied);
119
123
  throw err;
120
124
  }
121
125
  }
122
126
  sleepSync(WIN32_SETTLE_STEP_MS);
123
127
  }
124
128
  }
125
- function readChild(claimPath) {
126
- let text;
129
+ var Unreadable = class {
130
+ reason;
131
+ constructor(reason) {
132
+ this.reason = reason;
133
+ }
134
+ };
135
+ const NOT_JSON = new Unreadable("its record is missing, truncated or not valid JSON");
136
+ function readRecord(path, what) {
127
137
  try {
128
- text = settledAnswer(childPath(claimPath), () => readFileSync(childPath(claimPath), "utf-8"));
138
+ return settledAnswer(path, () => readFileSync(path, "utf-8"));
129
139
  } catch (err) {
130
- if (err?.code === "ENOENT") return null;
131
- return "unreadable";
140
+ const code = err?.code;
141
+ if (code === "ENOENT") return null;
142
+ return new Unreadable(`its ${what} could not be read (${code || err?.message})`);
132
143
  }
144
+ }
145
+ function readChild(claimPath) {
146
+ const text = readRecord(childPath(claimPath), "child record");
147
+ if (text === null || text instanceof Unreadable) return text;
133
148
  try {
134
149
  return { record: asOwner(JSON.parse(text)?.record) };
135
150
  } catch {
136
- return "unreadable";
151
+ return NOT_JSON;
137
152
  }
138
153
  }
139
154
  function readClaim(path, mode) {
140
- let text;
141
- try {
142
- text = settledAnswer(path, () => readFileSync(path, "utf-8"));
143
- } catch (err) {
144
- if (err?.code === "ENOENT") return null;
145
- return "unreadable";
146
- }
155
+ const text = readRecord(path, "record");
156
+ if (text === null || text instanceof Unreadable) return text;
147
157
  let parsed;
148
158
  try {
149
159
  parsed = JSON.parse(text);
150
160
  } catch {
151
- return "unreadable";
161
+ return NOT_JSON;
152
162
  }
153
163
  const owner = asOwner(parsed?.owner);
154
- if (!owner || typeof parsed.claimId !== "string" || !parsed.claimId) return "unreadable";
164
+ if (!owner || typeof parsed.claimId !== "string" || !parsed.claimId) return NOT_JSON;
155
165
  const child = readChild(path);
156
- if (child === "unreadable") return "unreadable";
166
+ if (child instanceof Unreadable) return child;
157
167
  return {
158
168
  path,
159
169
  claimId: parsed.claimId,
@@ -183,7 +193,7 @@ function processGroupAlive(pid) {
183
193
  function childLiveness(claimPath) {
184
194
  const child = readChild(claimPath);
185
195
  if (child === null) return "none";
186
- if (child === "unreadable" || !child.record) return "unknown";
196
+ if (child instanceof Unreadable || !child.record) return "unknown";
187
197
  const status = inspectProcessIdentity(child.record);
188
198
  if (status === "same") return "live";
189
199
  if (status === "gone") return processGroupAlive(child.record.pid) ? "live" : "dead";
@@ -252,10 +262,10 @@ function survey(dir, mode, into) {
252
262
  const path = join(dir, name);
253
263
  const holder = readClaim(path, mode);
254
264
  if (holder === null) continue;
255
- if (holder === "unreadable") {
265
+ if (holder instanceof Unreadable) {
256
266
  into.unresolved.push({
257
267
  path,
258
- reason: "its record is missing, truncated or not valid JSON"
268
+ reason: holder.reason
259
269
  });
260
270
  continue;
261
271
  }
@@ -296,7 +306,7 @@ function removeOrRefuse(path, root, claimPath, label) {
296
306
  function reap(holder, root, label) {
297
307
  const again = readClaim(holder.path, holder.mode);
298
308
  if (again === null) return true;
299
- if (again === "unreadable" || again.claimId !== holder.claimId) return false;
309
+ if (again instanceof Unreadable || again.claimId !== holder.claimId) return false;
300
310
  removeOrRefuse(childPath(holder.path), root, holder.path, label);
301
311
  removeOrRefuse(holder.path, root, holder.path, label);
302
312
  return true;
@@ -606,7 +616,7 @@ function releaseClaim(handle) {
606
616
  if (!handle) return false;
607
617
  const current = readClaim(handle.path, handle.mode);
608
618
  if (current === null) return true;
609
- if (current === "unreadable" || current.claimId !== handle.claimId) return false;
619
+ if (current instanceof Unreadable || current.claimId !== handle.claimId) return false;
610
620
  try {
611
621
  rmSync(childPath(handle.path), { force: true });
612
622
  rmSync(handle.path, { force: true });
@@ -1,2 +1,2 @@
1
- import { _ as releaseClaim, a as claimFailure, b as sharedClaimDir, c as clearClaimChild, d as inspectClaimSet, f as isClaimRefusal, g as readClaimSet, h as processGroupAlive, i as ClaimUnavailableError, l as clearFreeClaimSet, m as markClaimChildPending, n as CLAIM_UNAVAILABLE, o as claimLiveness, p as isClaimUnavailable, r as ClaimRefusedError, s as claimRemoveCommand, t as CLAIM_REFUSED, u as exclusiveClaimDir, v as setClaimChild, x as tryAcquireClaim, y as settleClaim } from "./ownership-claim-Cn3NHqBS.mjs";
1
+ import { _ as releaseClaim, a as claimFailure, b as sharedClaimDir, c as clearClaimChild, d as inspectClaimSet, f as isClaimRefusal, g as readClaimSet, h as processGroupAlive, i as ClaimUnavailableError, l as clearFreeClaimSet, m as markClaimChildPending, n as CLAIM_UNAVAILABLE, o as claimLiveness, p as isClaimUnavailable, r as ClaimRefusedError, s as claimRemoveCommand, t as CLAIM_REFUSED, u as exclusiveClaimDir, v as setClaimChild, x as tryAcquireClaim, y as settleClaim } from "./ownership-claim-O31Y5iIb.mjs";
2
2
  export { CLAIM_REFUSED, CLAIM_UNAVAILABLE, ClaimRefusedError, ClaimUnavailableError, claimFailure, claimLiveness, claimRemoveCommand, clearClaimChild, clearFreeClaimSet, exclusiveClaimDir, inspectClaimSet, isClaimRefusal, isClaimUnavailable, markClaimChildPending, processGroupAlive, readClaimSet, releaseClaim, setClaimChild, settleClaim, sharedClaimDir, tryAcquireClaim };
@@ -15,6 +15,16 @@ declare function captureProcessIdentity(pid: number): CapturedIdentity;
15
15
  declare function captureProcessToken(pid: number): string | null;
16
16
  type ProcessIdentityStatus = "same" | "different" | "gone" | "unknown";
17
17
  declare function inspectProcessIdentity(record: ProcessRecord | null | undefined): ProcessIdentityStatus;
18
+ type ProcessStart = {
19
+ status: "running";
20
+ startedAtMs: number;
21
+ } | {
22
+ status: "gone";
23
+ } | {
24
+ status: "unknown";
25
+ };
26
+ /** Start time of a live process, read from the same OS identity that `captureProcessIdentity` records. */
27
+ declare function inspectProcessStart(pid: number): ProcessStart;
18
28
  declare function waitForProcessExit(record: ProcessRecord, timeoutMs: number): Promise<boolean>;
19
29
  //#endregion
20
- export { CapturedIdentity, ProcessIdentityStatus, ProcessRecord, captureProcessIdentity, captureProcessToken, inspectProcessIdentity, sameProcessRecord, waitForProcessExit };
30
+ export { CapturedIdentity, ProcessIdentityStatus, ProcessRecord, ProcessStart, captureProcessIdentity, captureProcessToken, inspectProcessIdentity, inspectProcessStart, sameProcessRecord, waitForProcessExit };
@@ -26,6 +26,19 @@ function inspectProcessIdentity(record) {
26
26
  const result = check(record.processToken);
27
27
  return result.ok ? result.value : "unknown";
28
28
  }
29
+ /** Start time of a live process, read from the same OS identity that `captureProcessIdentity` records. */
30
+ function inspectProcessStart(pid) {
31
+ const captured = capture(pid);
32
+ if (!captured.ok) return captured.error.code === "NOT_FOUND" ? { status: "gone" } : { status: "unknown" };
33
+ const decoded = decode(captured.value);
34
+ if (!decoded.ok || decoded.value.platform !== "darwin") return { status: "unknown" };
35
+ const match = /^(\d+):(\d+)$/.exec(decoded.value.startTime);
36
+ if (!match) return { status: "unknown" };
37
+ return {
38
+ status: "running",
39
+ startedAtMs: Number(match[1]) * 1e3 + Math.floor(Number(match[2]) / 1e3)
40
+ };
41
+ }
29
42
  async function waitForProcessExit(record, timeoutMs) {
30
43
  const deadline = Date.now() + timeoutMs;
31
44
  while (Date.now() <= deadline) {
@@ -37,4 +50,4 @@ async function waitForProcessExit(record, timeoutMs) {
37
50
  return false;
38
51
  }
39
52
  //#endregion
40
- export { captureProcessIdentity, captureProcessToken, inspectProcessIdentity, sameProcessRecord, waitForProcessExit };
53
+ export { captureProcessIdentity, captureProcessToken, inspectProcessIdentity, inspectProcessStart, sameProcessRecord, waitForProcessExit };