stim 1.8.0 → 1.9.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 (80) hide show
  1. package/README.md +23 -155
  2. package/dist/{android-BRgBYzU0.mjs → android-BPGizezh.mjs} +112 -135
  3. package/dist/{android-DeYdjjBP.mjs → android-DEgo4oCo.mjs} +2 -2
  4. package/dist/{android-DWssqHQ8.mjs → android-_y4Ohi5z.mjs} +54 -15
  5. package/dist/android-cas-compiler.mjs +2 -2
  6. package/dist/{android-cas-SWIjzeuL.mjs → android-cas-eiExNLf7.mjs} +21 -14
  7. package/dist/app-install-Uf3a83Tv.mjs +819 -0
  8. package/dist/{build-lock-CgtsB6TV.mjs → build-lock-DPXPLgTc.mjs} +120 -24
  9. package/dist/build-progress-CVqkfPC6.mjs +188 -0
  10. package/dist/{cache-manifest-CYJi8-Hx.mjs → cache-manifest-Dg7OnMrp.mjs} +4 -4
  11. package/dist/cache-manifest.mjs +1 -1
  12. package/dist/cli.mjs +22 -19
  13. package/dist/collector-run.d.mts +2 -0
  14. package/dist/collector-run.mjs +10 -9
  15. package/dist/{command-output-B3ycpsYv.mjs → command-output-gByVmIjy.mjs} +11 -2
  16. package/dist/{config-B7iyKWkL.mjs → config-Cp9lM3wX.mjs} +187 -57
  17. package/dist/{dependency-state-D0M7oKA7.mjs → dependency-state-Bq5DcLGn.mjs} +2 -2
  18. package/dist/{deps-oCXVNEqQ.mjs → deps-Cip5vUm1.mjs} +8 -5
  19. package/dist/{detached-entry-Bpo6ZY_I.mjs → detached-entry-KEu0_AXL.mjs} +1 -1
  20. package/dist/{device-BUoZyMoR.mjs → dev-client-HyqISM0h.mjs} +1178 -635
  21. package/dist/{device-RdujjHLB.mjs → device-D4U-xLty.mjs} +13 -20
  22. package/dist/{device-lease-Ffz_M5t0.mjs → device-lease-Crs-HkJY.mjs} +16 -9
  23. package/dist/{device-pool-BxRwdufO.mjs → device-pool-CnTAz8X1.mjs} +7 -7
  24. package/dist/{device-remote-ByN8P6CB.mjs → device-remote-AQ5rQPqr.mjs} +482 -35
  25. package/dist/{doctor-BMt156qe.mjs → doctor-B_Ei3bZu.mjs} +95 -107
  26. package/dist/{error-diagnostics-D1Kaf-eT.mjs → error-diagnostics-Vfz83GCF.mjs} +22 -16
  27. package/dist/exec-jIAlgilk.mjs +132 -0
  28. package/dist/{gc-v2YcoJvm.mjs → gc-Ckw8RQHi.mjs} +763 -126
  29. package/dist/{guide-BTAlD1_y.mjs → guide-BiAbhs1J.mjs} +891 -501
  30. package/dist/{guide-status-Craq9z0t.mjs → guide-status-BAMVU0ZA.mjs} +5 -3
  31. package/dist/{ios-BcCiI4TS.mjs → ios-BL2NFckr.mjs} +43 -20
  32. package/dist/{ios-BP04pY5f.mjs → ios-BzUJWk1K.mjs} +141 -175
  33. package/dist/{ios-device-CauFJiYx.mjs → ios-device-D972i9wf.mjs} +149 -52
  34. package/dist/{ios-device-CFXA46ez.mjs → ios-device-DPsSm0v_.mjs} +3 -2
  35. package/dist/launch-verify-Q_OtW1Rf.mjs +365 -0
  36. package/dist/{logs-CakdBTtm.mjs → logs-CI9NQSK7.mjs} +18 -13
  37. package/dist/{logs-query-BGL-Pl12.mjs → logs-query-Cqma3WIu.mjs} +36 -28
  38. package/dist/{metro-C7bcJ0uo.mjs → metro-DrLHaMn2.mjs} +78 -8
  39. package/dist/{named-ports-DzMOlmaX.mjs → named-ports-CmLQSIRj.mjs} +4 -4
  40. package/dist/{ndjson-BZwI1NBv.mjs → ndjson-9uFNRwfe.mjs} +36 -3
  41. package/dist/{ownership-Cp52QAK1.mjs → ownership-D15Op8vN.mjs} +3 -3
  42. package/dist/{ownership-wolE-0Do.mjs → ownership-DCQPuWEc.mjs} +26 -7
  43. package/dist/{ports-BIbJaKNW.mjs → ports-BNKoWVWZ.mjs} +3 -3
  44. package/dist/{process-output-Dl5uKE03.mjs → process-output-Fpl8PYi8.mjs} +1 -1
  45. package/dist/{project-DDZWf_16.mjs → project-DS112DYB.mjs} +8 -3
  46. package/dist/{reload-BdypPbjM.mjs → reload-ZNJUo1pl.mjs} +12 -13
  47. package/dist/{server-bare-BQkyxa_b.mjs → server-bare-DWkkcIxV.mjs} +2 -2
  48. package/dist/{server-expo-BVAA1OqJ.mjs → server-expo-B_lctBR3.mjs} +8 -7
  49. package/dist/server-expo-CeILEu86.mjs +2 -0
  50. package/dist/settings-BKNh5rl4.mjs +376 -0
  51. package/dist/{settings-BXXorVM2.mjs → settings-D030k0t4.mjs} +464 -152
  52. package/dist/settings.schema.json +989 -0
  53. package/dist/{teardown-BJqEzPo_.mjs → sim-pool-KaIsp2Ft.mjs} +14 -419
  54. package/dist/simslim-B_LsGLuJ.mjs +259 -0
  55. package/dist/{slot-launch-ZUUe0TQP.mjs → slot-launch-DU9A-gYw.mjs} +4 -4
  56. package/dist/spawn-claims-Dn3C_N6w.mjs +49 -0
  57. package/dist/{start-CmxtOKjU.mjs → start-DYkkKF0s.mjs} +27 -31
  58. package/dist/{state-DZobzxLW.mjs → state-NcJzMEBF.mjs} +5 -3
  59. package/dist/{stats-CLg5RuDS.mjs → stats-B63-JVAY.mjs} +4 -4
  60. package/dist/{stats-BYcZ6n1h.mjs → stats-DG9e66Rz.mjs} +66 -5
  61. package/dist/status-S6ehL4Ui.mjs +932 -0
  62. package/dist/{stop-C8JW6kKp.mjs → stop-BO28HGqF.mjs} +101 -25
  63. package/dist/{stop-B1kOpYei.mjs → stop-T1R-cZRz.mjs} +2 -2
  64. package/dist/supervisor-run.mjs +52 -34
  65. package/dist/{workspace-process-lock-BYfZz_S-.mjs → workspace-process-lock-DWaiRD0e.mjs} +14 -9
  66. package/dist/{workspace-state-BT5E3SA2.mjs → workspace-state-DF2gCFhx.mjs} +52 -29
  67. package/dist/worktree-DKhSF0UX.mjs +2 -0
  68. package/dist/{worktree-CFLA-6MX.mjs → worktree-LGB-UwgG.mjs} +603 -59
  69. package/dist/{worktree-FG_ZKKdb.mjs → worktree-R_cUlE7s.mjs} +62 -41
  70. package/dist/{xcode-B6YaWeg0.mjs → xcode-BZqyxh3e.mjs} +216 -58
  71. package/package.json +5 -6
  72. package/skill/SKILL.md +2 -0
  73. package/dist/app-install-BDbsSgCl.mjs +0 -1175
  74. package/dist/build-slots-C-oAHRT1.mjs +0 -151
  75. package/dist/dev-client-BmT-ioFI.mjs +0 -797
  76. package/dist/exec-B29XNuhP.mjs +0 -77
  77. package/dist/reclaim-KJ-0uWEF.mjs +0 -361
  78. package/dist/server-expo-YDDG3eP7.mjs +0 -2
  79. package/dist/status-DdkLki47.mjs +0 -426
  80. package/dist/{metro-store-CDDZHtPl.mjs → metro-store-DzeWkfqW.mjs} +1 -1
