stim 1.6.0 → 1.7.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 (71) hide show
  1. package/README.md +1 -1
  2. package/dist/{android-BkYmndPp.mjs → android-DWssqHQ8.mjs} +165 -661
  3. package/dist/{android-DLJB8ljN.mjs → android-Da-G_o3n.mjs} +646 -548
  4. package/dist/{android-BW8i_YxU.mjs → android-DeYdjjBP.mjs} +6 -2
  5. package/dist/{android-cas-CrzI_Zha.mjs → android-cas-SWIjzeuL.mjs} +54 -18
  6. package/dist/android-cas-compiler.mjs +1 -1
  7. package/dist/{app-install--PAocWvq.mjs → app-install-DJg7bVv-.mjs} +36 -10
  8. package/dist/build-lock-CgtsB6TV.mjs +604 -0
  9. package/dist/{build-slots-CuPszJDu.mjs → build-slots-C-oAHRT1.mjs} +2 -2
  10. package/dist/{cache-manifest-uoOKhgUD.mjs → cache-manifest-CYJi8-Hx.mjs} +2 -2
  11. package/dist/cache-manifest.d.mts +1 -1
  12. package/dist/cache-manifest.mjs +1 -1
  13. package/dist/cli.mjs +14 -14
  14. package/dist/collector-run.d.mts +2 -0
  15. package/dist/collector-run.mjs +7 -7
  16. package/dist/{command-output-BuIb_ZOI.mjs → command-output-B3ycpsYv.mjs} +2 -2
  17. package/dist/{config-DMP7L7H6.mjs → config-B7iyKWkL.mjs} +56 -16
  18. package/dist/{dependency-state-CTL8A9p3.mjs → dependency-state-D0M7oKA7.mjs} +2 -2
  19. package/dist/{deps-C1zGnr61.mjs → deps-oCXVNEqQ.mjs} +6 -6
  20. package/dist/detached-entry-Bpo6ZY_I.mjs +65 -0
  21. package/dist/{dev-client-BaTXAAnC.mjs → dev-client-DDUkWgJL.mjs} +12 -12
  22. package/dist/{device-xoQxw8hR.mjs → device-CBSaC6BU.mjs} +7 -7
  23. package/dist/{device-XY_5eGdN.mjs → device-CLeDp-KH.mjs} +17 -15
  24. package/dist/{device-lease-DBW-IL4p.mjs → device-lease-Ffz_M5t0.mjs} +3 -3
  25. package/dist/{device-pool-C0s_83Og.mjs → device-pool-DNSHL5z8.mjs} +4 -4
  26. package/dist/{device-remote-D9wN3CdE.mjs → device-remote-BprxZp-m.mjs} +11 -11
  27. package/dist/{doctor-BRO8UKlK.mjs → doctor-CTAx9gMu.mjs} +90 -46
  28. package/dist/{error-diagnostics-B5STzxqN.mjs → error-diagnostics-D1Kaf-eT.mjs} +23 -17
  29. package/dist/{exec-CyylIdq9.mjs → exec-B29XNuhP.mjs} +14 -2
  30. package/dist/{gc-C-6grAQI.mjs → gc-CX0KRfLG.mjs} +17 -16
  31. package/dist/{guide-CYtd7xbA.mjs → guide-CPmAG9me.mjs} +38 -15
  32. package/dist/{guide-status-BF6RsJJp.mjs → guide-status-Craq9z0t.mjs} +3 -3
  33. package/dist/{ios-Ccm6c8Hj.mjs → ios-BRXYSxSc.mjs} +34 -37
  34. package/dist/{ios-device-DJOi9FR5.mjs → ios-device-CDbvn5H2.mjs} +2 -2
  35. package/dist/{ios-device-D3pzTKQE.mjs → ios-device-CFXA46ez.mjs} +1 -1
  36. package/dist/{ios-Deh5js5V.mjs → ios-lEJ5pKsq.mjs} +3 -3
  37. package/dist/{logs-D4Poojxy.mjs → logs-CakdBTtm.mjs} +6 -6
  38. package/dist/{logs-query-Cdp5aaCH.mjs → logs-query-BGL-Pl12.mjs} +1 -1
  39. package/dist/metro-C7bcJ0uo.mjs +233 -0
  40. package/dist/{named-ports-DH9SXd0f.mjs → named-ports-DzMOlmaX.mjs} +57 -42
  41. package/dist/{ownership-CD_XnpDu.mjs → ownership-Cp52QAK1.mjs} +3 -3
  42. package/dist/{ownership-CVY9cqCr.mjs → ownership-D5vkdm8H.mjs} +13 -13
  43. package/dist/{ports-A6uTJSPj.mjs → ports-BIbJaKNW.mjs} +3 -3
  44. package/dist/{process-output-C7IlIzd7.mjs → process-output-Dl5uKE03.mjs} +1 -1
  45. package/dist/{project-B_SDt4ip.mjs → project-DDZWf_16.mjs} +3 -3
  46. package/dist/{reclaim-WXTbXPX4.mjs → reclaim-D614jI6_.mjs} +11 -11
  47. package/dist/{reload-xlQvHKfN.mjs → reload-ChW46VDb.mjs} +9 -9
  48. package/dist/{server-bare-CCLb90FS.mjs → server-bare-BQkyxa_b.mjs} +1 -1
  49. package/dist/{server-expo-CX-zkgWl.mjs → server-expo-BVAA1OqJ.mjs} +23 -11
  50. package/dist/server-expo-YDDG3eP7.mjs +2 -0
  51. package/dist/{settings-CGHTt5vb.mjs → settings-BXXorVM2.mjs} +3 -3
  52. package/dist/{slot-launch-BYjnljAs.mjs → slot-launch-DbLvyrup.mjs} +4 -4
  53. package/dist/{start-DHYAyGzm.mjs → start-TTVILlUi.mjs} +111 -38
  54. package/dist/{state-CnWzvCWP.mjs → state-DZobzxLW.mjs} +2 -2
  55. package/dist/{stats-Fo9ACIv7.mjs → stats-BYcZ6n1h.mjs} +1 -1
  56. package/dist/{stats-DHrO0frH.mjs → stats-CLg5RuDS.mjs} +4 -4
  57. package/dist/{status-QeLZb8SM.mjs → status-DUNy_HZs.mjs} +14 -15
  58. package/dist/{stop-CHTaw65F.mjs → stop-CnUpnFT5.mjs} +2 -2
  59. package/dist/{stop-DsdOx9Hj.mjs → stop-ZrfODZ51.mjs} +13 -13
  60. package/dist/supervisor-run.mjs +7 -5
  61. package/dist/{teardown-sm1FwF6V.mjs → teardown-5J5UXz2P.mjs} +14 -17
  62. package/dist/{workspace-process-lock-Bz19_R9R.mjs → workspace-process-lock-BYfZz_S-.mjs} +1 -1
  63. package/dist/{workspace-state-WVwWw_hB.mjs → workspace-state-BT5E3SA2.mjs} +3 -3
  64. package/dist/{worktree-CCNPg_f0.mjs → worktree-BfE4lzHK.mjs} +21 -16
  65. package/dist/{worktree-DJnBbQZ9.mjs → worktree-FG_ZKKdb.mjs} +13 -9
  66. package/dist/{xcode-DUF7jrXj.mjs → xcode-B6YaWeg0.mjs} +8 -8
  67. package/package.json +12 -8
  68. package/shim/android-optimizations.gradle +6 -0
  69. package/dist/metro-ato6ef3J.mjs +0 -99
  70. package/dist/ports-BZprTJRa.mjs +0 -90
  71. package/dist/server-expo-CjeTJ273.mjs +0 -2
