stim 1.3.1 → 1.4.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.
Files changed (63) hide show
  1. package/README.md +13 -0
  2. package/dist/{android-CT-PkXUh.mjs → android-BTR1KTMP.mjs} +197 -114
  3. package/dist/{android-hMj83VpN.mjs → android-BkM4qTaN.mjs} +65 -72
  4. package/dist/{android-cas-DXorPCi_.mjs → android-cas-BjLRZyks.mjs} +15 -12
  5. package/dist/{app-install-DIp9riNx.mjs → app-install--PAocWvq.mjs} +2 -2
  6. package/dist/{build-slots-C4AouyNz.mjs → build-slots-D6sDzOe-.mjs} +7 -8
  7. package/dist/{cache-manifest-CXR-Y8pH.mjs → cache-manifest-D02TpYjA.mjs} +1 -1
  8. package/dist/cache-manifest.mjs +1 -1
  9. package/dist/cli.mjs +14 -13
  10. package/dist/collector-run.mjs +4 -4
  11. package/dist/{command-output-8wHMEde0.mjs → command-output-BuIb_ZOI.mjs} +1 -4
  12. package/dist/{config-CcefMY2Q.mjs → config-6vdGngKa.mjs} +51 -51
  13. package/dist/dependency-state-BMirHEPD.mjs +76 -0
  14. package/dist/{deps-0Yt0Inly.mjs → deps-Ck4wuTwa.mjs} +1 -1
  15. package/dist/{dev-client-VfSZNLT4.mjs → dev-client-Cd0v7iZk.mjs} +12 -12
  16. package/dist/{device-Bbv45Z9Z.mjs → device-CKXLnRRb.mjs} +7 -7
  17. package/dist/{xcode-xnxyvJkD.mjs → device-CMDFzx7C.mjs} +208 -967
  18. package/dist/{device-lease-CJwK7OZr.mjs → device-lease-DDJr85OK.mjs} +3 -3
  19. package/dist/{device-pool-CcBNTvM7.mjs → device-pool-DJvbIBGW.mjs} +4 -4
  20. package/dist/{device-remote-DAA0Zm6Z.mjs → device-remote-CMfHhoc8.mjs} +7 -6
  21. package/dist/{doctor-B4EITgQB.mjs → doctor-B_mfURdt.mjs} +536 -114
  22. package/dist/{error-diagnostics-DwhNBFDU.mjs → error-diagnostics-_VmkqRZo.mjs} +7 -6
  23. package/dist/{gc-BRu62v4o.mjs → gc-0AyxmECv.mjs} +61 -27
  24. package/dist/{guide-D95E0u3i.mjs → guide-CGxdmQ0K.mjs} +126 -7
  25. package/dist/{ios-DAju3TVy.mjs → ios-CtmGLx3X.mjs} +2667 -2607
  26. package/dist/{ios-device-CWOM_S44.mjs → ios-device-DJOi9FR5.mjs} +1 -1
  27. package/dist/{logs-CAdwW6c2.mjs → logs-BF3qPA7w.mjs} +5 -5
  28. package/dist/metro-DFLmaVz8.mjs +99 -0
  29. package/dist/named-ports-BO7Zpjp0.mjs +137 -0
  30. package/dist/{ownership-8O073GIC.mjs → ownership-BvSpZVBk.mjs} +5 -5
  31. package/dist/{ownership-DXcol4oU.mjs → ownership-C08Yzrp7.mjs} +6 -6
  32. package/dist/ports-BiKqP92J.mjs +39 -0
  33. package/dist/ports-ClqG8GPt.mjs +90 -0
  34. package/dist/{project-fKmuIaMI.mjs → project-CBFCwnN5.mjs} +2 -2
  35. package/dist/{reclaim-CUmwucWG.mjs → reclaim-DpDVo-AP.mjs} +20 -13
  36. package/dist/{reload-CWpXoOgN.mjs → reload-ByTqg6ZI.mjs} +8 -8
  37. package/dist/rolldown-runtime-BsOwBuO_.mjs +30 -0
  38. package/dist/{server-bare-DEzPZLvf.mjs → server-bare-Buk-k8tI.mjs} +3 -6
  39. package/dist/{server-expo-DizT4OMk.mjs → server-expo-D4P7iTvf.mjs} +3 -3
  40. package/dist/server-expo-Dho5Raiw.mjs +2 -0
  41. package/dist/{settings-DEJf6_kJ.mjs → settings-DwfltIXQ.mjs} +2 -2
  42. package/dist/{slot-launch-CZklomj6.mjs → slot-launch-DeKen9e5.mjs} +4 -4
  43. package/dist/{start-B5G4NjiA.mjs → start-BpKUHcU_.mjs} +15 -13
  44. package/dist/state-CDbvmZdt.mjs +131 -0
  45. package/dist/{stats-BR-kwj0B.mjs → stats-C59QLKm2.mjs} +1 -1
  46. package/dist/{stats-CaaQr4yN.mjs → stats-k5qMjyxo.mjs} +4 -4
  47. package/dist/{status-CI9clRSb.mjs → status-Y2ij3dm6.mjs} +12 -11
  48. package/dist/{stop-CjlgDk0a.mjs → stop-_P2OQwmZ.mjs} +15 -14
  49. package/dist/{stop-CQhffpmX.mjs → stop-vKmmxhea.mjs} +2 -2
  50. package/dist/supervisor-run.d.mts +3 -27
  51. package/dist/supervisor-run.mjs +7 -6
  52. package/dist/{teardown--gESs9ck.mjs → teardown-KsPpvldq.mjs} +148 -109
  53. package/dist/{workspace-process-lock-IBj1Towr.mjs → workspace-process-lock-UEIrvZMJ.mjs} +4 -4
  54. package/dist/workspace-state-Do1e2mOH.mjs +146 -0
  55. package/dist/{worktree-DtQGO9-s.mjs → worktree-BvcAIMdJ.mjs} +24 -25
  56. package/dist/{worktree-Eq6TLH8N.mjs → worktree-DWNx4tUg.mjs} +32 -2
  57. package/dist/xcode-D9tVItl-.mjs +860 -0
  58. package/package.json +7 -7
  59. package/dist/doctor-BueDfLYG.mjs +0 -530
  60. package/dist/metro-CORqXyTL.mjs +0 -184
  61. package/dist/ownership-claim-BtPtJVmq.mjs +0 -584
  62. package/dist/server-expo-44em5Pmg.mjs +0 -2
  63. package/dist/state-B3XA2wp2.mjs +0 -268
