@stim-cli/core 1.20.0 → 1.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,7 @@ import fs, { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, realpa
3
3
  import os from "node:os";
4
4
  import path, { basename, dirname, join } from "node:path";
5
5
  import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
6
+ import { AsyncLocalStorage } from "node:async_hooks";
6
7
  import { spawnSync } from "node:child_process";
7
8
  import { connect, createServer, isIP } from "node:net";
8
9
  //#region dir-lock.ts
@@ -152,6 +153,48 @@ function withDirLock(lockPath, fn, { waitMs = DEFAULT_LOCK_WAIT_MS, pollMs = DEF
152
153
  }
153
154
  }
154
155
  //#endregion
156
+ //#region state/json-file.ts
157
+ const readScope = new AsyncLocalStorage();
158
+ /**
159
+ * Runs `fn` with each state file read from disk at most once: inside it, including after an `await`, `readStateFile`
160
+ * returns the text of the first read of that path, and a path found missing stays missing. Callers still parse every time, so no two
161
+ * share a mutable object. Only for computations that write none of the files they read.
162
+ */
163
+ function withStateReadCache(fn) {
164
+ return readScope.run(/* @__PURE__ */ new Map(), fn);
165
+ }
166
+ function readStateFile(path) {
167
+ const cache = readScope.getStore();
168
+ if (!cache) return readFileSync(path, "utf-8");
169
+ const cached = cache.get(path);
170
+ if (cached) {
171
+ if ("missing" in cached) throw cached.missing;
172
+ return cached.text;
173
+ }
174
+ try {
175
+ const text = readFileSync(path, "utf-8");
176
+ cache.set(path, { text });
177
+ return text;
178
+ } catch (error) {
179
+ if (error.code === "ENOENT") cache.set(path, { missing: error });
180
+ throw error;
181
+ }
182
+ }
183
+ function readJsonFile(path) {
184
+ return JSON.parse(readStateFile(path));
185
+ }
186
+ function isJsonObject(value) {
187
+ return typeof value === "object" && value !== null && !Array.isArray(value);
188
+ }
189
+ function readJsonObject(path) {
190
+ try {
191
+ const value = readJsonFile(path);
192
+ return isJsonObject(value) ? value : null;
193
+ } catch {
194
+ return null;
195
+ }
196
+ }
197
+ //#endregion
155
198
  //#region macos-resources.ts
156
199
  /** Whether a resource destination is a contained, non-empty path under Contents/Resources. */
157
200
  function validMacosResourceDestination(value) {
@@ -480,7 +523,9 @@ function logSize(file) {
480
523
  }
481
524
  function cachePathSetting(key) {
482
525
  try {
483
- const value = JSON.parse(fs.readFileSync(path.join(configDir(), "config.json"), "utf-8"))?.caches?.[key];
526
+ const parsed = JSON.parse(fs.readFileSync(path.join(configDir(), "config.json"), "utf-8"));
527
+ const caches = isJsonObject(parsed) ? parsed.caches : void 0;
528
+ const value = isJsonObject(caches) ? caches[key] : void 0;
484
529
  return typeof value === "string" && path.isAbsolute(value) ? value : null;
485
530
  } catch {
486
531
  return null;
@@ -557,7 +602,7 @@ function readCacheManifest(file) {
557
602
  const parsed = JSON.parse(fs.readFileSync(file, "utf-8"));
558
603
  return {
559
604
  version: 1,
560
- caches: Array.isArray(parsed?.caches) ? parsed.caches : []
605
+ caches: (isJsonObject(parsed) && Array.isArray(parsed.caches) ? parsed.caches : []).filter(isJsonObject)
561
606
  };
562
607
  } catch {
563
608
  return {
@@ -612,4 +657,4 @@ function matchesCache(cache, match) {
612
657
  return match.layout === void 0 || cache.layout === match.layout;
613
658
  }
614
659
  //#endregion
615
- export { storeArtifact as A, workspaceSlug as C, compiledHelper as D, createMetroGateway as E, withDirLock as M, artifactIn as O, workspaceName as S, createMetroBridge as T, sharedStoreRoot as _, buildCacheRoot as a, workspaceId as b, cachePathSetting as c, hostSimulatorArch as d, metroCacheRoot as f, rotatedLogPath as g, rotateLog as h, buildCacheKey as i, validMacosResourceDestination as j, resolveArtifact as k, configDir as l, registerCache as m, METRO_NAMED_CACHE_LAYOUT as n, cacheManifestLockPath as o, readCacheManifest as p, STORE_ROOT_TAG as r, cacheNameSegment as s, LOG_ROTATE_BYTES as t, ensurePrivateDir as u, tagSharedStore as v, workspaceStateDir as w, workspaceLogDir as x, updateCacheManifest as y };
660
+ export { storeArtifact as A, workspaceSlug as C, compiledHelper as D, createMetroGateway as E, readStateFile as F, withStateReadCache as I, withDirLock as L, isJsonObject as M, readJsonFile as N, artifactIn as O, readJsonObject as P, workspaceName as S, createMetroBridge as T, sharedStoreRoot as _, buildCacheRoot as a, workspaceId as b, cachePathSetting as c, hostSimulatorArch as d, metroCacheRoot as f, rotatedLogPath as g, rotateLog as h, buildCacheKey as i, validMacosResourceDestination as j, resolveArtifact as k, configDir as l, registerCache as m, METRO_NAMED_CACHE_LAYOUT as n, cacheManifestLockPath as o, readCacheManifest as p, STORE_ROOT_TAG as r, cacheNameSegment as s, LOG_ROTATE_BYTES as t, ensurePrivateDir as u, tagSharedStore as v, workspaceStateDir as w, workspaceLogDir as x, updateCacheManifest as y };
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  import { S as quotedPath } from "./ownership-claim-O31Y5iIb.mjs";
2
- import { A as storeArtifact, C as workspaceSlug, D as compiledHelper, E as createMetroGateway, M as withDirLock, O as artifactIn, S as workspaceName, T as createMetroBridge, _ as sharedStoreRoot, a as buildCacheRoot, b as workspaceId, c as cachePathSetting, d as hostSimulatorArch, f as metroCacheRoot, g as rotatedLogPath, h as rotateLog, i as buildCacheKey, j as validMacosResourceDestination, k as resolveArtifact, l as configDir, m as registerCache, n as METRO_NAMED_CACHE_LAYOUT, o as cacheManifestLockPath, p as readCacheManifest, r as STORE_ROOT_TAG, s as cacheNameSegment, t as LOG_ROTATE_BYTES, u as ensurePrivateDir, v as tagSharedStore, w as workspaceStateDir, x as workspaceLogDir, y as updateCacheManifest } from "./core-Btea8jlo.mjs";
2
+ import { A as storeArtifact, C as workspaceSlug, D as compiledHelper, E as createMetroGateway, L as withDirLock, O as artifactIn, S as workspaceName, T as createMetroBridge, _ as sharedStoreRoot, a as buildCacheRoot, b as workspaceId, c as cachePathSetting, d as hostSimulatorArch, f as metroCacheRoot, g as rotatedLogPath, h as rotateLog, i as buildCacheKey, j as validMacosResourceDestination, k as resolveArtifact, l as configDir, m as registerCache, n as METRO_NAMED_CACHE_LAYOUT, o as cacheManifestLockPath, p as readCacheManifest, r as STORE_ROOT_TAG, s as cacheNameSegment, t as LOG_ROTATE_BYTES, u as ensurePrivateDir, v as tagSharedStore, w as workspaceStateDir, x as workspaceLogDir, y as updateCacheManifest } from "./core-CzCQ9yWY.mjs";
3
3
  export { LOG_ROTATE_BYTES, METRO_NAMED_CACHE_LAYOUT, STORE_ROOT_TAG, artifactIn, buildCacheKey, buildCacheRoot, cacheManifestLockPath, cacheNameSegment, cachePathSetting, compiledHelper, configDir, createMetroBridge, createMetroGateway, ensurePrivateDir, hostSimulatorArch, metroCacheRoot, quotedPath, readCacheManifest, registerCache, resolveArtifact, rotateLog, rotatedLogPath, sharedStoreRoot, storeArtifact, tagSharedStore, updateCacheManifest, validMacosResourceDestination, withDirLock, workspaceId, workspaceLogDir, workspaceName, workspaceSlug, workspaceStateDir };
package/dist/state.mjs CHANGED
@@ -1,12 +1,11 @@
1
1
  import { _ as releaseClaim, f as isClaimRefusal, g as readClaimSet, x as tryAcquireClaim } from "./ownership-claim-O31Y5iIb.mjs";
2
2
  import { inspectProcessIdentity } from "./process-identity.mjs";
3
- import { M as withDirLock, S as workspaceName, g as rotatedLogPath, h as rotateLog, l as configDir, t as LOG_ROTATE_BYTES, u as ensurePrivateDir, w as workspaceStateDir } from "./core-Btea8jlo.mjs";
3
+ import { F as readStateFile, I as withStateReadCache, L as withDirLock, M as isJsonObject, N as readJsonFile, P as readJsonObject, S as workspaceName, g as rotatedLogPath, h as rotateLog, l as configDir, t as LOG_ROTATE_BYTES, u as ensurePrivateDir, w as workspaceStateDir } from "./core-CzCQ9yWY.mjs";
4
4
  import { appendFileSync, closeSync, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, realpathSync, statSync } from "node:fs";
5
5
  import { availableParallelism, homedir, loadavg } from "node:os";
6
6
  import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
7
7
  import { createHash, randomBytes } from "node:crypto";
8
8
  import { isIP } from "node:net";
9
- import { AsyncLocalStorage } from "node:async_hooks";
10
9
  import { StringDecoder } from "node:string_decoder";
11
10
  //#region state/agent-actions.ts
12
11
  const EVENTS_VERSION = 1;
@@ -307,48 +306,6 @@ function createAgentActionReader({ sessionsDirs, targets: resolveTargets, sinceT
307
306
  };
308
307
  }
309
308
  //#endregion
310
- //#region state/json-file.ts
311
- const readScope = new AsyncLocalStorage();
312
- /**
313
- * Runs `fn` with each state file read from disk at most once: inside it, including after an `await`, `readStateFile`
314
- * returns the text of the first read of that path, and a path found missing stays missing. Callers still parse every time, so no two
315
- * share a mutable object. Only for computations that write none of the files they read.
316
- */
317
- function withStateReadCache(fn) {
318
- return readScope.run(/* @__PURE__ */ new Map(), fn);
319
- }
320
- function readStateFile(path) {
321
- const cache = readScope.getStore();
322
- if (!cache) return readFileSync(path, "utf-8");
323
- const cached = cache.get(path);
324
- if (cached) {
325
- if ("missing" in cached) throw cached.missing;
326
- return cached.text;
327
- }
328
- try {
329
- const text = readFileSync(path, "utf-8");
330
- cache.set(path, { text });
331
- return text;
332
- } catch (error) {
333
- if (error.code === "ENOENT") cache.set(path, { missing: error });
334
- throw error;
335
- }
336
- }
337
- function readJsonFile(path) {
338
- return JSON.parse(readStateFile(path));
339
- }
340
- function isJsonObject(value) {
341
- return typeof value === "object" && value !== null && !Array.isArray(value);
342
- }
343
- function readJsonObject(path) {
344
- try {
345
- const value = readJsonFile(path);
346
- return isJsonObject(value) ? value : null;
347
- } catch {
348
- return null;
349
- }
350
- }
351
- //#endregion
352
309
  //#region state/agent-device-usage.ts
353
310
  function agentDeviceUsageFile() {
354
311
  return join(configDir(), "agent-device-usage.json");
@@ -403,8 +360,17 @@ function swiftpmCacheUsageFile() {
403
360
  }
404
361
  function readSwiftpmCacheUsage() {
405
362
  const value = readJsonObject(swiftpmCacheUsageFile());
406
- if (!value || value.version !== 1 || typeof value.measuredAt !== "string" || !Number.isFinite(Date.parse(value.measuredAt)) || typeof value.dir !== "string" || typeof value.present !== "boolean" || typeof value.complete !== "boolean" || !(value.bytes === null || typeof value.bytes === "number" && Number.isFinite(value.bytes) && value.bytes >= 0)) return null;
407
- return value;
363
+ if (!value) return null;
364
+ const { version, measuredAt, dir, present, bytes, complete } = value;
365
+ if (version !== 1 || typeof measuredAt !== "string" || !Number.isFinite(Date.parse(measuredAt)) || typeof dir !== "string" || typeof present !== "boolean" || typeof complete !== "boolean" || !(bytes === null || typeof bytes === "number" && Number.isFinite(bytes) && bytes >= 0)) return null;
366
+ return {
367
+ version,
368
+ measuredAt,
369
+ dir,
370
+ present,
371
+ bytes,
372
+ complete
373
+ };
408
374
  }
409
375
  //#endregion
410
376
  //#region state/paths.ts
@@ -802,23 +768,47 @@ function parseHostedMacosChoice(value) {
802
768
  };
803
769
  }
804
770
  function parseHostedMacosDevice(value) {
805
- if (!isJsonObject(value) || !parseHostedMacosChoice(value) || !hostedMacosAppSlot(value.appSlot) || Object.keys(value).some((key) => ![
771
+ if (!isJsonObject(value) || !hostedMacosAppSlot(value.appSlot) || Object.keys(value).some((key) => ![
806
772
  "architecture",
807
773
  "macosVersion",
808
774
  "appSlot"
809
775
  ].includes(key))) return null;
810
- return value;
776
+ const choice = parseHostedMacosChoice(value);
777
+ return choice && {
778
+ ...choice,
779
+ appSlot: value.appSlot
780
+ };
811
781
  }
812
782
  const AGENT_PATH = /^\/device-host\/agent\/[a-f0-9-]{36}\/$/;
813
783
  const LEASE_NAME = /^[A-Za-z0-9._-]{1,128}$/;
814
784
  const LEASE_DEVICE_KEY = /^[A-Za-z0-9_-]+(?:\.[A-Za-z0-9_-]+)+@[1-9][0-9]{0,9}$/;
785
+ function leaseName(value) {
786
+ return typeof value === "string" && LEASE_NAME.test(value);
787
+ }
815
788
  function parseHostedAgentLease(value) {
816
- if (!isJsonObject(value) || Object.keys(value).length !== (value.backend === "ios-instance" || value.backend === "android-instance" ? 5 : 4) || ![
817
- value.tenant,
818
- value.runId,
819
- value.clientId
820
- ].every((name) => typeof name === "string" && LEASE_NAME.test(name)) || typeof value.deviceKey !== "string" || !(value.backend === "ios-instance" ? /^ios:mobile:[a-fA-F0-9-]{36}$/.test(value.deviceKey) : value.backend === "android-instance" ? /^android:mobile:emulator-[0-9]+$/.test(value.deviceKey) : value.backend === void 0 && LEASE_DEVICE_KEY.test(value.deviceKey))) return null;
821
- return value;
789
+ if (!isJsonObject(value)) return null;
790
+ const { tenant, runId, clientId, deviceKey, backend } = value;
791
+ if (Object.keys(value).length !== (backend === "ios-instance" || backend === "android-instance" ? 5 : 4) || !leaseName(tenant) || !leaseName(runId) || !leaseName(clientId) || typeof deviceKey !== "string") return null;
792
+ if (backend === "ios-instance") return /^ios:mobile:[a-fA-F0-9-]{36}$/.test(deviceKey) ? {
793
+ tenant,
794
+ runId,
795
+ clientId,
796
+ deviceKey,
797
+ backend
798
+ } : null;
799
+ if (backend === "android-instance") return /^android:mobile:emulator-[0-9]+$/.test(deviceKey) ? {
800
+ tenant,
801
+ runId,
802
+ clientId,
803
+ deviceKey,
804
+ backend
805
+ } : null;
806
+ return backend === void 0 && LEASE_DEVICE_KEY.test(deviceKey) ? {
807
+ tenant,
808
+ runId,
809
+ clientId,
810
+ deviceKey
811
+ } : null;
822
812
  }
823
813
  /** A grant naming a driver this client does not know is not usable; it parses as null. */
824
814
  function parseHostedAgentGrant(value) {
@@ -1010,7 +1000,7 @@ const SETTINGS = [
1010
1000
  key: "ios.scheme",
1011
1001
  type: STRING,
1012
1002
  scopes: ["workspace", "committed"],
1013
- description: "Shared Xcode scheme to build when the workspace has several"
1003
+ description: "Shared Xcode scheme to build when the workspace has several; the --scheme flag overrides it"
1014
1004
  },
1015
1005
  {
1016
1006
  key: "ios.remote",
@@ -2062,16 +2052,19 @@ function liveNativeBuilds() {
2062
2052
  for (const name of names) {
2063
2053
  let active;
2064
2054
  try {
2065
- active = JSON.parse(readFileSync(join(dir, name, "state.json"), "utf8")).activeBuild;
2055
+ const state = JSON.parse(readFileSync(join(dir, name, "state.json"), "utf8"));
2056
+ active = isJsonObject(state) ? state.activeBuild : void 0;
2066
2057
  } catch {
2067
2058
  continue;
2068
2059
  }
2069
- if (!active || typeof active !== "object") continue;
2060
+ if (!isJsonObject(active)) continue;
2070
2061
  const { phase, claim, placement } = active;
2071
2062
  if (typeof phase !== "string" || !NATIVE_PHASES.has(phase) || placement) continue;
2072
- if (typeof claim?.root !== "string" || typeof claim.claimId !== "string") continue;
2063
+ if (!isJsonObject(claim)) continue;
2064
+ const { root, claimId } = claim;
2065
+ if (typeof root !== "string" || typeof claimId !== "string") continue;
2073
2066
  try {
2074
- if (readClaimSet(claim.root).live.some((holder) => holder.claimId === claim.claimId)) count += 1;
2067
+ if (readClaimSet(root).live.some((holder) => holder.claimId === claimId)) count += 1;
2075
2068
  } catch {}
2076
2069
  }
2077
2070
  return count;
@@ -2264,10 +2257,15 @@ function ids(value) {
2264
2257
  function readCreatedDevices() {
2265
2258
  try {
2266
2259
  const parsed = JSON.parse(readStateFile(createdDevicesFile()));
2260
+ if (!isJsonObject(parsed)) return {
2261
+ ios: /* @__PURE__ */ new Set(),
2262
+ android: /* @__PURE__ */ new Set(),
2263
+ web: /* @__PURE__ */ new Set()
2264
+ };
2267
2265
  return {
2268
- ios: new Set(ids(parsed?.ios)),
2269
- android: new Set(ids(parsed?.android)),
2270
- web: new Set(ids(parsed?.web))
2266
+ ios: new Set(ids(parsed.ios)),
2267
+ android: new Set(ids(parsed.android)),
2268
+ web: new Set(ids(parsed.web))
2271
2269
  };
2272
2270
  } catch {
2273
2271
  return {
@@ -3459,7 +3457,7 @@ const REVIEW_DECISIONS = /* @__PURE__ */ new Set([
3459
3457
  ]);
3460
3458
  function pullRequestOf(value) {
3461
3459
  if (value === null) return null;
3462
- if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
3460
+ if (!isJsonObject(value)) return void 0;
3463
3461
  const pr = value;
3464
3462
  if (!Number.isInteger(pr.number) || typeof pr.url !== "string" || typeof pr.title !== "string") return void 0;
3465
3463
  if (!PR_STATES.has(pr.state) || typeof pr.checkedAt !== "string") return void 0;
@@ -3524,7 +3522,7 @@ function readBuildDetail(root, claimId) {
3524
3522
  const file = readJsonObject(workspaceBuildDetailFile(root));
3525
3523
  if (!file || file.claimId !== claimId) return null;
3526
3524
  const detail = file.detail;
3527
- if (!detail || typeof detail !== "object" || Array.isArray(detail)) return null;
3525
+ if (!isJsonObject(detail)) return null;
3528
3526
  const raw = detail;
3529
3527
  if (typeof raw.updatedAt !== "string") return null;
3530
3528
  return {
@@ -3541,7 +3539,7 @@ function optionalString(value) {
3541
3539
  }
3542
3540
  /** An agent session as Stim wrote it to its cache or workspace state, or null when it is not one. */
3543
3541
  function agentSessionOf(value) {
3544
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3542
+ if (!isJsonObject(value)) return null;
3545
3543
  const raw = value;
3546
3544
  if (!AGENT_TOOLS.includes(raw.tool)) return null;
3547
3545
  if (typeof raw.sessionId !== "string" || !raw.sessionId || typeof raw.cwd !== "string" || !raw.cwd) return null;
@@ -3558,7 +3556,7 @@ function agentSessionOf(value) {
3558
3556
  if (title) session.title = title;
3559
3557
  if (startedAt) session.startedAt = startedAt;
3560
3558
  if (lastActiveAt) session.lastActiveAt = lastActiveAt;
3561
- if (Number.isInteger(raw.pid) && raw.pid > 0) session.pid = raw.pid;
3559
+ if (typeof raw.pid === "number" && Number.isInteger(raw.pid) && raw.pid > 0) session.pid = raw.pid;
3562
3560
  if (openUrl) session.openUrl = openUrl;
3563
3561
  if (webUrl) session.webUrl = webUrl;
3564
3562
  return session;
@@ -3718,7 +3716,7 @@ function missChange(value) {
3718
3716
  }
3719
3717
  /** A recorded miss reason in the shape status reports, without `baseline.cacheKey`; null when malformed. */
3720
3718
  function parseMissReason(value) {
3721
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3719
+ if (!isJsonObject(value)) return null;
3722
3720
  const record = value;
3723
3721
  if (typeof record.kind !== "string" || !MISS_KINDS.has(record.kind) || typeof record.summary !== "string") return null;
3724
3722
  const changes = (Array.isArray(record.changes) ? record.changes : []).map(missChange).filter((change) => change !== null).slice(0, MISS_CHANGE_CAP);
@@ -3727,7 +3725,7 @@ function parseMissReason(value) {
3727
3725
  kind: record.kind,
3728
3726
  summary: record.summary,
3729
3727
  changes,
3730
- changeCount: Number.isInteger(record.changeCount) && record.changeCount >= changes.length ? record.changeCount : changes.length,
3728
+ changeCount: typeof record.changeCount === "number" && Number.isInteger(record.changeCount) && record.changeCount >= changes.length ? record.changeCount : changes.length,
3731
3729
  baseline: baseline && typeof baseline.fingerprint === "string" && (baseline.from === "workspace" || baseline.from === "project") ? {
3732
3730
  fingerprint: baseline.fingerprint,
3733
3731
  from: baseline.from
@@ -3755,7 +3753,7 @@ function buildDiagnostics(value) {
3755
3753
  }).toSorted((a, b) => Number(b.file !== null) - Number(a.file !== null)).slice(0, DIAGNOSTIC_CAP);
3756
3754
  }
3757
3755
  function devicePlacement(value) {
3758
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3756
+ if (!isJsonObject(value)) return null;
3759
3757
  const record = value;
3760
3758
  if (record.decision !== "local" && record.decision !== "hosted" && record.decision !== "waited-locally" && record.decision !== "eas" || typeof record.reason !== "string") return null;
3761
3759
  return {
@@ -3771,7 +3769,7 @@ function readDevicePlacement(state, platform, slot = "default") {
3771
3769
  return devicePlacement(isJsonObject(device) ? device.devicePlacement : void 0);
3772
3770
  }
3773
3771
  function lastBuildReport(platform, value) {
3774
- if (!value || typeof value !== "object" || Array.isArray(value)) return null;
3772
+ if (!isJsonObject(value)) return null;
3775
3773
  const record = value;
3776
3774
  if (record.platform !== platform || record.status !== "ok" && record.status !== "failed") return null;
3777
3775
  if (typeof record.startedAt !== "string") return null;
@@ -4331,24 +4329,39 @@ function deviceHostArea(id) {
4331
4329
  }
4332
4330
  function parseHostedChoice(value) {
4333
4331
  if (!isJsonObject(value)) return null;
4334
- if (![
4335
- "deviceTypeId",
4336
- "runtimeId",
4337
- "deviceType",
4338
- "runtime"
4339
- ].every((key) => typeof value[key] === "string" && value[key])) return null;
4340
- if (value.architecture !== "arm64" && value.architecture !== "x86_64") return null;
4341
- return value;
4332
+ const { deviceTypeId, runtimeId, deviceType, runtime, architecture } = value;
4333
+ if (typeof deviceTypeId !== "string" || !deviceTypeId || typeof runtimeId !== "string" || !runtimeId || typeof deviceType !== "string" || !deviceType || typeof runtime !== "string" || !runtime || architecture !== "arm64" && architecture !== "x86_64") return null;
4334
+ return {
4335
+ deviceTypeId,
4336
+ runtimeId,
4337
+ deviceType,
4338
+ runtime,
4339
+ architecture
4340
+ };
4342
4341
  }
4343
4342
  function parseHostedDevice(value) {
4344
- if (!isJsonObject(value) || value.avdName !== void 0 || !parseHostedChoice(value)) return null;
4345
- if (typeof value.udid !== "string" || !/^[a-fA-F0-9-]{36}$/.test(value.udid) || typeof value.name !== "string") return null;
4346
- return value;
4343
+ if (!isJsonObject(value) || value.avdName !== void 0) return null;
4344
+ const choice = parseHostedChoice(value);
4345
+ const { udid, name } = value;
4346
+ if (!choice || typeof udid !== "string" || !/^[a-fA-F0-9-]{36}$/.test(udid) || typeof name !== "string") return null;
4347
+ return {
4348
+ ...choice,
4349
+ udid,
4350
+ name
4351
+ };
4347
4352
  }
4348
4353
  function parseHostedAndroidDevice(value) {
4349
4354
  if (!isJsonObject(value)) return null;
4350
- if (value.udid !== void 0 || typeof value.avdName !== "string" || !/^stim-[A-Za-z0-9._-]+$/.test(value.avdName) || !hostedConsolePort(value.consolePort) || value.serial !== `emulator-${value.consolePort}` || typeof value.systemImage !== "string" || !/^system-images;android-\d+;[^;\s]+;(arm64-v8a|x86_64)$/.test(value.systemImage) || typeof value.deviceProfile !== "string" || !value.deviceProfile || value.architecture !== "arm64-v8a" && value.architecture !== "x86_64" || !value.systemImage.endsWith(`;${value.architecture}`)) return null;
4351
- return value;
4355
+ const { avdName, consolePort, systemImage, deviceProfile, architecture } = value;
4356
+ if (value.udid !== void 0 || typeof avdName !== "string" || !/^stim-[A-Za-z0-9._-]+$/.test(avdName) || !hostedConsolePort(consolePort) || value.serial !== `emulator-${consolePort}` || typeof systemImage !== "string" || !/^system-images;android-\d+;[^;\s]+;(arm64-v8a|x86_64)$/.test(systemImage) || typeof deviceProfile !== "string" || !deviceProfile || architecture !== "arm64-v8a" && architecture !== "x86_64" || !systemImage.endsWith(`;${architecture}`)) return null;
4357
+ return {
4358
+ avdName,
4359
+ serial: `emulator-${consolePort}`,
4360
+ consolePort,
4361
+ systemImage,
4362
+ deviceProfile,
4363
+ architecture
4364
+ };
4352
4365
  }
4353
4366
  /** Android Emulator -help-port defines the supported even console ports as 5554 through 5584. */
4354
4367
  function hostedConsolePort(value) {
@@ -4645,8 +4658,8 @@ function readHostedApp(session, attempt, workerHome) {
4645
4658
  function processRecord(value) {
4646
4659
  if (!value || typeof value !== "object") return void 0;
4647
4660
  const { pid, processToken, startedAtMicros } = value;
4648
- if (!Number.isSafeInteger(pid) || pid <= 0 || typeof processToken !== "string") return void 0;
4649
- if (!Number.isSafeInteger(startedAtMicros) || startedAtMicros <= 0) return void 0;
4661
+ if (typeof pid !== "number" || !Number.isSafeInteger(pid) || pid <= 0 || typeof processToken !== "string") return void 0;
4662
+ if (typeof startedAtMicros !== "number" || !Number.isSafeInteger(startedAtMicros) || startedAtMicros <= 0) return void 0;
4650
4663
  return {
4651
4664
  pid,
4652
4665
  processToken,
@@ -4866,11 +4879,11 @@ const hostedNativeLogsDir = (home, platform) => join(home, `${platform}-logs`);
4866
4879
  function readHostedNativeLogsCheckpoint(home, platform = "ios") {
4867
4880
  try {
4868
4881
  const value = JSON.parse(readFileSync(join(hostedNativeLogsDir(home, platform), "checkpoint.json"), "utf8"));
4869
- if (!isJsonObject(value) || !Number.isSafeInteger(value.until) || value.until < 0 || value.windowMs !== void 0 && (!Number.isSafeInteger(value.windowMs) || value.windowMs < 1e3) || !Array.isArray(value.boundary) || !value.boundary.every((each) => typeof each === "string" && /^[a-f0-9]{64}$/.test(each))) return null;
4882
+ if (!isJsonObject(value) || !Number.isSafeInteger(value.until) || value.until < 0 || value.windowMs !== void 0 && (typeof value.windowMs !== "number" || !Number.isSafeInteger(value.windowMs) || value.windowMs < 1e3) || !Array.isArray(value.boundary) || !value.boundary.every((each) => typeof each === "string" && /^[a-f0-9]{64}$/.test(each))) return null;
4870
4883
  return {
4871
4884
  until: value.until,
4872
4885
  boundary: value.boundary,
4873
- ...typeof value.appAttempt === "string" && Number.isSafeInteger(value.pid) && value.pid > 0 ? {
4886
+ ...typeof value.appAttempt === "string" && typeof value.pid === "number" && Number.isSafeInteger(value.pid) && value.pid > 0 ? {
4874
4887
  appAttempt: value.appAttempt,
4875
4888
  pid: value.pid
4876
4889
  } : {},
@@ -5159,7 +5172,7 @@ function parseSetupJournal(value) {
5159
5172
  "ok",
5160
5173
  "skipped",
5161
5174
  "failed"
5162
- ].includes(step.state) && text(step.title) && (step.detail === void 0 || typeof step.detail === "string") && (step.fix === void 0 || typeof step.fix === "string")) || !Array.isArray(value.granted) || !value.granted.every((grant) => isJsonObject(grant) && knownKeys(grant, ["capability", "id"]) && capability(grant.capability) && text(grant.id)) || typeof value.done !== "boolean" || value.exit !== void 0 && (!Number.isInteger(value.exit) || value.exit < 0)) return null;
5175
+ ].includes(step.state) && text(step.title) && (step.detail === void 0 || typeof step.detail === "string") && (step.fix === void 0 || typeof step.fix === "string")) || !Array.isArray(value.granted) || !value.granted.every((grant) => isJsonObject(grant) && knownKeys(grant, ["capability", "id"]) && capability(grant.capability) && text(grant.id)) || typeof value.done !== "boolean" || value.exit !== void 0 && (typeof value.exit !== "number" || !Number.isInteger(value.exit) || value.exit < 0)) return null;
5163
5176
  return value;
5164
5177
  }
5165
5178
  function readSetupJournal(hash) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stim-cli/core",
3
- "version": "1.20.0",
3
+ "version": "1.20.1",
4
4
  "description": "Shared internal cache and runtime primitives for Stim packages.",
5
5
  "license": "MIT",
6
6
  "repository": {