@@ -1,13 +1,13 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { C as withConfigLock, E as ownership_claim_exports, F as deviceSlotPlatforms, L as projectDeviceSlots, N as assignSlotDevice, R as removeSlotDevice, T as acquireAvdClaim, d as getConfigPath, g as loadConfig, s as ensureConfig, u as getConfigDir, y as saveConfig } from "./config-DMP7L7H6.mjs";
1
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
+ import { C as withConfigLock, E as ownership_claim_exports, I as deviceSlotPlatforms, N as assignSlotDevice, R as projectDeviceSlots, T as acquireAvdClaim, d as getConfigPath, g as loadConfig, s as ensureConfig, u as getConfigDir, y as saveConfig, z as removeSlotDevice } from "./config-B7iyKWkL.mjs";
3
3
  import { r as createNdjsonWriter } from "./ndjson-BZwI1NBv.mjs";
4
- import { _ as workspaceLogsDir } from "./workspace-state-WVwWw_hB.mjs";
5
- import { l as resolvePackageJson } from "./project-B_SDt4ip.mjs";
6
- import { n as stripAnsi } from "./process-output-C7IlIzd7.mjs";
7
- import { l as phaseLine } from "./command-output-BuIb_ZOI.mjs";
8
- import { E as ownedAvdMatchesConfiguration, F as shutdownAndroidEmulator, I as waitForAndroidEmulatorShutdown, N as resolveOwnedAvdSerial, O as ownedAvdSystemImage, S as listOrphanedAvdDirectories, T as ownedAvdDirectory, l as avdStorageRoots, o as assertOwnedAvdStopped, p as deleteAvd, s as avdPathExists } from "./android-BkYmndPp.mjs";
9
- import { n as expoBinFromPackage, r as expoBinPath, s as findBinUpward } from "./server-expo-CX-zkgWl.mjs";
10
- import { C as shutdownIosSim, _ as renameIosSim, a as deleteIosSim, d as listIosDeviceTypes, g as parseRuntimeVersion, h as parkedSimName, o as deleteParkedIosSim, r as clearAppDataContainer, s as findAppDataContainer, x as resolveOwnedIosSim } from "./ios-Deh5js5V.mjs";
4
+ import { _ as workspaceLogsDir } from "./workspace-state-BT5E3SA2.mjs";
5
+ import { l as resolvePackageJson } from "./project-DDZWf_16.mjs";
6
+ import { n as stripAnsi } from "./process-output-Dl5uKE03.mjs";
7
+ import { I as resolveOwnedAvdSerial, O as ownedAvdDirectory, R as shutdownAndroidEmulator, T as listOrphanedAvdDirectories, c as assertOwnedAvdStopped, d as avdStorageRoots, h as deleteAvd, j as ownedAvdSystemImage, k as ownedAvdMatchesConfiguration, l as avdPathExists, z as waitForAndroidEmulatorShutdown } from "./android-DWssqHQ8.mjs";
8
+ import { l as phaseLine } from "./command-output-B3ycpsYv.mjs";
9
+ import { n as expoBinFromPackage, r as expoBinPath, s as findBinUpward } from "./server-expo-BVAA1OqJ.mjs";
10
+ import { C as shutdownIosSim, _ as renameIosSim, a as deleteIosSim, d as listIosDeviceTypes, g as parseRuntimeVersion, h as parkedSimName, o as deleteParkedIosSim, r as clearAppDataContainer, s as findAppDataContainer, x as resolveOwnedIosSim } from "./ios-lEJ5pKsq.mjs";
11
11
  import { createRequire } from "node:module";