@@ -1,18 +1,17 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { u as getConfigDir } from "./config-CcefMY2Q.mjs";
3
- import { r as register } from "./cache-manifest-CXR-Y8pH.mjs";
4
- import { b as sharedBuildCache } from "./state-B3XA2wp2.mjs";
5
- import { r as pidExists } from "./metro-CORqXyTL.mjs";
6
- import { a as formatElapsed } from "./command-output-8wHMEde0.mjs";
7
- import { d as releaseClaim, i as claimRemoveCommand, m as tryAcquireClaim, t as ClaimRefusedError, u as readClaimSet } from "./ownership-claim-BtPtJVmq.mjs";
8
- import { c as androidDataPartitionSizeBytes } from "./settings-DEJf6_kJ.mjs";
2
+ import { E as ownership_claim_exports, u as getConfigDir } from "./config-6vdGngKa.mjs";
3
+ import { r as register } from "./cache-manifest-D02TpYjA.mjs";
4
+ import { l as sharedBuildCache } from "./workspace-state-Do1e2mOH.mjs";
5
+ import { r as pidExists } from "./metro-DFLmaVz8.mjs";
6
+ import { a as formatElapsed } from "./command-output-BuIb_ZOI.mjs";
7
+ import { c as androidDataPartitionSizeBytes } from "./settings-DwfltIXQ.mjs";
9
8
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
10
9
  import { createHash } from "node:crypto";
11
10
  import { join } from "node:path";
12
- import { existsSync as existsSync$1, lstatSync as lstatSync$1, mkdirSync as mkdirSync$1, openSync as openSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, renameSync as renameSync$1, rmSync as rmSync$1, statSync as statSync$1, utimesSync, writeFileSync as writeFileSync$1 } from "fs";
11
+ import { existsSync as existsSync$1, lstatSync as lstatSync$1, mkdirSync as mkdirSync$1, openSync as openSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, renameSync as renameSync$1, rmSync as rmSync$1, statSync as statSync$1, writeFileSync as writeFileSync$1 } from "fs";
13
12
  import { homedir } from "os";
14
- import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, resolve as resolve$1 } from "path";
15
- import { buildCacheKey } from "@stim-cli/core";
13
+ import { dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, resolve as resolve$1 } from "path";
14
+ import { buildCacheKey, resolveArtifact, storeArtifact } from "@stim-cli/core";
16
15
  import * as expoFingerprint from "@expo/fingerprint";
17
16
  import { buildUploadTimeoutMs } from "@stim-cli/cache";
18
17
  const ASSET_MANIFEST_FILE = "assets-manifest.json";