package/README.md CHANGED
@@ -8,8 +8,8 @@ keep native and JavaScript builds warm across worktrees.
8
8
 
9
9
  ## Install
10
10
 
11
- If you previously installed `stim-cli` globally, follow the
12
- [migration instructions](#migrating-from-stim-cli) first.
11
+ Node 22.12.0 or later is required. If you previously installed `stim-cli`
12
+ globally, follow the [migration instructions](#migrating-from-stim-cli) first.
13
13
 
14
14
  ```bash
15
15
  npm install --global stim
@@ -22,18 +22,6 @@ Run without a global install when needed:
22
22
  npx stim <command>
23
23
  ```
24
24
 
25
- Node 22.12.0 or later is required.
26
-
27
- Machine defaults in `~/.stim/config.json` can enable or disable native artifact
28
- caching, remote caches, Metro sharing, iOS compiler caching and prefix mapping,
29
- and Android ccache/CAS, PCH, Gradle caching, and target ABI narrowing. The
30
- top-level `iosSimulatorApp` setting selects Xcode's viewer or an installed
31
- Siniulator.
32
- `.stim.json` runtime overrides apply per app, beside its `package.json`; monorepo
33
- apps do not inherit the repository-root file. Worktree-copy rules stay at the
34
- repository root. Run `stim guide settings` for the
35
- `optimizations` schema; existing defaults remain unchanged.
36
-
37
25
  ## Normal workflow
38
26
 
39
27
  ```bash
@@ -44,115 +32,29 @@ stim logs --errors
44
32
  stim stop
45
33
  ```
46
34
 
47
- For `stim logs --errors`, a clean check requires exit code 0 and no matching
48
- errors in the captured logs. Exit code 0 alone means the query succeeded, even
49
- when it prints errors. A workspace that has never produced a log timeline
50
- refuses with `STIM_NO_PROJECT`; in a monorepo, the error names the nearest
51
- registered descendant app with logs when one exists.
52
-
53
- Use `stim start --reset-cache` to recover from stale Metro transforms or file-map
54
- state. It restarts only this app's verified owned Metro, keeping its port and
55
- devices, with Metro's own reset: every store in the app's Metro config is cleared,
56
- including this app's shared transform store, so other worktrees of the same app
57
- rebuild their transforms too. Other apps and native build caches are unchanged.
58
-
59
- Use `stim doctor --platform ios` or `stim doctor --platform android` when only
60
- one native platform is in scope; shared project checks still run. Doctor also
61
- prints the running CLI version and the `stim` installation resolved from PATH,
62
- and flags a resolved installation that is older than another available one.
63
-
64
- For parallel iOS work, SimSlim is recommended as an optional way to reduce
65
- simulator background services and memory use. Run `stim guide lifecycle simslim`
66
- to review the service tradeoffs and configure a profile. Doctor recommends the
67
- setup without installing or enabling it. When a simulator cannot start
68
- processes, Stim bounds the wait and reports observed host memory pressure when
69
- available; free memory before retrying. A timeout alone does not prove OOM.
70
-
71
- For stale Android CMake launcher findings, stop native builds and run
72
- `stim doctor --fix --platform android` in the affected checkout. It clears
73
- affected ignored, untracked generated `.cxx` configurations in the app and
74
- installed native modules, then reports remaining findings. The next build
75
- recreates that output; source files, custom launcher settings, and shared
76
- ccache entries are preserved. Its cache-lock check cannot detect uncached,
77
- release-swap fallback, or direct Gradle builds; stop all native builds first.
78
-
79
35
  Stim builds or restores the app, installs it, launches it, and checks launch
80
36
  readiness. Plain output streams progress and reports the complete result. Use
81
37
  `--json` when a script needs structured data.
82
38
 
83
- To use an existing EAS development build, run `stim ios --eas-profile ios-simulator`
84
- or `stim android --eas-profile development` with your project's profile name.
85
- The profile must be an internal development build. Add `--device` for a connected
86
- phone; iOS requires `ios.simulator: true` for a simulator and false for a phone.
87
- Stim downloads a matching native build and connects it to the workspace's Metro.
88
- EAS CLI manages the downloaded artifact cache. An iOS provisioning failure
89
- points to EAS device registration and rebuild commands.
90
- On a miss it stops and prints the EAS build command, which may incur charges;
91
- it never triggers the build automatically. See `stim guide lifecycle eas` for
92
- setup, environment handling, and cache behavior.
93
-
94
- Use `stim ios --scheme "App Staging"` when a project has several shared Xcode
95
- app schemes. Combine it with `--configuration Release` if needed. Without the
96
- flag, Stim keeps its automatic scheme selection. Explicit schemes use separate
97
- artifact caches and Xcode build directories; run `stim guide lifecycle builds`
98
- for provider behavior. This selects an Xcode scheme, not a URL scheme.
99
-
100
- Launch evidence does not prove that the UI is interactive. Apps can optionally
101
- [declare readiness with two debug log messages](https://stim.appandflow.com/docs/dev-server-and-logs#optional-app-declared-readiness),
102
- without a package or SDK. A captured pending message extends the default
103
- three-second stability window after bundle delivery to a bounded wait for ready.
104
- Managed Metro response capture distinguishes a finished build from a finished
105
- response; servers without capture use the build-complete marker. Run
106
- `stim guide lifecycle readiness` for implementation instructions; verify the
107
- expected screen separately.
108
-
109
- `stim reload [ios|android]` requests a JavaScript reload in the live app on this
110
- workspace's owned local device. Use it when an error screen remains after a fix,
111
- and on Android after a failed first bundle load. An iOS app whose first bundle
112
- failed never connects to Metro, so reload cannot reach it. It reloads
113
- JavaScript and never restarts the app. It is not part of the normal workflow
114
- and does not build, install, boot, or launch an app. The platform is optional
115
- when only one app is live.
116
- Success confirms that the request was sent; Stim does not observe completion.
117
- Verify the expected UI on the reported device and inspect `stim logs --errors`
118
- before claiming recovery.
119
-
120
- For an isolated branch, create the worktree with Git, then warm it:
121
-
122
- ```bash
123
- git worktree add -b feature/settings ../feature-settings HEAD
124
- cd ../feature-settings
125
- stim worktree warm
126
- ```
127
-
128
- If a harness already created the linked worktree, run only `stim worktree warm`
129
- there. Warm copies missing ignored state from the source checkout, including
130
- eligible `.env` and local configuration files. Existing entries are preserved;
131
- existing ignored directories such as `node_modules` are skipped whole.
132
-
133
- `stim worktree warm --refresh` brings the source checkout up to date first: it
134
- checks the upstream, fetches changes when needed, fast-forwards whatever branch is checked out there, and installs what
135
- the new commits moved before copying. It refuses a source checkout it cannot
136
- move (uncommitted tracked changes, a rebase or merge in progress, a detached
137
- HEAD, a diverged branch) and never switches branches. One lock per repository
138
- keeps a copy from reading a `node_modules` a refresh is rewriting; plain warms
139
- still run side by side. A plain warm also refuses (`STIM_DEPS_INCOMPLETE`) when the last
140
- install of the lockfile on disk did not finish, instead of copying a partial
141
- `node_modules`; `stim worktree warm --refresh` reinstalls it.
142
-
143
- Wait for warm to finish before editing, installing dependencies, starting
144
- Metro/builds, or running another warm in that worktree. Concurrent writes are
145
- unsafe: entries created after the initial check can be overwritten or removed.
146
-
147
- After the work is preserved, `stim worktree remove` removes any linked
148
- worktree, warmed or not. Git-created branches stay. See the
149
- [worktree guide](https://stim.appandflow.com/docs/worktrees) for exclusions
150
- and cleanup rules.
151
-
152
- ## Reference
153
-
154
- The [documentation website](https://stim.appandflow.com/) explains the
155
- human workflow and all commands.
39
+ ## Documentation
40
+
41
+ The [documentation website](https://stim.appandflow.com/) is the full
42
+ reference:
43
+
44
+ - [Getting started](https://stim.appandflow.com/docs/getting-started): terms,
45
+ the first run, parallel worktrees, and what to do when a run fails.
46
+ - [Worktrees](https://stim.appandflow.com/docs/worktrees): `worktree warm`,
47
+ `--refresh`, `worktree remove`, and `gc --worktrees` to remove finished
48
+ worktrees in bulk.
49
+ - [Build caches](https://stim.appandflow.com/docs/build-caches): what `gc`
50
+ reports and trims, including the build outputs of idle workspaces.
51
+ - [Devices](https://stim.appandflow.com/docs/owned-devices): owned simulators
52
+ and emulators, physical devices, slots, and remote devices.
53
+ - [EAS development builds](https://stim.appandflow.com/docs/eas-builds).
54
+ - [Commands](https://stim.appandflow.com/docs/commands) and
55
+ [settings](https://stim.appandflow.com/docs/settings).
56
+ - [Troubleshooting](https://stim.appandflow.com/docs/troubleshooting): every
57
+ refusal code and its remedy.
156
58
 
157
59
  The installed CLI contains version-matched operational guidance:
158
60
 
@@ -163,10 +65,6 @@ stim <command> --help
163
65
  stim guide
164
66
  ```
165
67
 
166
- Runtime state defaults to `~/.stim`. Set `STIM_HOME` to move it. Stim manages
167
- owned simulators and emulators, leases connected physical devices, and supports
168
- configured remote devices.
169
-
170
68
  ## Migrating from stim-cli
171
69
 
172
70
  Remove the old global package before installing `stim`, since both provide the
@@ -180,36 +78,6 @@ npm install --global stim
180
78
  Update programmatic imports from `stim-cli/cache-manifest` to
181
79
  `stim/cache-manifest`. The `@stim-cli/*` packages keep their names.
182
80
 
183
- MIT License.
184
-
185
- ### Multiple devices in one workspace
186
-
187
- Use a stable slot name for each simulator, emulator, or physical device. Slots
188
- can use identical models and all share the workspace's Metro server and build
189
- cache. Omit `--slot` to reuse your existing default device.
190
-
191
- ```sh
192
- stim ios --slot phone
193
- stim ios --slot tablet --device-type "iPad Pro 13-inch (M4)"
194
- stim ios --slot hardware --device <udid>
195
- stim logs --slot tablet --source device
196
- stim stop --slot tablet
197
- ```
81
+ ## License
198
82
 
199
- `stim status` shows every slot. Plain `stim stop` stops the whole workspace;
200
- `--slot` preserves the shared server and other devices. `stim reload ios` or
201
- `stim reload android` can reload multiple connected devices. Read
202
- `stim guide lifecycle options` for launch verification and recycling behavior.
203
-
204
- ### Named server ports
205
-
206
- Use `stim ports get web` to reserve a workspace port for a server Stim does not
207
- manage, then pass that number to the server, for example
208
- `pnpm exec vite --port "$(stim ports get web)" --strictPort`.
209
- `stim ports` lists named allocations and managed Metro. `stim ports stop web`
210
- stops the TCP listener and releases its port; `--dry-run` previews the action.
211
- `stim ports release web` releases without stopping. Omitting the label selects
212
- all named ports. These commands leave Metro alone, and `stim stop` leaves
213
- named ports alone. Worktree removal and `gc --delete` stop and release named
214
- ports for removed workspaces. Read `stim guide ports` for the reserved band,
215
- listener ownership, and allocation limits.
83
+ MIT
@@ -1,37 +1,38 @@
1
- import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
- import { B as validateDeviceSlot, C as withConfigLock, E as ownership_claim_exports, F as deviceSlotKey, I as deviceSlotPlatforms, P as deviceSlotFileKey, S as upsertProject, b as setDevice, f as getProject, g as loadConfig, l as getConcurrencyLimits, y as saveConfig } from "./config-B7iyKWkL.mjs";
3
- import { r as createNdjsonWriter } from "./ndjson-BZwI1NBv.mjs";
4
- import { _ as workspaceLogsDir, h as workspaceDir, o as writeWorkspaceState, r as readWorkspaceState, s as emulatorLogFile } from "./workspace-state-BT5E3SA2.mjs";
5
- import { T as removeTemporaryEntry, g as repoRoot, s as gitCommonDir, w as makeTemporaryDirectory } from "./worktree-FG_ZKKdb.mjs";
6
- import { a as findProjectRoot, c as projectShortcut, i as detectIsExpo, n as detectAndroidPackage, r as detectBundleId, t as appProjectProblem } from "./project-DDZWf_16.mjs";
7
- import { n as MODE_EXPO, p as writeWorkspaceLaunch, t as MODE_BARE } from "./state-DZobzxLW.mjs";
8
- import { r as waitForChild, t as createLineReader } from "./process-output-Dl5uKE03.mjs";
9
- import { a as resolveProjectMetro, i as pidExists, o as signalProcessTree } from "./metro-C7bcJ0uo.mjs";
10
- import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-Cp52QAK1.mjs";
11
- import { A as resolveSettings, B as artifactCachePolicy, D as remoteDeviceSettingError, E as remoteAndroidSetting, F as tunnelModeSetting, H as optimizationBuildProfile, I as unknownSettingKeys, N as settingShapeErrors, R as detectProviders, T as publicUrlSetting, U as resolveOptimizations, V as compilerCacheFallbackMessage, a as SETTING_SHAPE_REMEDY, d as cacheProviderSettingError, j as settingFile, k as resolveCacheProviderConfig, r as REMOTE_DEVICE_BACKENDS, s as androidAvdConfigSettingError, u as androidDataPartitionSizeGbSettingError, x as metroWarmupUrlSetting } from "./settings-BXXorVM2.mjs";
12
- import { B as waitForBoot, E as memoizeEmulatorProbe, F as resetAdoptedAvd, I as resolveOwnedAvdSerial, L as resolvePhysicalDevice, M as physicalDeviceModel, P as probeEmulatorSerial, S as listAdbDevices, _ as emulatorFailureRemedy, a as androidPoolNoCandidatesRefusal, g as emulatorDiskSpaceRemedy, i as androidPoolCandidates, n as androidDeviceAbi, o as androidSystemImageAbi, r as androidHome, s as androidToolCwd, t as androidBuildToolName, v as extractEmulatorFailure, w as listInstalledSystemImages, y as findBuildTool } from "./android-DWssqHQ8.mjs";
13
- import { _ as launchErrorPreview, c as launchErrorReport, g as stepTimer, h as stepClock, i as formatDuration, l as phaseLine, n as appReadinessMessage, p as shortHash } from "./command-output-B3ycpsYv.mjs";
14
- import { C as untrackedNativeFiles, D as readAssetManifest, E as compareAssetManifests, S as untrackedMissLine, T as captureAssetManifest, _ as providerUploadOutcome, b as storeBuild, c as buildCacheKey, d as filesystemBuildCapability, f as fingerprintDiffRecord, g as providerDownloadPath, h as prepareProviderDownloadDir, i as releaseBuildLock, l as describeFingerprintMiss, m as fingerprintProject, n as acquireBuildLock, o as takeoverLine, p as fingerprintDiffSuffix, s as waitForBuild, t as WAIT_CEILING_MS, v as refingerprintAfterMutation, w as assetDiffReason, x as storedAssetManifest, y as resolveBuild } from "./build-lock-CgtsB6TV.mjs";
15
- import { t as cleanLine } from "./server-expo-BVAA1OqJ.mjs";
16
- import { C as resolveRemote, S as resolveEasCliBin, _ as exitAfterFlush, f as RESOLVE_TIMEOUT_MS, g as easAuthNote, h as checkEasAuth, l as parkedMaxSetting, m as cacheLevel, p as UPLOAD_TIMEOUT_MS, v as isEasAuthFailureText, w as uploadRemote, y as loadProjectProvider } from "./teardown-BJqEzPo_.mjs";
17
- import { c as ensureWorkspaceStorageSafely, d as launchOutcomeRecord, f as noMetroMessage, g as warmMetro, i as detectEntryFile, m as resolveMetroWithRetry, n as pickDevClientScheme, o as needsPrebuild, p as noMetroRemedy, s as runPrebuild, t as devClientScheme, u as isPhysicalDeviceRequest, v as isEasBuildFailure, y as resolveEasDevelopmentBuild } from "./dev-client-BmT-ioFI.mjs";
18
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-BYfZz_S-.mjs";
19
- import { D as verifyAndroidReleaseLaunch, E as unverifiedLaunchLines, O as verifyLaunch, S as launchAndroidReleaseApp, _ as installConflictKind, c as RELEASE_VERIFY_WAIT_MS, d as androidDevClientUrl, g as installAndroidApp, h as deviceShellArg, i as LAUNCH_BUNDLING, l as VERIFY_TIMEOUT_MS, n as DEFAULT_METRO_PORT, o as LAUNCH_FATAL, s as LAUNCH_UNVERIFIED, t as ADB_INSTALL_TIMEOUT_MS, u as androidAppProcess, x as launchAndroidApp } from "./app-install-BDbsSgCl.mjs";
20
- import { a as lostLine, c as releaseLeaseOnSignal, i as leaseExpiryText, l as runLease, n as DEBUG_VERIFY_STEP_MS, o as lostRefusal, r as acquireRunLease, s as parseDeviceWait, t as selectFromPool, u as waitFlagConflict } from "./device-pool-BxRwdufO.mjs";
21
- import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-C-oAHRT1.mjs";
22
- import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-BYcZ6n1h.mjs";
23
- import { i as ensureOwnedDevice, l as AvdBootError, o as unknownAndroidSystemImageRefusal, r as ensureBooted, t as checkDeviceCapacity, u as AvdRecoveryError } from "./device-BUoZyMoR.mjs";
24
- import { a as ensureMetroReachable, b as ownedSessionName, l as resolveRemoteContext, n as binOnPath, o as ensureRemoteBootOwned, s as remoteAndroidDeps } from "./device-remote-ByN8P6CB.mjs";
25
- import { a as buildAndroid, c as CCACHE_NOT_RUN, f as resolveCcache, l as CCACHE_UNAVAILABLE, n as resolveAndroidCas, o as productFlavorRefusal, p as formatDiagnostic, r as resolveAndroidCompilerCache, s as readProductFlavors, u as ccacheActivityLine } from "./android-cas-SWIjzeuL.mjs";
26
- import { d as startBuildHeartbeat, f as tailLines, n as HEARTBEAT_INTERVAL_MS } from "./xcode-B6YaWeg0.mjs";
27
- import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-ZUUe0TQP.mjs";
1
+ import { G as parseDeviceSlotOption, H as deviceSlotKey, J as validateDeviceSlot, S as setDevice, T as withConfigLock, U as deviceSlotPlatforms, V as deviceSlotFileKey, f as getProject, g as loadConfig, k as ownership_claim_exports, l as getConcurrencyLimits, w as upsertProject, x as saveConfig } from "./config-Cp9lM3wX.mjs";
2
+ import { t as getExecutor } from "./exec-jIAlgilk.mjs";
3
+ import { r as createNdjsonWriter } from "./ndjson-9uFNRwfe.mjs";
4
+ import { _ as workspaceDir, b as workspaceLogsDir, c as writeWorkspaceState, i as recordWorkspaceUse, l as emulatorLogFile, r as readWorkspaceState } from "./workspace-state-DF2gCFhx.mjs";
5
+ import { E as removeTemporaryEntry, T as makeTemporaryDirectory, _ as repoRoot, s as gitCommonDir } from "./worktree-R_cUlE7s.mjs";
6
+ import { a as detectIsExpo, i as detectBundleId, l as projectShortcut, n as appProjectProblem, o as findProjectRoot, r as detectAndroidPackage } from "./project-DS112DYB.mjs";
7
+ import { n as MODE_EXPO, p as writeWorkspaceLaunch, t as MODE_BARE } from "./state-NcJzMEBF.mjs";
8
+ import { r as waitForChild, t as createLineReader } from "./process-output-Fpl8PYi8.mjs";
9
+ import { a as pidExists, c as resolveProjectMetro, l as signalProcessTree } from "./metro-DrLHaMn2.mjs";
10
+ import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-D15Op8vN.mjs";
11
+ import { A as settingFile, E as remoteAndroidSetting, F as unknownSettingKeys, J as optimizationBuildProfile, K as artifactCachePolicy, L as REMOTE_DEVICE_BACKENDS, M as settingShapeErrors, O as resolveCacheProviderConfig, P as tunnelModeSetting, W as detectProviders, Y as resolveOptimizations, b as metroWarmupUrlSetting, i as SETTING_SHAPE_REMEDY, k as resolveSettings, l as androidDataPartitionSizeGbSettingError, o as androidAvdConfigSettingError, q as compilerCacheFallbackMessage, u as cacheProviderSettingError, w as publicUrlSetting } from "./settings-D030k0t4.mjs";
12
+ import { C as listAdbDevices, D as memoizeEmulatorProbe, H as waitForBoot, I as probeEmulatorSerial, L as resetAdoptedAvd, P as physicalDeviceModel, R as resolveOwnedAvdSerial, T as listInstalledSystemImages, _ as emulatorDiskSpaceRemedy, a as androidPoolNoCandidatesRefusal, b as findBuildTool, i as androidPoolCandidates, n as androidDeviceAbi, o as androidSystemImageAbi, r as androidHome, s as androidToolCwd, t as androidBuildToolName, v as emulatorFailureRemedy, y as extractEmulatorFailure, z as resolvePhysicalDevice } from "./android-_y4Ohi5z.mjs";
13
+ import { _ as stepTimer, c as launchErrorReport, f as refuseNoProject, g as stepClock, i as formatDuration, l as phaseLine, m as shortHash, n as appReadinessMessage, v as launchErrorPreview } from "./command-output-gByVmIjy.mjs";
14
+ import { C as assetDiffReason, E as readAssetManifest, S as untrackedNativeFiles, T as compareAssetManifests, _ as refingerprintAfterMutation, a as waitForBuild, b as storedAssetManifest, c as describeFingerprintMiss, d as fingerprintDiffRecord, f as fingerprintDiffSuffix, g as providerUploadOutcome, h as providerDownloadPath, m as prepareProviderDownloadDir, o as waitForSharedBuild, p as fingerprintProject, r as releaseBuildLock, s as buildCacheKey, t as acquireBuildLock, u as filesystemBuildCapability, v as resolveBuild, w as captureAssetManifest, x as untrackedMissLine, y as storeBuild } from "./build-lock-DPXPLgTc.mjs";
15
+ import { t as cleanLine } from "./server-expo-B_lctBR3.mjs";
16
+ import { S as uploadRemote, _ as loadProjectProvider, b as resolveEasCliBin, d as UPLOAD_TIMEOUT_MS, f as cacheLevel, g as isEasAuthFailureText, h as exitAfterFlush, m as easAuthNote, o as parkedMaxSetting, p as checkEasAuth, u as RESOLVE_TIMEOUT_MS, x as resolveRemote } from "./sim-pool-KaIsp2Ft.mjs";
17
+ import { C as isEasBuildFailure, _ as noMetroMessage, c as ensureBooted, d as AvdRecoveryError, g as launchOutcomeRecord, h as isPhysicalDeviceRequest, i as detectEntryFile, l as ensureOwnedDevice, n as pickDevClientScheme, o as needsPrebuild, p as ensureWorkspaceStorageSafely, s as runPrebuild, t as devClientScheme, u as AvdBootError, v as noMetroRemedy, w as resolveEasDevelopmentBuild, x as warmMetro, y as resolveMetroWithRetry } from "./dev-client-HyqISM0h.mjs";
18
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-DWaiRD0e.mjs";
19
+ import { n as NO_BUILD_PROGRESS, s as startBuildProgress } from "./build-progress-CVqkfPC6.mjs";
20
+ import { a as VERIFY_TIMEOUT_MS, d as verifyLaunch, i as RELEASE_VERIFY_WAIT_MS, l as unverifiedLaunchLines, n as LAUNCH_FATAL, r as LAUNCH_UNVERIFIED, t as LAUNCH_BUNDLING, u as verifyAndroidReleaseLaunch } from "./launch-verify-Q_OtW1Rf.mjs";
21
+ import { _ as launchAndroidReleaseApp, d as deviceShellArg, f as installAndroidApp, g as launchAndroidApp, o as androidAppProcess, p as installConflictKind, r as DEFAULT_METRO_PORT, s as androidDevClientUrl, t as ADB_INSTALL_TIMEOUT_MS } from "./app-install-Uf3a83Tv.mjs";
22
+ import { a as lostLine, c as releaseLeaseOnSignal, i as leaseExpiryText, l as runLease, n as DEBUG_VERIFY_STEP_MS, o as lostRefusal, r as acquireRunLease, s as parseDeviceWait, t as selectFromPool, u as waitFlagConflict } from "./device-pool-CnTAz8X1.mjs";
23
+ import { d as startBuildHeartbeat, f as tailLines, g as acquireBuildSlot, n as HEARTBEAT_INTERVAL_MS, v as releaseBuildSlot } from "./xcode-BZqyxh3e.mjs";
24
+ import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-DG9e66Rz.mjs";
25
+ import { o as unknownAndroidSystemImageRefusal, r as checkDeviceCapacity } from "./simslim-B_LsGLuJ.mjs";
26
+ import { a as ensureMetroReachable, b as ownedSessionName, l as resolveRemoteContext, n as binOnPath, o as ensureRemoteBootOwned, s as remoteAndroidDeps } from "./device-remote-AQ5rQPqr.mjs";
27
+ import { a as buildAndroid, c as CCACHE_NOT_RUN, f as resolveCcache, l as CCACHE_UNAVAILABLE, n as resolveAndroidCas, o as productFlavorRefusal, p as formatDiagnostic, r as resolveAndroidCompilerCache, s as readProductFlavors, u as ccacheActivityLine } from "./android-cas-eiExNLf7.mjs";
28
+ import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-DU9A-gYw.mjs";
28
29
  import { t as spawnEntry } from "./spawn-entry-nCE6m770.mjs";
29
- import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-D1Kaf-eT.mjs";
30
+ import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-Vfz83GCF.mjs";
30
31
  import { constants, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync } from "node:fs";
31
32
  import { InvalidArgumentError } from "commander";
32
33
  import { basename, dirname, isAbsolute, join, relative } from "node:path";
33
- import { createWarnOnce, loadCacheProvider, resolveTieredBuild, storeTieredBuild } from "@stim-cli/cache";
34
34
  import chalk from "chalk";
35
+ import { createWarnOnce, loadCacheProvider, resolveTieredBuild, storeTieredBuild } from "@stim-cli/cache";
35
36
  //#region src/engine/apk-swap.ts
36
37
  const ANDROID_BUNDLE_NAME = "index.android.bundle";
37
38
  const LAST_LINES = 5;
@@ -644,7 +645,7 @@ function fail$1(code, message, remedy, extra = {}) {
644
645
  };
645
646
  }
646
647
  async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo, device, physical, buildPlan, cacheProviderConfig, requestedBuildCache, easBuild, androidPackage: initialPackage, record, maxBuilds, progress }, { deviceAbi, fingerprint, untracked, resolveCached, storeCached, storedAssets, captureAssets, acquireLock, releaseLock, waitForBuild, loadProvider, easAuth, resolveRemoteBuild, uploadRemoteBuild, loadCacheProviderModule, acquireSlot, releaseSlot, needsPrebuildFor, prebuild, build, ccacheFor, swapApk, readState, now }) {
647
- const { phase, out, estimates, stats } = progress;
648
+ const { phase, out, estimates, stats, step } = progress;
648
649
  const { variant, release, profile: buildProfile, cas, cache: cachePolicy } = buildPlan;
649
650
  const useBuildCache = cachePolicy.read;
650
651
  let androidPackage = initialPackage;
@@ -715,6 +716,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
715
716
  stats.setCacheKey(cacheKey);
716
717
  return true;
717
718
  }
719
+ step("cache-lookup");
718
720
  const fingerprintTimer = stepTimer(now);
719
721
  try {
720
722
  const computed = await fingerprint(root, { platform: PLATFORM });
@@ -841,88 +843,37 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
841
843
  if (!easBuild) await resolveRemoteArtifact();
842
844
  let waitedForBuild = null;
843
845
  let releasedWait = null;
844
- async function waitForSharedBuild() {
845
- if (!useBuildCache) return true;
846
- const waitStarted = now();
847
- let failedHolder;
848
- while (!apkPath) {
849
- let attempt = null;
850
- try {
851
- attempt = acquireLock({
852
- platform: PLATFORM,
853
- key: cacheKey,
854
- root,
855
- logFile: buildLog
856
- });
857
- } catch (err) {
858
- const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
859
- if (refusal) {
860
- phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
861
- return false;
862
- }
863
- phase("build", chalk.yellow(`could not take the build lock: ${err?.message || err}; building anyway`));
864
- }
865
- if (attempt?.acquired) {
866
- buildLock = attempt;
867
- const previous = attempt.tookOver ?? failedHolder;
868
- if (previous) phase("build", chalk.yellow(takeoverLine(previous)));
869
- break;
870
- } else if (attempt?.held) {
871
- releasedWait = null;
872
- const holder = attempt.held;
873
- const who = holder.projectRoot || "another workspace";
874
- phase("build", `${who} is already building ${shortHash(hash)} (pid ${holder.pid})${holder.logFile ? ` -- tail ${holder.logFile}` : ""} -- stim guide lifecycle concurrency`);
875
- let waited = null;
876
- try {
877
- const ceilingMs = WAIT_CEILING_MS - (now() - waitStarted);
878
- if (ceilingMs <= 0) throw Object.assign(/* @__PURE__ */ new Error(`Waited ${formatDuration(now() - waitStarted)} for shared builds without an artifact; ${who} (pid ${holder.pid}) holds ${attempt.path}.`), {
879
- code: "STIM_BUILD_WAIT_TIMEOUT",
880
- lockPath: attempt.path
881
- });
882
- waited = await waitForBuild({
883
- platform: PLATFORM,
884
- key: cacheKey,
885
- out,
886
- ceilingMs
887
- });
888
- } catch (err) {
889
- const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
890
- if (refusal) {
891
- phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
892
- return false;
893
- }
894
- const wtErr = err;
895
- if (wtErr?.code !== "STIM_BUILD_WAIT_TIMEOUT") throw err;
896
- phaseFailure = fail$1("STIM_BUILD_WAIT_TIMEOUT", wtErr.message, `Check pid ${holder.pid}; if it is not really building, remove ${wtErr.lockPath} and run \`stim android\` again.`, { lastBuildStatus: true });
897
- return false;
898
- }
899
- if (waited?.hit) {
900
- apkPath = waited.hit ?? null;
901
- record.cacheHit = "local";
902
- waitedForBuild = {
903
- pid: holder.pid,
904
- ms: waited.waitedMs
905
- };
906
- phase("build", `waited ${formatDuration(waited.waitedMs)} for ${who}'s build -> installed from cache -- stim guide lifecycle concurrency`);
907
- } else if (waited?.lockReleased) {
908
- failedHolder = void 0;
909
- releasedWait = {
910
- facts: {
911
- pid: holder.pid,
912
- ms: waited.waitedMs
913
- },
914
- who
915
- };
916
- phase("build", `${who}'s build lock was released; rechecking this workspace before building`);
917
- } else {
918
- phase("build", chalk.yellow(`${who}'s build ended without an artifact (${waited?.builderFailed}); retrying the build lock`));
919
- failedHolder = holder;
920
- }
921
- } else break;
846
+ if (!easBuild && useBuildCache && !apkPath) {
847
+ const shared = await waitForSharedBuild({
848
+ platform: PLATFORM,
849
+ key: cacheKey,
850
+ fingerprint: hash,
851
+ root,
852
+ logFile: buildLog,
853
+ command: "stim android",
854
+ acquire: acquireLock,
855
+ wait: waitForBuild,
856
+ now,
857
+ phase: (text) => {
858
+ step("wait");
859
+ phase("build", text);
860
+ },
861
+ warn: (text) => phase("build", chalk.yellow(text)),
862
+ out
863
+ });
864
+ if (shared.refusal) {
865
+ const { code, message, remedy } = shared.refusal;
866
+ phaseFailure = fail$1(code, message, remedy, { lastBuildStatus: true });
867
+ return refused();
868
+ }
869
+ buildLock = shared.lock;
870
+ releasedWait = shared.released;
871
+ if (shared.hit) {
872
+ apkPath = shared.hit.path;
873
+ record.cacheHit = "local";
874
+ waitedForBuild = shared.hit.waited;
922
875
  }
923
- return true;
924
876
  }
925
- if (!easBuild && !await waitForSharedBuild()) return refused();
926
877
  let swapDir = null;
927
878
  let swapFellBack = false;
928
879
  const installableCachedApk = async (key, cachedPath) => {
@@ -976,6 +927,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
976
927
  phase("build", chalk.yellow(`could not take a build slot: ${err?.message || err}; building anyway`));
977
928
  }
978
929
  if (needsPrebuildFor(root, "android", isExpo)) {
930
+ step("prebuild");
979
931
  const pre = await prebuild(root, PLATFORM, writer, { isExpo });
980
932
  if (pre.failed) {
981
933
  phaseFailure = fail$1(pre.code, pre.reason, pre.remedy, {
@@ -1017,6 +969,7 @@ async function acquireAndroidArtifact({ root, buildLog, writer, settings, isExpo
1017
969
  }
1018
970
  }
1019
971
  if (!apkPath) {
972
+ step("compile");
1020
973
  phase("build", `compiling ${variant || "debug"} with Gradle`);
1021
974
  const built = await build({
1022
975
  root,
@@ -1508,7 +1461,7 @@ async function resolveInstallSerial({ root, slot, device, physical, remoteDevice
1508
1461
  });
1509
1462
  return resolved.serial;
1510
1463
  }
1511
- async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCheck, useBuildCache, variant, release, isExpo, metroPort, logsDir, emuLog, device, physical, remoteDevice, bootPromise, resolveAvdSerial, waitForDeviceBoot, bootDuration, apkPath, androidPackage: initialPackage, swapDir, record, waitedForBuild, ccache, uploadPending, providerUpload, providerName, remote, abandonedRemote: remoteWasAbandoned, started, startedAt, writer, phase, fail, readApkPackage, install, launch, launchRelease, resolveDevClientScheme, verifyLaunched, verifyReleaseLaunched, spawn, kill, pidAlive, verifyCollector, writeLaunch, writeState, now, out, emit, recordRun }) {
1464
+ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCheck, useBuildCache, variant, release, isExpo, metroPort, logsDir, emuLog, device, physical, remoteDevice, bootPromise, resolveAvdSerial, waitForDeviceBoot, bootDuration, apkPath, androidPackage: initialPackage, swapDir, record, waitedForBuild, ccache, uploadPending, providerUpload, providerName, remote, abandonedRemote: remoteWasAbandoned, started, startedAt, writer, phase, fail, readApkPackage, install, launch, launchRelease, resolveDevClientScheme, verifyLaunched, verifyReleaseLaunched, spawn, kill, pidAlive, verifyCollector, writeLaunch, writeState, now, out, emit, recordRun, enterPhase }) {
1512
1465
  let androidPackage = initialPackage;
1513
1466
  let leaseWarned = false;
1514
1467
  const raiseLeaseFor = (boundMs, beforeInstall) => {
@@ -1524,6 +1477,7 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1524
1477
  }
1525
1478
  return null;
1526
1479
  };
1480
+ enterPhase("install");
1527
1481
  const booted = await bootPromise;
1528
1482
  const runCommand = nativeRunCommand("android", slot, {
1529
1483
  physical,
@@ -1613,6 +1567,7 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1613
1567
  if (!androidPackage) return fail(LAUNCH_FAILED, "Could not determine this app's Android package name, so there is nothing to launch.", "Set `expo.android.package` in app.json / app.config.js, or `namespace` in android/app/build.gradle.", { lastBuildStatus: true });
1614
1568
  if (physical) raiseLeaseFor(0, false);
1615
1569
  const scheme = release ? void 0 : resolveDevClientScheme(root, apkPath);
1570
+ enterPhase("launch");
1616
1571
  const launchTimer = stepTimer(now);
1617
1572
  const launchedAt = now();
1618
1573
  writer.write({
@@ -1858,8 +1813,6 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
1858
1813
  if (dataPartitionSizeError) return fail("STIM_BAD_ARG", dataPartitionSizeError, "Set android.dataPartitionSizeGb to a whole number of GiB from 6 through 16384.");
1859
1814
  const avdConfigError = validateAvdConfig(settings, root);
1860
1815
  if (avdConfigError) return fail("STIM_BAD_ARG", avdConfigError, "Use only documented android.avdConfig keys, or an android.avdConfigFile fragment contained by the app directory.");
1861
- const remoteSettingError = remoteDeviceSettingError(settings);
1862
- if (remoteSettingError) return fail("STIM_BAD_ARG", remoteSettingError, `Set ios.remote and android.remote to one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}.`);
1863
1816
  const systemImage = resolveSystemImage(systemImageFlag, settings);
1864
1817
  const variant = easProfile !== void 0 ? "debug" : resolveVariant(variantFlag, settings);
1865
1818
  const flavorRefusal = productFlavorRefusal({
@@ -1932,32 +1885,47 @@ function androidCommand(program) {
1932
1885
  registerAndroid(program);
1933
1886
  }
1934
1887
  function registerAndroid(program) {
1935
- program.command("android").description("Build (or install from the shared cache), install and launch this workspace's Android app on its owned emulator, wired to the reserved Metro port. Never starts the bundler -- run `stim start` first.").option("--eas-profile <name>", "Download a matching EAS development build; on a miss, print the build command without running it").option("--slot <name>", "Reusable device slot within this workspace (default: default)", validateDeviceSlot).option("--json", "Emit the facts as a single JSON line on stdout; every other line goes to stderr").option("--no-metro-check", "Skip the reserved-port Metro health check (the app will load no bundle unless something else serves it)").option("--no-build-cache", "Build fresh, ignoring cached artifacts (local and the project's build-cache provider); the fresh build still replaces the cache entry").option("--variant <name>", "Gradle variant to assemble and install (e.g. productionDebug on a flavored project); overrides the android.variant setting. A variant ending in Release embeds the JS bundle and skips Metro entirely. Default: debug").option("--system-image <id>", "Android system image to create this workspace's owned AVD from, as the sdkmanager package id (e.g. \"system-images;android-36;google_apis;arm64-v8a\"). Overrides the android.systemImage setting for this invocation. An unknown id refuses with STIM_BAD_ARG and prints the installed images.").option("--device [serial]", "Install and launch on a connected physical device instead of this workspace's owned emulator. With no serial, the first connected device this workspace can lease is used. Stim never creates, boots, or deletes a physical device.").option("--remote <backend>", "Install and launch on a remote device with proxy or EAS. Builds are local unless --eas-profile selects an existing EAS build.", (value) => {
1888
+ program.command("android").description("Build (or install from the shared cache), install and launch this workspace's Android app on its owned emulator, wired to the reserved Metro port. Never starts the bundler -- run `stim start` first.").option("--eas-profile <name>", "Download a matching EAS development build; on a miss, print the build command without running it").option("--slot <name>", "Reusable device slot within this workspace (default: default)", parseDeviceSlotOption).option("--json", "Emit the facts as a single JSON line on stdout; every other line goes to stderr").option("--no-metro-check", "Skip the reserved-port Metro health check (the app will load no bundle unless something else serves it)").option("--no-build-cache", "Build fresh, ignoring cached artifacts (local and the project's build-cache provider); the fresh build still replaces the cache entry").option("--variant <name>", "Gradle variant to assemble and install (e.g. productionDebug on a flavored project); overrides the android.variant setting. A variant ending in Release embeds the JS bundle and skips Metro entirely. Default: debug").option("--system-image <id>", "Android system image to create this workspace's owned AVD from, as the sdkmanager package id (e.g. \"system-images;android-36;google_apis;arm64-v8a\"). Overrides the android.systemImage setting for this invocation. An unknown id refuses with STIM_BAD_ARG and prints the installed images.").option("--device [serial]", "Install and launch on a connected physical device instead of this workspace's owned emulator. With no serial, the first connected device this workspace can lease is used. Stim never creates, boots, or deletes a physical device.").option("--remote <backend>", "Install and launch on a remote device with proxy or EAS. Builds are local unless --eas-profile selects an existing EAS build.", (value) => {
1936
1889
  if (REMOTE_DEVICE_BACKENDS.includes(value)) return value;
1937
1890
  throw new InvalidArgumentError(`expected one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}`);
1938
1891
  }).option("--wait <seconds>", "How long to wait for another workspace to release the device it leases, before refusing with STIM_DEVICE_BUSY (default 60, 0 refuses at once). Only with --device.").option("--no-wait", "Install on a device another workspace leases instead of waiting: this run takes no lease and, when both workspaces build the same app id, the install terminates the holder's running app. Only with --device.").action(async (opts) => {
1939
1892
  const root = findProjectRoot(process.cwd());
1940
1893
  if (!root) {
1941
- console.error(chalk.red("Not in a React Native project (no package.json found)."));
1942
- process.exit(1);
1894
+ refuseNoProject({ json: Boolean(opts.json) });
1943
1895
  return;
1944
1896
  }
1945
- if (!(await withWorkspaceProcessLock(workspaceDir(root), "native-run", () => runAndroid({
1946
- root,
1947
- slot: opts.slot,
1948
- easProfile: opts.easProfile,
1949
- json: Boolean(opts.json),
1950
- metroCheck: opts.metroCheck !== false,
1951
- useBuildCache: opts.buildCache !== false,
1952
- variant: opts.variant ?? null,
1953
- systemImage: opts.systemImage ?? null,
1954
- remoteDevice: opts.remote ?? null,
1955
- device: opts.device ?? null,
1956
- wait: opts.wait,
1957
- waitConflict: waitFlagConflict(process.argv)
1958
- }), {
1897
+ if (!(await withWorkspaceProcessLock(workspaceDir(root), "native-run", async (claim) => {
1898
+ recordWorkspaceUse(root);
1899
+ const progress = startBuildProgress({
1900
+ root,
1901
+ platform: "android",
1902
+ slot: opts.slot ?? "default",
1903
+ claim,
1904
+ note: (line) => console.error(chalk.dim(line))
1905
+ });
1906
+ try {
1907
+ return await runAndroid({
1908
+ root,
1909
+ slot: opts.slot,
1910
+ easProfile: opts.easProfile,
1911
+ json: Boolean(opts.json),
1912
+ metroCheck: opts.metroCheck !== false,
1913
+ useBuildCache: opts.buildCache !== false,
1914
+ variant: opts.variant ?? null,
1915
+ systemImage: opts.systemImage ?? null,
1916
+ remoteDevice: opts.remote ?? null,
1917
+ device: opts.device ?? null,
1918
+ wait: opts.wait,
1919
+ waitConflict: waitFlagConflict(process.argv),
1920
+ progress
1921
+ });
1922
+ } finally {
1923
+ progress.clear();
1924
+ }
1925
+ }, {
1959
1926
  external: true,
1960
- waitMs: 18e5
1927
+ waitMs: 18e5,
1928
+ declareSpawns: true
1961
1929
  })).ok) process.exit(1);
1962
1930
  });
1963
1931
  }
@@ -2055,6 +2023,10 @@ function androidSlotOptions(options) {
2055
2023
  ...args,
2056
2024
  slot
2057
2025
  }),
2026
+ ensureDeviceBooted: (args) => base.ensureDeviceBooted({
2027
+ ...args,
2028
+ slot
2029
+ }),
2058
2030
  checkCapacity: (args) => base.checkCapacity({
2059
2031
  ...args,
2060
2032
  slot
@@ -2095,6 +2067,7 @@ function avdSetupFailure(error, root, logFile) {
2095
2067
  }
2096
2068
  async function runAndroid(options = {}) {
2097
2069
  let { root, json, commandRemoteBackend, resolveSettingsFor, resolveRemoteDeviceContext, makeRemoteDeviceDeps, resolveEasBin, ensureRemoteMetro, ensureRemoteOwned, detectRemoteProviders, metroCheck, useBuildCache: requestedBuildCache, easProfile, resolveEasBuild, variantFlag, systemImageFlag, deviceFlag, waitFlag, waitConflict, acquireLease, makeRunLease, selectPool, onLeaseSignal, listDevices, deviceModel, deviceAbi, isEmulatorDevice, readApkPackage, getLimits, checkCapacity, acquireSlot, releaseSlot, ensureDevice, listSystemImages, ensureDeviceBooted, resolveAvdSerial, waitForDeviceBoot, resolveMetro, prewarmMetro, resolveMetroRetrying, readState, pidAlive, verifyCollector, verifyLaunched, ensureStorage, fingerprint, untracked, resolveCached, storeCached, storedAssets, captureAssets, acquireLock, releaseLock, waitForBuild, loadProvider, easAuth, resolveRemoteBuild, uploadRemoteBuild, resolveCacheProvider, loadCacheProviderModule, needsPrebuildFor, prebuild, build, ccacheFor, install, launch, launchRelease, verifyReleaseLaunched, swapApk, resolveDevClientScheme, spawn, kill, createWriter, writeLaunch, writeState, recordStats, readEstimates, now, out, emit } = androidSlotOptions(options);
2070
+ const progress = options.progress ?? NO_BUILD_PROGRESS;
2098
2071
  const slot = validateDeviceSlot(options.slot);
2099
2072
  const started = now();
2100
2073
  const startedAt = new Date(started).toISOString();
@@ -2159,7 +2132,8 @@ async function runAndroid(options = {}) {
2159
2132
  platform: PLATFORM,
2160
2133
  write: recordStats,
2161
2134
  now,
2162
- note: (line) => out(phaseLine("stats", chalk.dim(line)))
2135
+ note: (line) => out(phaseLine("stats", chalk.dim(line))),
2136
+ phases: () => progress.durations()
2163
2137
  });
2164
2138
  const recordRun = stats.record;
2165
2139
  let ccacheActivity = CCACHE_NOT_RUN;
@@ -2300,7 +2274,6 @@ async function runAndroid(options = {}) {
2300
2274
  if (remoteBackend) {
2301
2275
  const resolved = await resolveRemoteDeviceContext({
2302
2276
  root,
2303
- label,
2304
2277
  platform: PLATFORM,
2305
2278
  backend: remoteBackend,
2306
2279
  easBin: resolveEasBin(root)?.file ?? null
@@ -2389,6 +2362,7 @@ async function runAndroid(options = {}) {
2389
2362
  const boot = () => Promise.resolve(ensureDeviceBooted({
2390
2363
  platform: PLATFORM,
2391
2364
  device,
2365
+ projectPath: root,
2392
2366
  out,
2393
2367
  logFile: emuLog
2394
2368
  })).catch((e) => ({
@@ -2404,6 +2378,7 @@ async function runAndroid(options = {}) {
2404
2378
  boot,
2405
2379
  createdSessionId: remoteDevice.createdSessionId,
2406
2380
  abandonCreatedSession: remoteDevice.abandonCreatedSession,
2381
+ webPreviewUrl: remoteDevice.webPreviewUrl,
2407
2382
  writeState,
2408
2383
  register: registerProject
2409
2384
  }) : boot()).then((result) => {
@@ -2457,7 +2432,8 @@ async function runAndroid(options = {}) {
2457
2432
  phase,
2458
2433
  out,
2459
2434
  estimates,
2460
- stats
2435
+ stats,
2436
+ step: progress.step
2461
2437
  }
2462
2438
  }, {
2463
2439
  deviceAbi,
@@ -2590,7 +2566,8 @@ async function runAndroid(options = {}) {
2590
2566
  now,
2591
2567
  out,
2592
2568
  emit,
2593
- recordRun
2569
+ recordRun,
2570
+ enterPhase: progress.step
2594
2571
  });
2595
2572
  } finally {
2596
2573
  releaseLease();
@@ -1,5 +1,5 @@
1
- import { t as getExecutor } from "./exec-B29XNuhP.mjs";
2
- import { s as androidToolCwd } from "./android-DWssqHQ8.mjs";
1
+ import { t as getExecutor } from "./exec-jIAlgilk.mjs";
2
+ import { s as androidToolCwd } from "./android-_y4Ohi5z.mjs";
3
3
  //#region src/collector/android.ts
4
4
  const LEVEL_BY_LETTER = {
5
5
  V: "debug",