12
12
  import { existsSync, lstatSync, readFileSync, renameSync, rmSync } from "node:fs";
13
13
  import { randomUUID } from "node:crypto";
@@ -202,7 +202,7 @@ function easAuthNote(status) {
202
202
  if (code === "wrong-account") return `eas is authenticated as ${account}, and this project's owner is ${owner} -- run \`eas login\` as a member of ${owner} (or set EXPO_TOKEN); consulting the EAS cache anyway`;
203
203
  return null;
204
204
  }
205
- function resolveEasCliBin(projectRoot, { lookupPath = null, timeoutMs = 5e3 } = {}) {
205
+ function resolveEasCliBin(projectRoot, { lookupPath = null } = {}) {
206
206
  const fromPackage = expoBinFromPackage(resolvePackageJson(projectRoot, EAS_CLI_PACKAGE), EAS_CLI_BIN);
207
207
  if (fromPackage) return {
208
208
  file: fromPackage,
@@ -213,7 +213,7 @@ function resolveEasCliBin(projectRoot, { lookupPath = null, timeoutMs = 5e3 } =
213
213
  file: shim,
214
214
  source: "project"
215
215
  };
216
- const onPath = lookupPath ? lookupPath() : getExecutor().runQuiet(`command -v ${EAS_CLI_BIN}`, { timeoutMs });
216
+ const onPath = lookupPath ? lookupPath() : getExecutor().findExecutable(EAS_CLI_BIN);
217
217
  const file = (String(onPath || "").split("\n")[0] ?? "").trim();
218
218
  return file ? {
219
219
  file,
@@ -720,7 +720,7 @@ function dropParked(platform, udid) {
720
720
  return removeParkedAfter(platform, udid, () => {}) !== null;
721
721
  }
722
722
  //#endregion
723
- //#region src/agent-device-cleanup.ts
723
+ //#region src/devices/agent-device-cleanup.ts
724
724
  function parseAgentDeviceSessions(output) {
725
725
  const result = JSON.parse(output);
726
726
  if (result?.success !== true || !Array.isArray(result.data?.sessions)) throw new Error("agent-device did not return a successful session list");
@@ -763,10 +763,7 @@ function closeOwnedDeviceSessions(device, stillOwned) {
763
763
  "stim-teardown-inventory"
764
764
  ]));
765
765
  try {
766
- if (!exec.runQuiet("command -v agent-device", {
767
- timeoutMs: 2e3,
768
- killSignal: "SIGKILL"
769
- })) return;
766
+ if (!exec.findExecutable("agent-device")) return;
770
767
  const sessions = list().filter((session) => session.platform === device.platform && session.id === device.id);
771
768
  for (const session of sessions) try {
772
769
  if (!list().some((current) => current.name === session.name && current.platform === session.platform && current.id === session.id && current.createdAt === session.createdAt)) continue;
@@ -791,7 +788,7 @@ function closeOwnedDeviceSessions(device, stillOwned) {
791
788
  }
792
789
  }
793
790
  //#endregion
794
- //#region src/teardown.ts
791
+ //#region src/devices/teardown.ts
795
792
  function teardownParkedAvd(avdName) {
796
793
  try {
797
794
  return removeParkedAfter("android", avdName, () => {
@@ -1,4 +1,4 @@
1
- import { E as ownership_claim_exports } from "./config-DMP7L7H6.mjs";
1
+ import { E as ownership_claim_exports } from "./config-B7iyKWkL.mjs";
2
2
  import { join } from "node:path";
3
3
  //#region src/engine/workspace-process-lock.ts
4
4
  const DEFAULT_WAIT_MS = 6e4;
@@ -1,9 +1,9 @@
1
- import { D as withDirLock, u as getConfigDir } from "./config-DMP7L7H6.mjs";
1
+ import { D as withDirLock, u as getConfigDir } from "./config-B7iyKWkL.mjs";
2
2
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { dirname } from "node:path";
4
4
  import { join as join$1, resolve as resolve$1 } from "path";
5
5
  import { buildCacheRoot as sharedBuildCache, workspaceId, workspaceName as workspaceName$1, workspaceStateDir as workspaceDir } from "@stim-cli/core";
6
- //#region src/paths.ts
6
+ //#region src/workspace/paths.ts
7
7
  const WORKSPACE_METADATA_FILE_NAME = "workspace.json";
8
8
  function workspaceMetadataFile(projectRoot) {
9
9
  return join$1(workspaceDir(projectRoot), WORKSPACE_METADATA_FILE_NAME);
@@ -66,7 +66,7 @@ function sharedCcache() {
66
66
  return join$1(getConfigDir(), "ccache");
67
67
  }
68
68
  //#endregion
69
- //#region src/workspace-state.ts
69
+ //#region src/workspace/workspace-state.ts
70
70
  function readWorkspaceState(root) {
71
71
  try {
72
72
  const parsed = JSON.parse(readFileSync(workspaceStateFile(root), "utf-8"));
@@ -1,16 +1,16 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { E as ownership_claim_exports, S as upsertProject, f as getProject, g as loadConfig, h as isPathPrefix, u as getConfigDir, v as removeProject, w as process_identity_exports } from "./config-DMP7L7H6.mjs";
3
- import { S as warmWorktreePaths, _ as resolveFullRef, b as sourceCheckoutOf, c as hasRemote, d as listWorktrees, f as locallyKnownUpstream, h as removeWorktree, i as depsOutOfSync, l as hasUncommittedWork, m as readWorktreeExclude, n as cloneIgnoredEntries, o as dirtyPaths, p as podsOutOfSync, r as deleteBranch, s as gitCommonDir, t as branchExists, u as isPodInstallChurn, x as unpushedCommits, y as restoreFile } from "./worktree-DJnBbQZ9.mjs";
4
- import { a as findProjectRoot } from "./project-B_SDt4ip.mjs";
5
- import { l as readRemoteSession, s as readMetroTunnel } from "./state-CnWzvCWP.mjs";
6
- import { a as formatElapsed, f as releasedLeaseFact, i as formatDuration, l as phaseLine, m as shortUdid, u as plural } from "./command-output-BuIb_ZOI.mjs";
7
- import { A as resolveSettings, I as unknownSettingKeys, N as settingShapeErrors, a as SETTING_SHAPE_REMEDY } from "./settings-CGHTt5vb.mjs";
8
- import { a as POOL_SETTING_REMEDY, l as parkedMaxSetting } from "./teardown-sm1FwF6V.mjs";
9
- import { h as podInstallCommand, n as HEARTBEAT_INTERVAL_MS } from "./xcode-DUF7jrXj.mjs";
10
- import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-CTL8A9p3.mjs";
11
- import { c as withManagedTunnelRemovalLock, o as withManagedRemoteWorktreeRemovalLock } from "./ownership-CVY9cqCr.mjs";
12
- import { n as reclaimProject } from "./reclaim-WXTbXPX4.mjs";
13
- import { a as runPodInstall, i as runCaptured, n as podsAreStale, r as readPodState, t as DEPS_ERROR } from "./deps-C1zGnr61.mjs";
1
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
+ import { E as ownership_claim_exports, S as upsertProject, f as getProject, g as loadConfig, h as isPathPrefix, u as getConfigDir, v as removeProject, w as process_identity_exports } from "./config-B7iyKWkL.mjs";
3
+ import { S as warmWorktreePaths, _ as resolveFullRef, b as sourceCheckoutOf, c as hasRemote, d as listWorktrees, f as locallyKnownUpstream, h as removeWorktree, i as depsOutOfSync, l as hasUncommittedWork, m as readWorktreeExclude, n as cloneIgnoredEntries, o as dirtyPaths, p as podsOutOfSync, r as deleteBranch, s as gitCommonDir, t as branchExists, u as isPodInstallChurn, x as unpushedCommits, y as restoreFile } from "./worktree-FG_ZKKdb.mjs";
4
+ import { a as findProjectRoot } from "./project-DDZWf_16.mjs";
5
+ import { l as readRemoteSession, s as readMetroTunnel } from "./state-DZobzxLW.mjs";
6
+ import { A as resolveSettings, I as unknownSettingKeys, N as settingShapeErrors, a as SETTING_SHAPE_REMEDY } from "./settings-BXXorVM2.mjs";
7
+ import { a as formatElapsed, f as releasedLeaseFact, i as formatDuration, l as phaseLine, m as shortUdid, u as plural } from "./command-output-B3ycpsYv.mjs";
8
+ import { a as POOL_SETTING_REMEDY, l as parkedMaxSetting } from "./teardown-5J5UXz2P.mjs";
9
+ import { h as podInstallCommand, n as HEARTBEAT_INTERVAL_MS } from "./xcode-B6YaWeg0.mjs";
10
+ import { n as hasInstalledDependencies, r as installedNpmTreeIsValid, t as dependencyState } from "./dependency-state-D0M7oKA7.mjs";
11
+ import { c as withManagedTunnelRemovalLock, o as withManagedRemoteWorktreeRemovalLock } from "./ownership-D5vkdm8H.mjs";
12
+ import { n as reclaimProject } from "./reclaim-D614jI6_.mjs";
13
+ import { a as runPodInstall, i as runCaptured, n as podsAreStale, r as readPodState, t as DEPS_ERROR } from "./deps-oCXVNEqQ.mjs";
14
14
  import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
15
15
  import { createHash, randomUUID } from "node:crypto";
16
16
  import { join, relative } from "node:path";
@@ -198,7 +198,7 @@ async function withWarmClaim(options, fn) {
198
198
  }
199
199
  }
200
200
  //#endregion
201
- //#region src/worktree-refresh.ts
201
+ //#region src/workspace/worktree-refresh.ts
202
202
  const DIRTY_PATHS_SHOWN = 5;
203
203
  const FETCH_TIMEOUT_MS = 12e4;
204
204
  function mainCheckoutRefusal(root, state) {
@@ -1278,9 +1278,14 @@ async function runRemove(target, opts = {}) {
1278
1278
  }
1279
1279
  restorePodChurn(path, inspection.podChurn);
1280
1280
  try {
1281
- removeWorktree(path, { force: opts.force });
1281
+ removeWorktree(path, {
1282
+ from: source.path,
1283
+ force: opts.force
1284
+ });
1282
1285
  } catch (error) {
1283
- console.error(chalk.red(`git worktree remove failed: ${String(error?.message || error)}`));
1286
+ const message = String(error?.message || error);
1287
+ console.error(chalk.red(`git worktree remove failed: ${message}`));
1288
+ if (process.platform === "win32" && /Permission denied/i.test(message)) console.error(chalk.dim("Another process holds a file or directory inside the worktree. An adb server or emulator started from it keeps its working directory open; `adb kill-server` releases the server, and Sysinternals handle.exe lists other holders."));
1284
1289
  console.error(chalk.dim(`The directory and Stim ownership record for ${path} were kept.`));
1285
1290
  printRemovalCleanup(result, true);
1286
1291
  process.exitCode = 1;
@@ -1,5 +1,5 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { g as loadConfig, u as getConfigDir } from "./config-DMP7L7H6.mjs";
1
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
+ import { g as loadConfig, u as getConfigDir } from "./config-B7iyKWkL.mjs";
3
3
  import { accessSync, chmodSync, constants, lstatSync, mkdirSync, mkdtempSync, readdirSync, realpathSync, rmSync, statSync } from "node:fs";
4
4
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
5
  import { chmodSync as chmodSync$1, existsSync as existsSync$1, lstatSync as lstatSync$1, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdirSync as readdirSync$1, realpathSync as realpathSync$1, utimesSync } from "fs";
@@ -84,7 +84,7 @@ function removeTemporaryEntry(path) {
84
84
  });
85
85
  }
86
86
  //#endregion
87
- //#region src/worktree.ts
87
+ //#region src/workspace/worktree.ts
88
88
  const CARRY_SKIP_BASENAMES = /* @__PURE__ */ new Set([
89
89
  ".DerivedData",
90
90
  ".DS_Store",
@@ -93,6 +93,9 @@ const CARRY_SKIP_BASENAMES = /* @__PURE__ */ new Set([
93
93
  function isCarrySkipped(rel) {
94
94
  return /(^|\/)android\/build\/generated\/autolinking(\/|$)/.test(rel) || String(rel).split("/").some((seg) => CARRY_SKIP_BASENAMES.has(seg));
95
95
  }
96
+ function nativePath(path) {
97
+ return sep$1 === "/" ? path : path.replaceAll("/", sep$1);
98
+ }
96
99
  function gitCommonDir(cwd) {
97
100
  const out = getExecutor().runFileQuiet("git", [
98
101
  "-C",
@@ -101,7 +104,7 @@ function gitCommonDir(cwd) {
101
104
  "--path-format=absolute",
102
105
  "--git-common-dir"
103
106
  ]);
104
- return out ? out.trim() : null;
107
+ return out ? nativePath(out.trim()) : null;
105
108
  }
106
109
  function repoRoot(cwd) {
107
110
  const out = getExecutor().runFileQuiet("git", [
@@ -110,7 +113,7 @@ function repoRoot(cwd) {
110
113
  "rev-parse",
111
114
  "--show-toplevel"
112
115
  ]);
113
- return out ? out.trim() : null;
116
+ return out ? nativePath(out.trim()) : null;
114
117
  }
115
118
  function locallyKnownUpstream(projectRoot) {
116
119
  try {
@@ -440,7 +443,7 @@ function isPodInstallChurn(paths) {
440
443
  if (!paths || paths.length === 0) return false;
441
444
  return paths.every((line) => /(?:^|\/)(?:Podfile\.lock|project\.pbxproj)$/.test(line.slice(3).trim()));
442
445
  }
443
- const SAFE_BRANCH_NAME = /^[A-Za-z0-9._/-]+$/;
446
+ const SAFE_BRANCH_NAME = /^[A-Za-z0-9@._/-]+$/;
444
447
  function unpushedCommits(dir) {
445
448
  const exec = getExecutor();
446
449
  const branch = exec.runFileQuiet("git", [
@@ -511,10 +514,11 @@ function resolveFullRef(cwd, ref) {
511
514
  return null;
512
515
  }
513
516
  }
514
- function removeWorktree(path, { force = false } = {}) {
517
+ function removeWorktree(path, { from, force = false, platform = process.platform }) {
515
518
  const args = [
519
+ ...platform === "win32" ? ["-c", "core.longpaths=true"] : [],
516
520
  "-C",
517
- path,
521
+ from,
518
522
  "worktree",
519
523
  "remove",
520
524
  ...force ? ["--force"] : [],
@@ -539,7 +543,7 @@ function parseWorktrees(out) {
539
543
  let current = {};
540
544
  for (const line of out.split("\n")) if (line.startsWith("worktree ")) {
541
545
  if (current.path) entries.push(current);
542
- current = { path: line.slice(9) };
546
+ current = { path: nativePath(line.slice(9)) };
543
547
  } else if (line.startsWith("branch ")) current.branch = line.slice(7).replace("refs/heads/", "");
544
548
  else if (line === "prunable" || line.startsWith("prunable ")) current.prunable = true;
545
549
  else if (line === "bare") current.bare = true;
@@ -1,11 +1,11 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { r as register } from "./cache-manifest-uoOKhgUD.mjs";
3
- import { d as sharedCompilationCache, m as workspaceDerivedData } from "./workspace-state-WVwWw_hB.mjs";
4
- import { l as resolvePackageJson } from "./project-B_SDt4ip.mjs";
5
- import { t as createLineReader } from "./process-output-C7IlIzd7.mjs";
6
- import { a as formatElapsed, l as phaseLine } from "./command-output-BuIb_ZOI.mjs";
7
- import { U as resolveOptimizations } from "./settings-CGHTt5vb.mjs";
8
- import { t as cleanLine } from "./server-expo-CX-zkgWl.mjs";
1
+ import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
+ import { r as register } from "./cache-manifest-CYJi8-Hx.mjs";
3
+ import { d as sharedCompilationCache, m as workspaceDerivedData } from "./workspace-state-BT5E3SA2.mjs";
4
+ import { l as resolvePackageJson } from "./project-DDZWf_16.mjs";
5
+ import { t as createLineReader } from "./process-output-Dl5uKE03.mjs";
6
+ import { U as resolveOptimizations } from "./settings-BXXorVM2.mjs";
7
+ import { a as formatElapsed, l as phaseLine } from "./command-output-B3ycpsYv.mjs";
8
+ import { t as cleanLine } from "./server-expo-BVAA1OqJ.mjs";
9
9
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
10
10
  import { createHash } from "node:crypto";
11
11
  import { basename, isAbsolute, join, relative, resolve, sep } from "node:path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stim",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Stim: fast, isolated React Native environments for coding agents",
5
5
  "keywords": [
6
6
  "agent",
@@ -42,20 +42,24 @@
42
42
  },
43
43
  "dependencies": {
44
44
  "@expo/fingerprint": "^0.20.10",
45
- "@stim-cli/cache": "^1.6.0",
46
- "@stim-cli/core": "^1.6.0",
47
- "@stim-cli/metro": "^1.6.0",
48
- "chalk": "^5.4.1",
49
- "commander": "^13.1.0",
45
+ "@stim-cli/cache": "^1.7.0",
46
+ "@stim-cli/core": "^1.7.0",
47
+ "@stim-cli/metro": "^1.7.0",
48
+ "chalk": "^6.0.0",
49
+ "commander": "^15.0.0",
50
+ "cross-spawn": "^7.0.6",
51
+ "which": "^5.0.0",
50
52
  "ws": "^8.21.3"
51
53
  },
52
54
  "devDependencies": {
53
- "@stim-cli/expo-build-cache": "1.6.0",
55
+ "@stim-cli/expo-build-cache": "1.7.0",
56
+ "@types/cross-spawn": "^6.0.6",
57
+ "@types/which": "^3.0.4",
54
58
  "@types/ws": "^8.18.1",
55
59
  "unique-pid": "0.1.1"
56
60
  },
57
61
  "engines": {
58
- "node": "^20.19.4 || >=22.12.0"
62
+ "node": ">=22.12.0"
59
63
  },
60
64
  "scripts": {
61
65
  "test": "node --test test/*.test.js",
@@ -7,6 +7,12 @@ gradle.beforeProject { project ->
7
7
  def profile = System.getenv("STIM_ANDROID_NATIVE_PROFILE")
8
8
  def staging = android.externalNativeBuild.cmake.buildStagingDirectory ?: new File(project.projectDir, ".cxx")
9
9
  android.externalNativeBuild.cmake.buildStagingDirectory = new File(staging, "stim-" + profile)
10
+ // Windows caps a path at 260 characters and the SDK's ninja is not long-path aware, so the
11
+ // staging directory added above has to buy its room back: CMake replaces the mangled absolute
12
+ // source directory in an over-long object path with a hash of it.
13
+ if (System.getProperty("os.name").toLowerCase().startsWith("windows")) {
14
+ android.defaultConfig.externalNativeBuild.cmake.arguments.add("-DCMAKE_OBJECT_PATH_MAX=240")
15
+ }
10
16
  def configs = [android.defaultConfig] + android.buildTypes.toList() + android.productFlavors.toList()
11
17
  def pch = System.getenv("STIM_ANDROID_PCH")
12
18
  def ccache = System.getenv("STIM_ANDROID_CCACHE")
@@ -1,99 +0,0 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { w as process_identity_exports } from "./config-DMP7L7H6.mjs";
3
- import { r as readWorkspaceState } from "./workspace-state-WVwWw_hB.mjs";
4
- import { t as isMetroRunning } from "./ports-BZprTJRa.mjs";
5
- import { readlinkSync, realpathSync } from "fs";
6
- import { sep } from "path";
7
- //#region src/metro.ts
8
- /**
9
- * True when some process with this pid exists right now. It cannot tell a recycled pid from the
10
- * process that first held it, so it is only valid for a pid this run spawned and still holds a
11
- * handle on. For a pid read from a record on disk, decide with `inspectProcessIdentity` on that
12
- * record's process token, or take an ownership claim (`ownership-claim.ts`): this answer would keep
13
- * a dead holder's record looking live until some unrelated process exits.
14
- */
15
- function pidExists(pid) {
16
- if (!Number.isSafeInteger(pid) || pid <= 0) return false;
17
- try {
18
- process.kill(pid, 0);
19
- return true;
20
- } catch (error) {
21
- return error.code !== "ESRCH";
22
- }
23
- }
24
- function parseLsofPids(out) {
25
- if (!out) return [];
26
- return String(out).split("\n").map((s) => parseInt(s.trim(), 10)).filter((n) => Number.isFinite(n));
27
- }
28
- function parseLsofCwd(out) {
29
- if (!out) return null;
30
- const lines = String(out).split("\n");
31
- const idx = lines.findIndex((l) => l === "fcwd");
32
- if (idx === -1) return null;
33
- const nLine = lines.slice(idx + 1).find((l) => l.startsWith("n"));
34
- return nLine ? nLine.slice(1) : null;
35
- }
36
- function processCwd(pid) {
37
- if (process.platform === "linux") try {
38
- return readlinkSync(`/proc/${pid}/cwd`);
39
- } catch {}
40
- return parseLsofCwd(getExecutor().runQuiet(`lsof -a -p ${pid} -d cwd -Fn`));
41
- }
42
- function canonicalPath(path) {
43
- try {
44
- return realpathSync(path);
45
- } catch {
46
- return path;
47
- }
48
- }
49
- function isInsideProject(cwd, projectPath) {
50
- if (!cwd || !projectPath) return false;
51
- const a = canonicalPath(cwd);
52
- const b = canonicalPath(projectPath);
53
- return a === b || a.startsWith(b.endsWith(sep) ? b : b + sep);
54
- }
55
- const NOT_OURS_UNRESPONSIVE = "unresponsive";
56
- const NOT_OURS_UNREADABLE_CWD = "unreadable-cwd";
57
- const NOT_OURS_FOREIGN_CWD = "foreign-cwd";
58
- async function resolveProjectMetro(port, projectPath, { probe = isMetroRunning } = {}) {
59
- const pid = parseLsofPids(getExecutor().runQuiet(`lsof -nP -iTCP:${port} -sTCP:LISTEN -t`))[0];
60
- if (pid === void 0) return { missing: true };
61
- if (!await probe(port)) return {
62
- notOurs: `pid ${pid} on port ${port} does not answer Metro's /status`,
63
- kind: NOT_OURS_UNRESPONSIVE,
64
- pid
65
- };
66
- const cwd = processCwd(pid);
67
- if (!cwd) return {
68
- notOurs: `pid ${pid} on port ${port}: working directory could not be read`,
69
- kind: NOT_OURS_UNREADABLE_CWD,
70
- pid
71
- };
72
- if (!isInsideProject(cwd, projectPath)) return {
73
- notOurs: `pid ${pid} on port ${port} runs from ${cwd}, outside ${projectPath}`,
74
- kind: NOT_OURS_FOREIGN_CWD,
75
- pid
76
- };
77
- const supervisor = readWorkspaceState(canonicalPath(projectPath))?.supervisor;
78
- const owned = supervisor?.port === port && (0, process_identity_exports.inspectProcessIdentity)(supervisor) === "same";
79
- return { metro: {
80
- pid,
81
- leader: owned ? supervisor.pid : pid,
82
- cwd,
83
- ...owned ? { processToken: supervisor.processToken } : {}
84
- } };
85
- }
86
- function killMetroTree(leader, processToken) {
87
- if (!leader || leader === process.pid || (0, process_identity_exports.inspectProcessIdentity)({
88
- pid: leader,
89
- processToken
90
- }) !== "same") return false;
91
- try {
92
- process.kill(-leader, "SIGTERM");
93
- return true;
94
- } catch {
95
- return false;
96
- }
97
- }
98
- //#endregion
99
- export { resolveProjectMetro as i, killMetroTree as n, pidExists as r, NOT_OURS_FOREIGN_CWD as t };
@@ -1,90 +0,0 @@
1
- import { A as isOnMountedVolume, g as loadConfig, j as listMountedVolumes, n as allMetroPorts, r as claimMetroPort, v as removeProject } from "./config-DMP7L7H6.mjs";
2
- import { existsSync } from "fs";
3
- import { request } from "http";
4
- import { connect } from "net";
5
- //#region src/ports.ts
6
- function isMetroRunning(port) {
7
- return new Promise((resolve) => {
8
- const req = request({
9
- hostname: "localhost",
10
- port,
11
- path: "/status",
12
- timeout: 2e3
13
- }, (res) => {
14
- let data = "";
15
- res.on("data", (chunk) => {
16
- data += chunk;
17
- });
18
- res.on("end", () => resolve(data.includes("packager-status:running")));
19
- });
20
- req.on("error", () => resolve(false));
21
- req.on("timeout", () => {
22
- req.destroy();
23
- resolve(false);
24
- });
25
- req.end();
26
- });
27
- }
28
- function isPortFree(port, { timeoutMs = 400 } = {}) {
29
- return new Promise((resolve) => {
30
- const sock = connect({
31
- port,
32
- host: "127.0.0.1"
33
- });
34
- const done = (free) => {
35
- sock.removeAllListeners();
36
- sock.destroy();
37
- resolve(free);
38
- };
39
- sock.setTimeout(timeoutMs);
40
- sock.once("connect", () => done(false));
41
- sock.once("error", () => done(true));
42
- sock.once("timeout", () => done(false));
43
- });
44
- }
45
- const FIRST_PORT = 8082;
46
- async function computeNextPort(isFree = isPortFree) {
47
- const taken = new Set(allMetroPorts());
48
- for (let port = FIRST_PORT; port < 8282; port++) {
49
- if (taken.has(port)) continue;
50
- if (await isFree(port)) return port;
51
- }
52
- throw new Error(`Found no free Metro port between ${FIRST_PORT} and 8281. Free one up, or stop a stale bundler (\`stim status\`, \`stim stop <port>\`).`);
53
- }
54
- async function findReclaimablePort(excludeProjectPath, probe = isMetroRunning, { isMounted = isOnMountedVolume, mountedVolumes } = {}) {
55
- const cfg = loadConfig();
56
- if (!cfg?.projects) return null;
57
- const mounted = mountedVolumes || listMountedVolumes();
58
- const candidates = [];
59
- for (const [path, proj] of Object.entries(cfg.projects)) {
60
- if (path === excludeProjectPath) continue;
61
- if (Object.keys(proj.ports ?? {}).length) continue;
62
- if (existsSync(path)) continue;
63
- if (!isMounted(path, mounted)) continue;
64
- if (typeof proj.metroPort === "number") candidates.push({
65
- port: proj.metroPort,
66
- ownerPath: path
67
- });
68
- }
69
- for (const c of candidates) if (!await probe(c.port)) return c;
70
- return null;
71
- }
72
- async function allocatePort(projectPath, probe = isMetroRunning, isFree = isPortFree) {
73
- const reclaim = await findReclaimablePort(projectPath, probe);
74
- if (reclaim && await isFree(reclaim.port)) {
75
- removeProject(reclaim.ownerPath);
76
- return reclaim.port;
77
- }
78
- return computeNextPort(isFree);
79
- }
80
- const RESERVE_ATTEMPTS = 5;
81
- async function reserveMetroPort(projectPath, probe = isMetroRunning, isFree = isPortFree) {
82
- for (let attempt = 0; attempt < RESERVE_ATTEMPTS; attempt++) {
83
- const port = await allocatePort(projectPath, probe, isFree);
84
- const claimed = claimMetroPort(projectPath, port);
85
- if (claimed !== null) return claimed;
86
- }
87
- throw new Error(`Could not reserve a Metro port after ${RESERVE_ATTEMPTS} attempts: another Stim run claimed each one first. Retry.`);
88
- }
89
- //#endregion
90
- export { isPortFree as n, reserveMetroPort as r, isMetroRunning as t };
@@ -1,2 +0,0 @@
1
- import { p as startExpoServer } from "./server-expo-CX-zkgWl.mjs";
2
- export { startExpoServer };