@@ -163,16 +162,6 @@ function safeList$1(dir) {
163
162
  function entryDir(platform, key, root = sharedBuildCache()) {
164
163
  return join$1(root, platform, key);
165
164
  }
166
- function artifactIn(dir) {
167
- if (!existsSync$1(dir)) return null;
168
- let found;
169
- try {
170
- found = readdirSync$1(dir).find((f) => f.endsWith(".app") || f.endsWith(".apk"));
171
- } catch {
172
- return null;
173
- }
174
- return found ? join$1(dir, found) : null;
175
- }
176
165
  const FINGERPRINT_PLATFORMS = /* @__PURE__ */ new Set(["ios", "android"]);
177
166
  /**
178
167
  * Paths that exist in a working checkout and not in a fresh one, which
@@ -211,55 +200,25 @@ function registerOnce(root) {
211
200
  } catch {}
212
201
  }
213
202
  function resolveBuild(platform, key, root = sharedBuildCache()) {
214
- const hit = artifactIn(entryDir(platform, key, root));
215
- if (!hit) return null;
216
- try {
217
- utimesSync(dirname$1(hit), /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date());
218
- } catch {}
219
- return hit;
203
+ return resolveArtifact(entryDir(platform, key, root));
220
204
  }
221
205
  function storeBuild(platform, key, buildPath, rootOrOptions = {}) {
222
206
  const options = typeof rootOrOptions === "string" ? { root: rootOrOptions } : rootOrOptions || {};
223
207
  const root = options.root || sharedBuildCache();
224
- const overwrite = Boolean(options.overwrite);
225
208
  if (!buildPath || !existsSync$1(buildPath)) throw new Error(`No build to store at ${buildPath}`);
226
209
  registerOnce(root);
227
- const dest = entryDir(platform, key, root);
228
- const existing = artifactIn(dest);
229
- if (existing && !overwrite) return existing;
230
- const staging = `${dest}.staging-${process.pid}`;
231
- rmSync$1(staging, {
232
- recursive: true,
233
- force: true
234
- });
235
- mkdirSync$1(staging, { recursive: true });
236
- try {
237
- getExecutor().runFile("cp", [
238
- "-c",
239
- "-R",
240
- buildPath,
241
- join$1(staging, basename$1(buildPath))
242
- ]);
243
- } catch {
244
- getExecutor().runFile("cp", [
245
- "-R",
246
- buildPath,
247
- join$1(staging, basename$1(buildPath))
248
- ]);
249
- }
250
- if (Array.isArray(options.sources)) try {
251
- writeFileSync$1(join$1(staging, SOURCES_FILE), JSON.stringify(options.sources));
252
- } catch {}
253
- if (options.assetManifest) try {
254
- writeFileSync$1(join$1(staging, ASSET_MANIFEST_FILE), JSON.stringify(options.assetManifest));
255
- } catch {}
256
- mkdirSync$1(dirname$1(dest), { recursive: true });
257
- rmSync$1(dest, {
258
- recursive: true,
259
- force: true
210
+ return storeArtifact(entryDir(platform, key, root), buildPath, {
211
+ runFile: getExecutor().runFile,
212
+ overwrite: Boolean(options.overwrite),
213
+ writeMetadata: (staging) => {
214
+ if (Array.isArray(options.sources)) try {
215
+ writeFileSync$1(join$1(staging, SOURCES_FILE), JSON.stringify(options.sources));
216
+ } catch {}
217
+ if (options.assetManifest) try {
218
+ writeFileSync$1(join$1(staging, ASSET_MANIFEST_FILE), JSON.stringify(options.assetManifest));
219
+ } catch {}
220
+ }
260
221
  });
261
- renameSync$1(staging, dest);
262
- return artifactIn(dest);
263
222
  }
264
223
  const PROVIDER_DOWNLOAD_DIR = "cache-provider";
265
224
  function providerDownloadPath(workspacePath) {
@@ -498,13 +457,13 @@ function toRecord(holder) {
498
457
  }
499
458
  function readBuildLock(pathOrSpec) {
500
459
  const path = typeof pathOrSpec === "string" ? pathOrSpec : buildLockPath(pathOrSpec.platform, pathOrSpec.key);
501
- const survey = readClaimSet(path);
460
+ const survey = (0, ownership_claim_exports.readClaimSet)(path);
502
461
  const holder = survey.live[0] ?? survey.dead[0];
503
462
  return holder ? toRecord(holder) : null;
504
463
  }
505
464
  function acquireBuildLock({ platform, key, root = null, logFile = null }) {
506
465
  const path = buildLockPath(platform, key);
507
- const attempt = tryAcquireClaim({
466
+ const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
508
467
  root: path,
509
468
  mode: "exclusive",
510
469
  label: label(platform),
@@ -531,7 +490,7 @@ function acquireBuildLock({ platform, key, root = null, logFile = null }) {
531
490
  tookOver: toRecord(reaped)
532
491
  } : handle;
533
492
  }
534
- if (attempt.pending) releaseClaim(attempt.pending);
493
+ if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
535
494
  const holder = attempt.held ?? attempt.waitingFor?.[0];
536
495
  return holder ? {
537
496
  held: toRecord(holder),
@@ -539,7 +498,7 @@ function acquireBuildLock({ platform, key, root = null, logFile = null }) {
539
498
  } : { path };
540
499
  }
541
500
  function releaseBuildLock(handle) {
542
- return releaseClaim(handle?.claim);
501
+ return (0, ownership_claim_exports.releaseClaim)(handle?.claim);
543
502
  }
544
503
  function listBuildLocks() {
545
504
  const dir = buildLocksDir();
@@ -560,7 +519,7 @@ function listBuildLocks() {
560
519
  }
561
520
  const stem = name.slice(0, -5);
562
521
  const cut = stem.indexOf("-");
563
- const survey = readClaimSet(path);
522
+ const survey = (0, ownership_claim_exports.readClaimSet)(path);
564
523
  const holder = survey.live[0] ?? survey.dead[0];
565
524
  const info = holder ? toRecord(holder) : null;
566
525
  locks.push({
@@ -601,9 +560,9 @@ async function waitForBuild({ platform, key, resolve = resolveBuild, intervalMs
601
560
  hit,
602
561
  waitedMs: now() - started
603
562
  };
604
- const survey = readClaimSet(path);
563
+ const survey = (0, ownership_claim_exports.readClaimSet)(path);
605
564
  const unresolved = survey.unresolved[0];
606
- if (unresolved) throw new ClaimRefusedError({
565
+ if (unresolved) throw new ownership_claim_exports.ClaimRefusedError({
607
566
  claimPath: unresolved.path,
608
567
  root: path,
609
568
  label: label(platform),
@@ -626,7 +585,7 @@ async function waitForBuild({ platform, key, resolve = resolveBuild, intervalMs
626
585
  }
627
586
  const elapsed = now() - started;
628
587
  if (elapsed >= ceilingMs) {
629
- const err = /* @__PURE__ */ new Error(`Waited ${formatElapsed(elapsed)} for ${holder?.projectRoot || "another workspace"}'s ${platform} build of ${key} without an artifact, and pid ${holder?.pid} is still the live holder. The lock is ${path}; remove it if that process is not really building:\n ${claimRemoveCommand(path)}`);
588
+ const err = /* @__PURE__ */ new Error(`Waited ${formatElapsed(elapsed)} for ${holder?.projectRoot || "another workspace"}'s ${platform} build of ${key} without an artifact, and pid ${holder?.pid} is still the live holder. The lock is ${path}; remove it if that process is not really building:\n ${(0, ownership_claim_exports.claimRemoveCommand)(path)}`);
630
589
  err.code = "STIM_BUILD_WAIT_TIMEOUT";
631
590
  err.lockPath = path;
632
591
  err.holder = holder ?? void 0;
@@ -750,14 +709,48 @@ function pickDefaultSystemImage(images, { systemImage, hostArch } = {}) {
750
709
  if (matching.length === 0) return null;
751
710
  return matching.toSorted((a, b) => pageSizeRank(a) - pageSizeRank(b) || b.api - a.api || (b.tag === "google_apis" ? 1 : 0) - (a.tag === "google_apis" ? 1 : 0))[0] ?? null;
752
711
  }
753
- function createOwnedAvd(label, { systemImage } = {}) {
712
+ async function createOwnedAvd(label, { systemImage, spawn = (...args) => getExecutor().spawn(...args) } = {}) {
754
713
  const pick = pickDefaultSystemImage(listInstalledSystemImages(), { systemImage });
755
714
  if (!pick) {
756
715
  const arch = hostSystemImageArch();
757
716
  throw new Error(`No ${arch} Android system image is installed. Install one, e.g.: sdkmanager "system-images;android-36;google_apis;${arch}"`);
758
717
  }
759
718
  const avdName = ownedAvdName(label);
760
- getExecutor().run(`echo no | ${androidTool("avdmanager")} create avd -n "${avdName}" -k "${pick.pkg}" --device "${DEFAULT_AVD_DEVICE}"`);
719
+ const tool = androidToolPath("avdmanager");
720
+ const args = [
721
+ "create",
722
+ "avd",
723
+ "-n",
724
+ avdName,
725
+ "-k",
726
+ pick.pkg,
727
+ "--device",
728
+ DEFAULT_AVD_DEVICE
729
+ ];
730
+ const child = spawn(tool, args, {
731
+ detached: true,
732
+ stdio: [
733
+ "pipe",
734
+ "pipe",
735
+ "pipe"
736
+ ]
737
+ });
738
+ let stderr = "";
739
+ child.stdout?.on("data", () => {});
740
+ child.stderr?.on("data", (chunk) => {
741
+ stderr = (stderr + String(chunk)).slice(-67108864);
742
+ });
743
+ const completed = new Promise((finish, reject) => {
744
+ child.once("error", reject);
745
+ child.once("close", (code, signal) => finish({
746
+ code,
747
+ signal
748
+ }));
749
+ });
750
+ child.stdin?.on("error", () => {});
751
+ child.stdin?.end("no\n");
752
+ const result = await completed;
753
+ if (result.code !== 0) throw new Error(`Command failed: ${tool} ${args.join(" ")} (${result.signal ?? result.code})\n${stderr.trim()}`);
761
754
  return {
762
755
  avdName,
763
756
  systemImage: pick.pkg
@@ -1,11 +1,11 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { k as withDirLock, u as getConfigDir } from "./config-CcefMY2Q.mjs";
3
- import { r as register } from "./cache-manifest-CXR-Y8pH.mjs";
4
- import { D as workspaceLogsDir, x as sharedCcache, y as ensureWorkspaceStorage } from "./state-B3XA2wp2.mjs";
2
+ import { D as withDirLock, u as getConfigDir } from "./config-6vdGngKa.mjs";
3
+ import { r as register } from "./cache-manifest-D02TpYjA.mjs";
4
+ import { _ as workspaceLogsDir, c as ensureWorkspaceStorage, u as sharedCcache } from "./workspace-state-Do1e2mOH.mjs";
5
5
  import { n as stripAnsi, r as waitForChild, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
6
- import { l as phaseLine } from "./command-output-8wHMEde0.mjs";
7
- import { n as androidHome } from "./android-hMj83VpN.mjs";
8
- import { f as startBuildHeartbeat, r as HEARTBEAT_INTERVAL_MS } from "./xcode-xnxyvJkD.mjs";
6
+ import { l as phaseLine } from "./command-output-BuIb_ZOI.mjs";
7
+ import { n as androidHome } from "./android-BkM4qTaN.mjs";
8
+ import { d as startBuildHeartbeat, n as HEARTBEAT_INTERVAL_MS } from "./xcode-D9tVItl-.mjs";
9
9
  import { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync } from "node:fs";
10
10
  import { createHash, randomUUID } from "node:crypto";
11
11
  import { dirname, isAbsolute, join, relative } from "node:path";
@@ -717,7 +717,10 @@ function gradleArgs(task, { buildCache = true, abi = null } = {}) {
717
717
  async function buildAndroid({ root, logWriter, variant = null, abi = null }, { spawnFn = null, now = Date.now, env = process.env, buildCache = true, ccache = null, cas = null, pch = "auto", compilerCacheDisabled = false, heartbeatMs = HEARTBEAT_INTERVAL_MS, estimateMs = null, onHeartbeat = (line) => console.error(line), onNote = (line) => console.error(line) } = {}) {
718
718
  const project = discoverAndroidProject(root);
719
719
  if (project.failed) return {
720
- ...project,
720
+ ok: false,
721
+ code: project.code,
722
+ reason: project.reason,
723
+ remedy: project.remedy,
721
724
  diagnostics: [],
722
725
  truncated: 0,
723
726
  lastLines: [],
@@ -730,7 +733,7 @@ async function buildAndroid({ root, logWriter, variant = null, abi = null }, { s
730
733
  hasLocalProperties: existsSync(join(project.androidDir, "local.properties"))
731
734
  });
732
735
  if (refusal) return {
733
- failed: true,
736
+ ok: false,
734
737
  ...refusal,
735
738
  diagnostics: [],
736
739
  truncated: 0,
@@ -841,7 +844,7 @@ async function buildAndroid({ root, logWriter, variant = null, abi = null }, { s
841
844
  const how = result.signal ? `signal ${result.signal}` : `exit code ${result.code}`;
842
845
  const { shown, truncated } = capDiagnostics(extractGradleDiagnostics(transcript));
843
846
  return {
844
- failed: true,
847
+ ok: false,
845
848
  code: BUILD_ERROR,
846
849
  reason: `\`./gradlew ${task}\` failed (${how}).`,
847
850
  diagnostics: shown,
@@ -853,7 +856,7 @@ async function buildAndroid({ root, logWriter, variant = null, abi = null }, { s
853
856
  }
854
857
  const located = locateApk(root, transcript, variant);
855
858
  if (!located.apkPath && located.candidates?.length) return {
856
- failed: true,
859
+ ok: false,
857
860
  code: BUILD_ERROR,
858
861
  reason: `\`./gradlew ${task}\` left ${located.candidates.length} debug APKs under ${apkOutputsDir(root)}, and nothing says which flavor to install.`,
859
862
  remedy: `Set the android.variant setting to the variant to install -- e.g. {"android": {"variant": "${variantNameOf(relative(apkOutputsDir(root), located.candidates[0]).split("/").slice(0, -1))}"}} in .stim.json.`,
@@ -864,7 +867,7 @@ async function buildAndroid({ root, logWriter, variant = null, abi = null }, { s
864
867
  ccache: ccacheActivity
865
868
  };
866
869
  if (!located.apkPath) return {
867
- failed: true,
870
+ ok: false,
868
871
  code: BUILD_ERROR,
869
872
  reason: variant ? `\`./gradlew ${task}\` succeeded but produced no APK under ${apkOutputsDir(root)} for variant "${variant}".` : `\`./gradlew ${task}\` succeeded but produced no APK in ${debugApkDir(root)}.`,
870
873
  remedy: variant ? `Check that the android.variant setting ("${variant}") names a real variant (\`./gradlew :app:tasks\` lists the assemble tasks).` : `Check that ${task} builds the app module (\`./gradlew :app:${task}\` in android/) and that no flavour redirects the output.`,
@@ -894,7 +897,7 @@ function spawnFailure(err, project, durationMs) {
894
897
  const message = String(nodeErr?.message || err || "");
895
898
  const permissionDenied = nodeErr?.code === "EACCES" || /EACCES|permission denied/i.test(message);
896
899
  return {
897
- failed: true,
900
+ ok: false,
898
901
  code: BUILD_ERROR,
899
902
  reason: `Could not run ${project.gradlew}: ${message}`,
900
903
  remedy: permissionDenied ? `Make the wrapper executable: \`chmod +x ${project.gradlew}\`.` : "Check that the gradle wrapper is intact (android/gradlew and android/gradle/wrapper/) and that JAVA_HOME points at a JDK 17 install.",
@@ -1,7 +1,7 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
2
  import { o as parseNdjsonText } from "./ndjson-BZwI1NBv.mjs";
3
3
  import { t as DEV_MENU_LAUNCH_ARGS } from "./ios-device-D3pzTKQE.mjs";
4
- import { s as isAppLaunchError } from "./command-output-8wHMEde0.mjs";
4
+ import { s as isAppLaunchError } from "./command-output-BuIb_ZOI.mjs";
5
5
  import { l as iosSimulatorFailureAdvice, p as listUserApps, w as uninstallIosApp } from "./ios-Deh5js5V.mjs";
6
6
  import { closeSync, lstatSync, openSync, readFileSync, readSync, readdirSync } from "node:fs";
7
7
  import { createHash } from "node:crypto";
@@ -1136,7 +1136,7 @@ function unverifiedLaunchLines({ platform, metroPort, waitedMs = VERIFY_TIMEOUT_
1136
1136
  if (platform === "ios") {
1137
1137
  push(picker);
1138
1138
  if (url && udid) push(`Retry the deep link: xcrun simctl openurl ${udid} '${url}'`);
1139
- else if (udid && bundleId) push(`Re-launch: xcrun simctl launch --console ${udid} ${bundleId}`);
1139
+ else if (udid && bundleId) push(`Re-launch: xcrun simctl launch --terminate-running-process ${udid} ${bundleId}`);
1140
1140
  } else {
1141
1141
  if (url && serial) push(`Re-send the dev-client deep link -- this is the command that points the app at THIS workspace's Metro: adb -s ${serial} shell am start -a android.intent.action.VIEW -d ${deviceShellArg(deviceShellArg(url))} --ez ${ANDROID_DISABLE_AUTO_LAUNCH_EXTRA} true`);
1142
1142
  push(picker);
@@ -1,6 +1,5 @@
1
- import { u as getConfigDir } from "./config-CcefMY2Q.mjs";
2
- import { a as formatElapsed } from "./command-output-8wHMEde0.mjs";
3
- import { d as releaseClaim, m as tryAcquireClaim, s as isClaimRefusal, u as readClaimSet } from "./ownership-claim-BtPtJVmq.mjs";
1
+ import { E as ownership_claim_exports, u as getConfigDir } from "./config-6vdGngKa.mjs";
2
+ import { a as formatElapsed } from "./command-output-BuIb_ZOI.mjs";
4
3
  import { readdirSync, statSync } from "node:fs";
5
4
  import { join } from "node:path";
6
5
  //#region src/engine/build-slots.ts
@@ -39,7 +38,7 @@ function tryAcquireBuildSlot({ max, root = null, logFile = null }) {
39
38
  const path = buildSlotPath(index);
40
39
  let attempt;
41
40
  try {
42
- attempt = tryAcquireClaim({
41
+ attempt = (0, ownership_claim_exports.tryAcquireClaim)({
43
42
  root: path,
44
43
  mode: "exclusive",
45
44
  label: SLOT_LABEL,
@@ -50,11 +49,11 @@ function tryAcquireBuildSlot({ max, root = null, logFile = null }) {
50
49
  }
51
50
  });
52
51
  } catch (err) {
53
- if (!isClaimRefusal(err)) throw err;
52
+ if (!(0, ownership_claim_exports.isClaimRefusal)(err)) throw err;
54
53
  refusal ??= err;
55
54
  continue;
56
55
  }
57
- if (attempt.pending) releaseClaim(attempt.pending);
56
+ if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
58
57
  if (!attempt.acquired) {
59
58
  busy = true;
60
59
  continue;
@@ -111,7 +110,7 @@ async function acquireBuildSlot({ max, root = null, logFile = null, now = Date.n
111
110
  }
112
111
  function releaseBuildSlot(handle) {
113
112
  if (!handle || handle.unlimited) return false;
114
- return releaseClaim(handle.claim);
113
+ return (0, ownership_claim_exports.releaseClaim)(handle.claim);
115
114
  }
116
115
  function listBuildSlots() {
117
116
  const dir = buildSlotsDir();
@@ -130,7 +129,7 @@ function listBuildSlots() {
130
129
  } catch {
131
130
  continue;
132
131
  }
133
- const survey = readClaimSet(path);
132
+ const survey = (0, ownership_claim_exports.readClaimSet)(path);
134
133
  const holder = survey.live[0] ?? survey.dead[0];
135
134
  const info = holder ? toRecord(holder) : null;
136
135
  const index = Number(name.slice(5));
@@ -1,4 +1,4 @@
1
- import { u as getConfigDir } from "./config-CcefMY2Q.mjs";
1
+ import { u as getConfigDir } from "./config-6vdGngKa.mjs";
2
2
  import { existsSync } from "fs";
3
3
  import { homedir } from "os";
4
4
  import { join, resolve } from "path";
@@ -1,2 +1,2 @@
1
- import { a as unregister, i as registeredCaches, n as readManifest, r as register, t as manifestPath } from "./cache-manifest-CXR-Y8pH.mjs";
1
+ import { a as unregister, i as registeredCaches, n as readManifest, r as register, t as manifestPath } from "./cache-manifest-D02TpYjA.mjs";
2
2
  export { manifestPath, readManifest, register, registeredCaches, unregister };
package/dist/cli.mjs CHANGED
@@ -3,19 +3,20 @@ import { readFileSync } from "node:fs";
3
3
  import { Command } from "commander";
4
4
  //#region bin/cli.ts
5
5
  const commands = /* @__PURE__ */ new Map([
6
- ["doctor", () => import("./doctor-BueDfLYG.mjs")],
7
- ["worktree", () => import("./worktree-DtQGO9-s.mjs")],
8
- ["start", () => import("./start-B5G4NjiA.mjs")],
9
- ["stop", () => import("./stop-CQhffpmX.mjs")],
10
- ["ios", () => import("./ios-DAju3TVy.mjs")],
11
- ["android", () => import("./android-CT-PkXUh.mjs")],
12
- ["reload", () => import("./reload-CWpXoOgN.mjs")],
13
- ["device", () => import("./device-Bbv45Z9Z.mjs")],
14
- ["logs", () => import("./logs-CAdwW6c2.mjs")],
15
- ["status", () => import("./status-CI9clRSb.mjs")],
16
- ["stats", () => import("./stats-CaaQr4yN.mjs")],
17
- ["gc", () => import("./gc-BRu62v4o.mjs")],
18
- ["guide", () => import("./guide-D95E0u3i.mjs")]
6
+ ["doctor", () => import("./doctor-B_mfURdt.mjs")],
7
+ ["worktree", () => import("./worktree-BvcAIMdJ.mjs")],
8
+ ["start", () => import("./start-BpKUHcU_.mjs")],
9
+ ["ports", () => import("./ports-BiKqP92J.mjs")],
10
+ ["stop", () => import("./stop-vKmmxhea.mjs")],
11
+ ["ios", () => import("./ios-CtmGLx3X.mjs")],
12
+ ["android", () => import("./android-BTR1KTMP.mjs")],
13
+ ["reload", () => import("./reload-ByTqg6ZI.mjs")],
14
+ ["device", () => import("./device-CKXLnRRb.mjs")],
15
+ ["logs", () => import("./logs-BF3qPA7w.mjs")],
16
+ ["status", () => import("./status-Y2ij3dm6.mjs")],
17
+ ["stats", () => import("./stats-k5qMjyxo.mjs")],
18
+ ["gc", () => import("./gc-0AyxmECv.mjs")],
19
+ ["guide", () => import("./guide-CGxdmQ0K.mjs")]
19
20
  ]);
20
21
  const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf-8"));
21
22
  const program = new Command();
@@ -1,10 +1,10 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { I as deviceSlotKey, T as captureProcessToken, V as validateDeviceSlot } from "./config-CcefMY2Q.mjs";
2
+ import { P as deviceSlotKey, w as process_identity_exports, z as validateDeviceSlot } from "./config-6vdGngKa.mjs";
3
3
  import { r as createNdjsonWriter } from "./ndjson-BZwI1NBv.mjs";
4
- import { D as workspaceLogsDir } from "./state-B3XA2wp2.mjs";
4
+ import { _ as workspaceLogsDir } from "./workspace-state-Do1e2mOH.mjs";
5
5
  import { t as createLineReader } from "./process-output-C7IlIzd7.mjs";
6
6
  import { i as startIosDeviceConsole, r as parseDeviceConsoleLine } from "./ios-device-D3pzTKQE.mjs";
7
- import { a as unregisterCollector, i as registerCollector, r as readCollectors, t as collectorProcessTitle } from "./ownership-8O073GIC.mjs";
7
+ import { a as unregisterCollector, i as registerCollector, r as readCollectors, t as collectorProcessTitle } from "./ownership-BvSpZVBk.mjs";
8
8
  import { a as watchAppPid, i as waitForAppPid, n as parseLogcatLine, r as startAndroidLogcat, t as androidClockOffset } from "./android-BW8i_YxU.mjs";
9
9
  import { existsSync } from "node:fs";
10
10
  import { isAbsolute, join, resolve } from "node:path";
@@ -301,7 +301,7 @@ async function runCollector({ slot = "default", platform, root, udid = null, app
301
301
  };
302
302
  const writer = createNdjsonWriter(join(workspaceLogsDir(root), "device.ndjson"));
303
303
  const startedAt = new Date(now()).toISOString();
304
- const processToken = captureProcessToken(process.pid);
304
+ const processToken = (0, process_identity_exports.captureProcessToken)(process.pid);
305
305
  if (!processToken) {
306
306
  stderr("Stim collector: could not capture process identity; refusing to start an unmanaged collector.");
307
307
  writer.close();
@@ -1,4 +1,5 @@
1
1
  import { n as stripAnsi } from "./process-output-C7IlIzd7.mjs";
2
+ import { quotedPath } from "@stim-cli/core";
2
3
  //#region src/launch-error-preview.ts
3
4
  function framePriority(frame, root) {
4
5
  const normalized = frame.replaceAll("\\", "/");
@@ -244,10 +245,6 @@ function releasedLeaseFact(lease) {
244
245
  function phaseLine(label, text) {
245
246
  return ` ${String(label).padEnd(LABEL_WIDTH)} ${text}`;
246
247
  }
247
- /** Quote a path for a shell command Stim prints for a human to copy and run. */
248
- function quotedPath(path) {
249
- return `'${path.replaceAll("'", "'\\''")}'`;
250
- }
251
248
  function shortUdid(udid) {
252
249
  const text = String(udid ?? "");
253
250
  return text.length > 4 ? `${text.slice(0, 4)}..` : text;
@@ -1,9 +1,10 @@
1
+ import { n as __exportAll, r as __reExport } from "./rolldown-runtime-BsOwBuO_.mjs";
1
2
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
3
+ import { join } from "node:path";
2
4
  import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
3
5
  import { homedir } from "os";
4
- import { dirname, isAbsolute, join, normalize } from "path";
5
- import { withDirLock } from "@stim-cli/core";
6
- import { capture, check, decode } from "unique-pid";
6
+ import { dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, normalize } from "path";
7
+ import { configDir, withDirLock } from "@stim-cli/core";
7
8
  //#region src/device-slots.ts
8
9
  const DEFAULT_DEVICE_SLOT = "default";
9
10
  function validateDeviceSlot(slot = "default") {
@@ -114,7 +115,7 @@ function resolveWorkspaceRealish(workspacePath) {
114
115
  } catch {
115
116
  return null;
116
117
  }
117
- current = `${target.startsWith("/") ? target : normalize(join(dirname(prefix), target))}${normalized.slice(prefix.length)}`;
118
+ current = `${target.startsWith("/") ? target : normalize(join$1(dirname$1(prefix), target))}${normalized.slice(prefix.length)}`;
118
119
  followedSymlink = true;
119
120
  break;
120
121
  }
@@ -136,7 +137,7 @@ function listMountedVolumes({ statFn = statSync } = {}) {
136
137
  }
137
138
  try {
138
139
  for (const name of readdirSync("/Volumes")) {
139
- const path = join("/Volumes", name);
140
+ const path = join$1("/Volumes", name);
140
141
  let entryDev;
141
142
  try {
142
143
  entryDev = statFn(path).dev;
@@ -175,50 +176,41 @@ function formatBytes(bytes) {
175
176
  return `${Math.round(bytes / 1024)}K`;
176
177
  }
177
178
  //#endregion
178
- //#region src/process-identity.ts
179
- function sameProcessRecord(a, b) {
180
- if (!a || !b) return !a && !b;
181
- return a.pid === b.pid && a.processToken === b.processToken;
182
- }
183
- function captureProcessIdentity(pid) {
184
- const result = capture(pid);
185
- if (result.ok) return {
186
- ok: true,
187
- token: result.value
188
- };
189
- return {
190
- ok: false,
191
- reason: `${result.error.code} (${result.error.message})`
192
- };
193
- }
194
- function captureProcessToken(pid) {
195
- const captured = captureProcessIdentity(pid);
196
- return captured.ok ? captured.token : null;
197
- }
198
- function inspectProcessIdentity(record) {
199
- if (!record || typeof record.processToken !== "string") return "unknown";
200
- const decoded = decode(record.processToken);
201
- if (!decoded.ok || decoded.value.pid !== record.pid) return "unknown";
202
- const result = check(record.processToken);
203
- return result.ok ? result.value : "unknown";
204
- }
205
- async function waitForProcessExit(record, timeoutMs) {
206
- const deadline = Date.now() + timeoutMs;
207
- while (Date.now() <= deadline) {
208
- const identity = inspectProcessIdentity(record);
209
- if (identity === "different" || identity === "gone") return true;
210
- if (Date.now() >= deadline) return false;
211
- await new Promise((resolve) => setTimeout(resolve, 100));
212
- }
213
- return false;
179
+ //#region src/ownership-claim.ts
180
+ var ownership_claim_exports = /* @__PURE__ */ __exportAll({});
181
+ import * as import__stim_cli_core_ownership_claim from "@stim-cli/core/ownership-claim";
182
+ __reExport(ownership_claim_exports, import__stim_cli_core_ownership_claim);
183
+ //#endregion
184
+ //#region src/avd-claim.ts
185
+ function acquireAvdClaim(avdName) {
186
+ const root = join(configDir(), "avd-locks", `${encodeURIComponent(avdName.toLowerCase())}.lock`);
187
+ const label = `AVD ${avdName}`;
188
+ const attempt = (0, ownership_claim_exports.tryAcquireClaim)({
189
+ root,
190
+ mode: "exclusive",
191
+ label
192
+ });
193
+ if (attempt.pending) (0, ownership_claim_exports.releaseClaim)(attempt.pending);
194
+ if (attempt.acquired) return attempt.acquired;
195
+ throw new ownership_claim_exports.ClaimRefusedError({
196
+ root,
197
+ claimPath: attempt.held?.path ?? attempt.waitingFor?.[0]?.path ?? root,
198
+ label,
199
+ reason: "another operation is creating, recovering, or tearing down this AVD"
200
+ });
214
201
  }
215
202
  //#endregion
203
+ //#region src/process-identity.ts
204
+ var process_identity_exports = /* @__PURE__ */ __exportAll({});
205
+ import * as import__stim_cli_core_process_identity from "@stim-cli/core/process-identity";
206
+ __reExport(process_identity_exports, import__stim_cli_core_process_identity);
207
+ //#endregion
216
208
  //#region src/config.ts
217
209
  function getConfigDir() {
218
- return process.env.STIM_HOME || join(homedir(), ".stim");
210
+ return process.env.STIM_HOME || join$1(homedir(), ".stim");
219
211
  }
220
212
  function getConfigPath() {
221
- return join(getConfigDir(), "config.json");
213
+ return join$1(getConfigDir(), "config.json");
222
214
  }
223
215
  function ensureDir() {
224
216
  const dir = getConfigDir();
@@ -226,7 +218,7 @@ function ensureDir() {
226
218
  }
227
219
  const LOCK_DIR_NAME = "config.lock";
228
220
  function lockPath() {
229
- return join(getConfigDir(), LOCK_DIR_NAME);
221
+ return join$1(getConfigDir(), LOCK_DIR_NAME);
230
222
  }
231
223
  function withConfigLock(fn) {
232
224
  return withDirLock(lockPath(), fn, { ensureParent: ensureDir });
@@ -308,7 +300,7 @@ function getProject(projectPath) {
308
300
  return loadConfig()?.projects?.[projectPath] || null;
309
301
  }
310
302
  function requireAbsoluteProjectPath(projectPath) {
311
- if (isAbsolute(projectPath)) return;
303
+ if (isAbsolute$1(projectPath)) return;
312
304
  throw new Error(`Project key "${projectPath}" is not an absolute path. The registry is keyed by the realpath of a workspace root, so a relative key can never resolve.`);
313
305
  }
314
306
  function upsertProject(projectPath, fields) {
@@ -331,6 +323,8 @@ function removeProject(projectPath) {
331
323
  withConfigLock(() => {
332
324
  const cfg = loadConfig();
333
325
  if (!cfg?.projects?.[projectPath]) return;
326
+ if (Object.keys(cfg.projects[projectPath].ports ?? {}).length) throw new Error(`Named ports remain for ${projectPath}; stop or release them before removing its registry entry.`);
327
+ for (const { platforms } of projectDeviceSlots(cfg.projects[projectPath])) if (platforms.android?.owned && platforms.android.avdName) (0, ownership_claim_exports.releaseClaim)(acquireAvdClaim(platforms.android.avdName));
334
328
  delete cfg.projects[projectPath];
335
329
  saveConfig(cfg);
336
330
  });
@@ -339,7 +333,10 @@ function claimMetroPort(projectPath, port) {
339
333
  return withConfigLock(() => {
340
334
  const cfg = ensureConfig();
341
335
  if (!cfg.projects[projectPath]) throw new Error(`Project not registered: ${projectPath}`);
342
- for (const [path, proj] of Object.entries(cfg.projects)) if (path !== projectPath && proj?.metroPort === port) return null;
336
+ for (const [path, proj] of Object.entries(cfg.projects)) {
337
+ if (path !== projectPath && proj?.metroPort === port) return null;
338
+ if (Object.values(proj?.ports ?? {}).includes(port)) return null;
339
+ }
343
340
  cfg.projects[projectPath].metroPort = port;
344
341
  saveConfig(cfg);
345
342
  return port;
@@ -364,13 +361,16 @@ function releaseAndroidConsolePort(projectPath, consolePort, slot = "default") {
364
361
  return true;
365
362
  });
366
363
  }
367
- function clearDevice(projectPath, platform, slot = "default") {
368
- withConfigLock(() => {
364
+ function clearDevice(projectPath, platform, slot = "default", expectedId) {
365
+ return withConfigLock(() => {
369
366
  const cfg = loadConfig();
370
367
  const project = cfg?.projects?.[projectPath];
371
- if (!cfg || !project) return;
368
+ if (!cfg || !project) return false;
369
+ const current = deviceSlotPlatforms(project, slot)?.[platform];
370
+ if (expectedId !== void 0 && (platform === "ios" ? current?.deviceUdid : current?.avdName) !== expectedId) return false;
372
371
  removeSlotDevice(project, platform, slot);
373
372
  saveConfig(cfg);
373
+ return true;
374
374
  });
375
375
  }
376
376
  function setSupervisor(projectPath, { pid, port, startedAt, processToken }) {
@@ -396,7 +396,7 @@ function clearSupervisor(projectPath, expected) {
396
396
  const cfg = loadConfig();
397
397
  const current = cfg?.projects?.[projectPath]?.supervisor;
398
398
  if (!current) return true;
399
- if (expected !== void 0 && !sameProcessRecord(current, expected)) return false;
399
+ if (expected !== void 0 && !(0, process_identity_exports.sameProcessRecord)(current, expected)) return false;
400
400
  delete cfg.projects[projectPath].supervisor;
401
401
  saveConfig(cfg);
402
402
  return true;
@@ -459,4 +459,4 @@ function allConsolePortsAndSerials({ isMounted = isOnMountedVolume } = {}) {
459
459
  return result;
460
460
  }
461
461
  //#endregion
462
- export { directorySize as A, removeSlotDevice as B, withConfigLock as C, sameProcessRecord as D, inspectProcessIdentity as E, assignSlotDevice as F, deviceSlotKey as I, deviceSlotPlatforms as L, isOnMountedVolume as M, listMountedVolumes as N, waitForProcessExit as O, volumeRootFor as P, parseDeviceSlotKey as R, upsertProject as S, captureProcessToken as T, validateDeviceSlot as V, releaseAndroidConsolePort as _, clearSupervisor as a, setDevice as b, findEnclosingWorktreeRoot as c, getConfigPath as d, getProject as f, loadConfig as g, isPathPrefix as h, clearDevice as i, formatBytes as j, withDirLock as k, getConcurrencyLimits as l, getRepoSettings as m, allMetroPorts as n, configCorruptRepair as o, getProjectSettings as p, claimMetroPort as r, ensureConfig as s, allConsolePortsAndSerials as t, getConfigDir as u, removeProject as v, captureProcessIdentity as w, setSupervisor as x, saveConfig as y, projectDeviceSlots as z };
462
+ export { isOnMountedVolume as A, withConfigLock as C, withDirLock as D, ownership_claim_exports as E, deviceSlotPlatforms as F, parseDeviceSlotKey as I, projectDeviceSlots as L, volumeRootFor as M, assignSlotDevice as N, directorySize as O, deviceSlotKey as P, removeSlotDevice as R, upsertProject as S, acquireAvdClaim as T, releaseAndroidConsolePort as _, clearSupervisor as a, setDevice as b, findEnclosingWorktreeRoot as c, getConfigPath as d, getProject as f, loadConfig as g, isPathPrefix as h, clearDevice as i, listMountedVolumes as j, formatBytes as k, getConcurrencyLimits as l, getRepoSettings as m, allMetroPorts as n, configCorruptRepair as o, getProjectSettings as p, claimMetroPort as r, ensureConfig as s, allConsolePortsAndSerials as t, getConfigDir as u, removeProject as v, process_identity_exports as w, setSupervisor as x, saveConfig as y, validateDeviceSlot as z };