stim 1.3.0 → 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 (65) hide show
  1. package/README.md +17 -3
  2. package/dist/{android-CqcW1hrP.mjs → android-BTR1KTMP.mjs} +206 -120
  3. package/dist/{android-BqVIJoG7.mjs → android-BkM4qTaN.mjs} +76 -77
  4. package/dist/{android-cas-BR5YJBYv.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-C3-R4XQr.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-Spz8ozgo.mjs → deps-Ck4wuTwa.mjs} +1 -1
  15. package/dist/{dev-client-BjisHDB5.mjs → dev-client-Cd0v7iZk.mjs} +12 -12
  16. package/dist/{device-BkiW-gkk.mjs → device-CKXLnRRb.mjs} +7 -7
  17. package/dist/device-CMDFzx7C.mjs +1136 -0
  18. package/dist/{device-lease-BJYybF-2.mjs → device-lease-DDJr85OK.mjs} +3 -3
  19. package/dist/{device-pool-CPbLGfrB.mjs → device-pool-DJvbIBGW.mjs} +4 -4
  20. package/dist/{device-remote-AkYCKS8U.mjs → device-remote-CMfHhoc8.mjs} +7 -6
  21. package/dist/{doctor-Bmv86UUW.mjs → doctor-B_mfURdt.mjs} +536 -114
  22. package/dist/{error-diagnostics-DHC31rXA.mjs → error-diagnostics-_VmkqRZo.mjs} +8 -7
  23. package/dist/{gc-GGu-8ULN.mjs → gc-0AyxmECv.mjs} +61 -27
  24. package/dist/{guide-92CPA21s.mjs → guide-CGxdmQ0K.mjs} +236 -67
  25. package/dist/{ios-BSXFeO5H.mjs → ios-CtmGLx3X.mjs} +2667 -2607
  26. package/dist/{ios-device-CWOM_S44.mjs → ios-device-DJOi9FR5.mjs} +1 -1
  27. package/dist/{logs-DPNF4C9s.mjs → logs-BF3qPA7w.mjs} +32 -7
  28. package/dist/{logs-query-R-Cqc6Qv.mjs → logs-query-Cdp5aaCH.mjs} +1 -1
  29. package/dist/metro-DFLmaVz8.mjs +99 -0
  30. package/dist/named-ports-BO7Zpjp0.mjs +137 -0
  31. package/dist/{ownership-8O073GIC.mjs → ownership-BvSpZVBk.mjs} +5 -5
  32. package/dist/{ownership-BpCKiRlQ.mjs → ownership-C08Yzrp7.mjs} +6 -6
  33. package/dist/ports-BiKqP92J.mjs +39 -0
  34. package/dist/ports-ClqG8GPt.mjs +90 -0
  35. package/dist/{project-fKmuIaMI.mjs → project-CBFCwnN5.mjs} +2 -2
  36. package/dist/{reclaim-iW9Cad7k.mjs → reclaim-DpDVo-AP.mjs} +20 -13
  37. package/dist/{reload-DazHDSFA.mjs → reload-ByTqg6ZI.mjs} +8 -8
  38. package/dist/rolldown-runtime-BsOwBuO_.mjs +30 -0
  39. package/dist/{server-bare-DEzPZLvf.mjs → server-bare-Buk-k8tI.mjs} +3 -6
  40. package/dist/{server-expo-DizT4OMk.mjs → server-expo-D4P7iTvf.mjs} +3 -3
  41. package/dist/server-expo-Dho5Raiw.mjs +2 -0
  42. package/dist/{settings-DEJf6_kJ.mjs → settings-DwfltIXQ.mjs} +2 -2
  43. package/dist/{slot-launch-cci0xvmC.mjs → slot-launch-DeKen9e5.mjs} +4 -4
  44. package/dist/{start-mObjQH2W.mjs → start-BpKUHcU_.mjs} +16 -14
  45. package/dist/state-CDbvmZdt.mjs +131 -0
  46. package/dist/{stats-BR-kwj0B.mjs → stats-C59QLKm2.mjs} +1 -1
  47. package/dist/{stats-CaaQr4yN.mjs → stats-k5qMjyxo.mjs} +4 -4
  48. package/dist/{status-BxQNqu9t.mjs → status-Y2ij3dm6.mjs} +13 -12
  49. package/dist/{stop-DimGXEKY.mjs → stop-_P2OQwmZ.mjs} +15 -14
  50. package/dist/{stop-CrHGcjrV.mjs → stop-vKmmxhea.mjs} +2 -2
  51. package/dist/supervisor-run.d.mts +3 -27
  52. package/dist/supervisor-run.mjs +7 -6
  53. package/dist/{teardown-ClFp1Aax.mjs → teardown-KsPpvldq.mjs} +248 -110
  54. package/dist/{workspace-process-lock-3NcQnZ0X.mjs → workspace-process-lock-UEIrvZMJ.mjs} +4 -4
  55. package/dist/workspace-state-Do1e2mOH.mjs +146 -0
  56. package/dist/{worktree-DxTgN1CJ.mjs → worktree-BvcAIMdJ.mjs} +132 -123
  57. package/dist/{worktree-Eq6TLH8N.mjs → worktree-DWNx4tUg.mjs} +32 -2
  58. package/dist/xcode-D9tVItl-.mjs +860 -0
  59. package/package.json +7 -7
  60. package/dist/doctor-fAMXC4vl.mjs +0 -530
  61. package/dist/metro-CORqXyTL.mjs +0 -184
  62. package/dist/ownership-claim-Bf2CAfw3.mjs +0 -570
  63. package/dist/server-expo-44em5Pmg.mjs +0 -2
  64. package/dist/state-B3XA2wp2.mjs +0 -268
  65. package/dist/xcode-D7sAp-Zy.mjs +0 -1858
package/README.md CHANGED
@@ -44,8 +44,9 @@ stim stop
44
44
 
45
45
  For `stim logs --errors`, a clean check requires exit code 0 and no matching
46
46
  errors in the captured logs. Exit code 0 alone means the query succeeded, even
47
- when it prints errors; an empty result does not prove launch or log capture
48
- succeeded.
47
+ when it prints errors. A workspace that has never produced a log timeline
48
+ refuses with `STIM_NO_PROJECT`; in a monorepo, the error names the nearest
49
+ registered descendant app with logs when one exists.
49
50
 
50
51
  Use `stim start --reset-cache` to recover from stale Metro transforms or file-map
51
52
  state. It restarts only this app's verified owned Metro, keeping its port and
@@ -128,7 +129,7 @@ eligible `.env` and local configuration files. Existing entries are preserved;
128
129
  existing ignored directories such as `node_modules` are skipped whole.
129
130
 
130
131
  `stim worktree warm --refresh` brings the source checkout up to date first: it
131
- fetches, fast-forwards whatever branch is checked out there, and installs what
132
+ checks the upstream, fetches changes when needed, fast-forwards whatever branch is checked out there, and installs what
132
133
  the new commits moved before copying. It refuses a source checkout it cannot
133
134
  move (uncommitted tracked changes, a rebase or merge in progress, a detached
134
135
  HEAD, a diverged branch) and never switches branches. One lock per repository
@@ -197,3 +198,16 @@ stim stop --slot tablet
197
198
  `--slot` preserves the shared server and other devices. `stim reload ios` or
198
199
  `stim reload android` can reload multiple connected devices. Read
199
200
  `stim guide lifecycle options` for launch verification and recycling behavior.
201
+
202
+ ### Named server ports
203
+
204
+ Use `stim ports get web` to reserve a workspace port for a server Stim does not
205
+ manage, then pass that number to the server, for example
206
+ `pnpm exec vite --port "$(stim ports get web)" --strictPort`.
207
+ `stim ports` lists named allocations and managed Metro. `stim ports stop web`
208
+ stops the TCP listener and releases its port; `--dry-run` previews the action.
209
+ `stim ports release web` releases without stopping. Omitting the label selects
210
+ all named ports. These commands leave Metro alone, and `stim stop` leaves
211
+ named ports alone. Worktree removal and `gc --delete` stop and release named
212
+ ports for removed workspaces. Read `stim guide ports` for the reserved band,
213
+ listener ownership, and allocation limits.
@@ -1,30 +1,31 @@
1
1
  import { t as getExecutor } from "./exec-CyylIdq9.mjs";
2
- import { C as withConfigLock, I as deviceSlotKey, L as deviceSlotPlatforms, S as upsertProject, V as validateDeviceSlot, b as setDevice, f as getProject, g as loadConfig, l as getConcurrencyLimits, y as saveConfig } from "./config-CcefMY2Q.mjs";
2
+ import { C as withConfigLock, E as ownership_claim_exports, F as deviceSlotPlatforms, P as deviceSlotKey, S as upsertProject, b as setDevice, f as getProject, g as loadConfig, l as getConcurrencyLimits, y as saveConfig, z as validateDeviceSlot } from "./config-6vdGngKa.mjs";
3
3
  import { r as createNdjsonWriter } from "./ndjson-BZwI1NBv.mjs";
4
- import { D as workspaceLogsDir, T as workspaceDir, _ as writeWorkspaceState, g as writeWorkspaceLaunch, n as MODE_EXPO, p as readWorkspaceState, t as MODE_BARE, v as emulatorLogFile } from "./state-B3XA2wp2.mjs";
5
- import { C as removeTemporaryEntry, S as makeTemporaryDirectory, g as repoRoot, s as gitCommonDir } from "./worktree-Eq6TLH8N.mjs";
6
- import { a as findProjectRoot, c as projectShortcut, i as detectIsExpo, n as detectAndroidPackage, r as detectBundleId, t as appProjectProblem } from "./project-fKmuIaMI.mjs";
4
+ import { _ as workspaceLogsDir, h as workspaceDir, o as writeWorkspaceState, r as readWorkspaceState, s as emulatorLogFile } from "./workspace-state-Do1e2mOH.mjs";
5
+ import { C as makeTemporaryDirectory, _ as repoRoot, s as gitCommonDir, w as removeTemporaryEntry } from "./worktree-DWNx4tUg.mjs";
6
+ import { a as findProjectRoot, c as projectShortcut, i as detectIsExpo, n as detectAndroidPackage, r as detectBundleId, t as appProjectProblem } from "./project-CBFCwnN5.mjs";
7
+ import { n as MODE_EXPO, p as writeWorkspaceLaunch, t as MODE_BARE } from "./state-CDbvmZdt.mjs";
7
8
  import { r as waitForChild, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
8
- import { i as resolveProjectMetro, r as pidExists } from "./metro-CORqXyTL.mjs";
9
- import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-8O073GIC.mjs";
10
- 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-8wHMEde0.mjs";
11
- import { r as claimFailure } from "./ownership-claim-Bf2CAfw3.mjs";
12
- import { $ as providerDownloadPath, C as memoizeEmulatorProbe, G as buildCacheKey, J as filesystemBuildCapability, K as describeFingerprintMiss, L as waitForBoot, M as resetAdoptedAvd, N as resolveOwnedAvdSerial, P as resolvePhysicalDevice, Q as prepareProviderDownloadDir, R as WAIT_CEILING_MS, U as takeoverLine, V as releaseBuildLock, W as waitForBuild, X as fingerprintDiffSuffix, Y as fingerprintDiffRecord, Z as fingerprintProject, _ as findBuildTool, a as androidSystemImageAbi, at as untrackedMissLine, ct as captureAssetManifest, et as providerUploadOutcome, g as extractEmulatorFailure, h as emulatorFailureRemedy, i as androidPoolNoCandidatesRefusal, it as storedAssetManifest, j as probeEmulatorSerial, k as physicalDeviceModel, lt as compareAssetManifests, m as emulatorDiskSpaceRemedy, n as androidHome, nt as resolveBuild, ot as untrackedNativeFiles, r as androidPoolCandidates, rt as storeBuild, st as assetDiffReason, t as androidDeviceAbi, tt as refingerprintAfterMutation, ut as readAssetManifest, x as listInstalledSystemImages, y as listAdbDevices, z as acquireBuildLock } from "./android-BqVIJoG7.mjs";
13
- 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-DEJf6_kJ.mjs";
14
- import { t as cleanLine } from "./server-expo-DizT4OMk.mjs";
15
- import { C as uploadRemote, S as resolveRemote, _ as isEasAuthFailureText, c as parkedMaxSetting, d as RESOLVE_TIMEOUT_MS, f as UPLOAD_TIMEOUT_MS, g as exitAfterFlush, h as easAuthNote, m as checkEasAuth, p as cacheLevel, v as loadProjectProvider, x as resolveEasCliBin } from "./teardown-ClFp1Aax.mjs";
16
- 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-BjisHDB5.mjs";
17
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-3NcQnZ0X.mjs";
18
- 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-DIp9riNx.mjs";
19
- 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-CPbLGfrB.mjs";
20
- import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-C3-R4XQr.mjs";
21
- import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-BR-kwj0B.mjs";
22
- import { C as unknownAndroidSystemImageRefusal, _ as AvdRecoveryError, b as ensureBooted, f as startBuildHeartbeat, p as tailLines, r as HEARTBEAT_INTERVAL_MS, v as checkDeviceCapacity, x as ensureOwnedDevice } from "./xcode-D7sAp-Zy.mjs";
23
- import { a as ensureMetroReachable, b as ownedSessionName, l as resolveRemoteContext, n as binOnPath, o as ensureRemoteBootOwned, s as remoteAndroidDeps } from "./device-remote-AkYCKS8U.mjs";
24
- 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-BR5YJBYv.mjs";
25
- import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-cci0xvmC.mjs";
9
+ import { i as resolveProjectMetro, r as pidExists } from "./metro-DFLmaVz8.mjs";
10
+ import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-BvSpZVBk.mjs";
11
+ 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-BuIb_ZOI.mjs";
12
+ import { $ as providerDownloadPath, C as memoizeEmulatorProbe, G as buildCacheKey, J as filesystemBuildCapability, K as describeFingerprintMiss, L as waitForBoot, M as resetAdoptedAvd, N as resolveOwnedAvdSerial, P as resolvePhysicalDevice, Q as prepareProviderDownloadDir, R as WAIT_CEILING_MS, U as takeoverLine, V as releaseBuildLock, W as waitForBuild, X as fingerprintDiffSuffix, Y as fingerprintDiffRecord, Z as fingerprintProject, _ as findBuildTool, a as androidSystemImageAbi, at as untrackedMissLine, ct as captureAssetManifest, et as providerUploadOutcome, g as extractEmulatorFailure, h as emulatorFailureRemedy, i as androidPoolNoCandidatesRefusal, it as storedAssetManifest, j as probeEmulatorSerial, k as physicalDeviceModel, lt as compareAssetManifests, m as emulatorDiskSpaceRemedy, n as androidHome, nt as resolveBuild, ot as untrackedNativeFiles, r as androidPoolCandidates, rt as storeBuild, st as assetDiffReason, t as androidDeviceAbi, tt as refingerprintAfterMutation, ut as readAssetManifest, x as listInstalledSystemImages, y as listAdbDevices, z as acquireBuildLock } from "./android-BkM4qTaN.mjs";
13
+ 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-DwfltIXQ.mjs";
14
+ import { t as cleanLine } from "./server-expo-D4P7iTvf.mjs";
15
+ 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-KsPpvldq.mjs";
16
+ 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-Cd0v7iZk.mjs";
17
+ import { t as withWorkspaceProcessLock } from "./workspace-process-lock-UEIrvZMJ.mjs";
18
+ 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--PAocWvq.mjs";
19
+ 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-DJvbIBGW.mjs";
20
+ import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-D6sDzOe-.mjs";
21
+ import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-C59QLKm2.mjs";
22
+ import { i as ensureOwnedDevice, l as AvdBootError, o as unknownAndroidSystemImageRefusal, r as ensureBooted, t as checkDeviceCapacity, u as AvdRecoveryError } from "./device-CMDFzx7C.mjs";
23
+ import { a as ensureMetroReachable, b as ownedSessionName, l as resolveRemoteContext, n as binOnPath, o as ensureRemoteBootOwned, s as remoteAndroidDeps } from "./device-remote-CMfHhoc8.mjs";
24
+ 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-BjLRZyks.mjs";
25
+ import { d as startBuildHeartbeat, f as tailLines, n as HEARTBEAT_INTERVAL_MS } from "./xcode-D9tVItl-.mjs";
26
+ import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-DeKen9e5.mjs";
26
27
  import { t as spawnEntry } from "./spawn-entry-nCE6m770.mjs";
27
- import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-DHC31rXA.mjs";
28
+ import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-_VmkqRZo.mjs";
28
29
  import { existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync } from "node:fs";
29
30
  import { InvalidArgumentError } from "commander";
30
31
  import { basename, dirname, join, relative } from "node:path";
@@ -1012,12 +1013,7 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1012
1013
  deviceId: booted.serial
1013
1014
  });
1014
1015
  if (booted.failed) {
1015
- const diag = noDeviceDiagnostic({
1016
- reason: booted.reason ?? "The emulator did not boot.",
1017
- logFile: emuLog,
1018
- remedy: `Run \`stim status\` to see what Stim thinks it owns; re-running \`${runCommand}\` creates a fresh owned AVD.`,
1019
- localEmulator: !physical
1020
- });
1016
+ const diag = diagnoseBootFailure(booted, emuLog, runCommand, physical);
1021
1017
  return fail(NO_DEVICE, diag.message, diag.remedy, {
1022
1018
  lines: diag.lines,
1023
1019
  logPath: diag.logPath ? displayPath(root, diag.logPath) : null
@@ -1275,9 +1271,25 @@ async function finishAndroidRun({ lease, releaseLease, root, slot, json, metroCh
1275
1271
  facts
1276
1272
  };
1277
1273
  }
1274
+ function diagnoseBootFailure(booted, logFile, runCommand, physical) {
1275
+ return noDeviceDiagnostic({
1276
+ reason: booted.reason ?? "The emulator did not boot.",
1277
+ logFile,
1278
+ remedy: booted.remedy ?? `Run \`stim status\` to see what Stim thinks it owns; re-running \`${runCommand}\` creates a fresh owned AVD.`,
1279
+ localEmulator: !physical
1280
+ });
1281
+ }
1278
1282
  //#endregion
1279
- //#region src/commands/android.ts
1280
- const FALLBACK_LINES = 5;
1283
+ //#region src/commands/android/plan.ts
1284
+ function fail(code, message, remedy, { lines = [] } = {}) {
1285
+ return {
1286
+ ok: false,
1287
+ code,
1288
+ message,
1289
+ remedy,
1290
+ lines
1291
+ };
1292
+ }
1281
1293
  function androidCompilerCache({ root, optimizations, settingsContext }) {
1282
1294
  const { cas, optimizations: resolved } = resolveAndroidCompilerCache({
1283
1295
  optimizations,
@@ -1302,6 +1314,104 @@ function androidCompilerCache({ root, optimizations, settingsContext }) {
1302
1314
  })}`
1303
1315
  };
1304
1316
  }
1317
+ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, variant: variantFlag, systemImage: systemImageFlag, device: deviceFlag, wait: waitFlag, waitConflict, remote: commandRemoteBackend, buildCache: requestedBuildCache }, { warn, resolveCompilerCache = androidCompilerCache, resolveCacheProvider = resolveCacheProviderConfig, validateAvdConfig = androidAvdConfigSettingError, readFlavors = readProductFlavors, detectExpo = detectIsExpo, listSystemImages = listInstalledSystemImages, parkedLimit = parkedMaxSetting }) {
1318
+ const root = settingsContext.projectPath;
1319
+ const [shapeError, ...moreShapeErrors] = [parkedLimit("android").error, ...settingShapeErrors(settings)].filter((error) => Boolean(error));
1320
+ if (shapeError) return fail("STIM_BAD_ARG", shapeError, SETTING_SHAPE_REMEDY, { lines: moreShapeErrors });
1321
+ for (const key of unknownSettingKeys(settings)) warn("setting", `Warning: setting "${key}" is not read by Stim and will be ignored.`);
1322
+ let optimizations;
1323
+ try {
1324
+ optimizations = resolveOptimizations(settings);
1325
+ } catch (error) {
1326
+ return fail("STIM_BAD_ARG", `Could not configure Android build: ${error.message}`, SETTING_SHAPE_REMEDY);
1327
+ }
1328
+ const compilerCache = resolveCompilerCache({
1329
+ root,
1330
+ optimizations,
1331
+ settingsContext
1332
+ });
1333
+ const cas = compilerCache.cas;
1334
+ optimizations = compilerCache.optimizations;
1335
+ if (compilerCache.warning) warn("cache", compilerCache.warning);
1336
+ const buildProfile = optimizationBuildProfile("android", optimizations);
1337
+ const cacheProviderConfig = resolveCacheProvider(settingsContext);
1338
+ const cacheProviderError = cacheProviderSettingError(settings);
1339
+ if (cacheProviderError) warn("cache", `${cacheProviderError} Using the local cache.`);
1340
+ const dataPartitionSizeError = androidDataPartitionSizeGbSettingError(settings);
1341
+ if (dataPartitionSizeError) return fail("STIM_BAD_ARG", dataPartitionSizeError, "Set android.dataPartitionSizeGb to a whole number of GiB from 6 through 16384.");
1342
+ const avdConfigError = validateAvdConfig(settings, root);
1343
+ if (avdConfigError) return fail("STIM_BAD_ARG", avdConfigError, "Use only documented android.avdConfig keys, or an android.avdConfigFile fragment contained by the app directory.");
1344
+ const remoteSettingError = remoteDeviceSettingError(settings);
1345
+ if (remoteSettingError) return fail("STIM_BAD_ARG", remoteSettingError, `Set ios.remote and android.remote to one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}.`);
1346
+ const systemImage = resolveSystemImage(systemImageFlag, settings);
1347
+ const variant = easProfile !== void 0 ? "debug" : resolveVariant(variantFlag, settings);
1348
+ const flavorRefusal = productFlavorRefusal({
1349
+ flavors: readFlavors(root),
1350
+ variant
1351
+ });
1352
+ if (flavorRefusal) return fail(flavorRefusal.code, flavorRefusal.reason, flavorRefusal.remedy);
1353
+ const release = isReleaseVariant(variant);
1354
+ const cachePolicy = artifactCachePolicy(optimizations, requestedBuildCache, release);
1355
+ const isExpo = detectExpo(root);
1356
+ const physical = isPhysicalDeviceRequest(deviceFlag);
1357
+ if (physical && deviceFlag === "") return fail("STIM_BAD_ARG", "--device was given an empty serial.", "Pass `--device` on its own to take the first connected device this workspace can lease, or `--device <serial>` to name one.");
1358
+ if (physical && commandRemoteBackend) return fail("STIM_BAD_ARG", "--device installs on a device connected to this machine, and --remote installs on a remote one.", "Pass only one of --device and --remote.");
1359
+ const noWait = waitFlag === false;
1360
+ const waitFlagged = waitFlag !== void 0;
1361
+ if (waitConflict) return fail("STIM_BAD_ARG", "--wait and --no-wait ask for opposite things.", "Pass `--wait <seconds>` to wait for the lease, or `--no-wait` to install without one.");
1362
+ if (waitFlagged && !physical) return fail("STIM_BAD_ARG", "--wait and --no-wait only apply to a `--device` run.", "This workspace owns its emulator, so nothing contends for it. Drop the flag, or pass `--device`.");
1363
+ const waitParsed = parseDeviceWait(noWait ? void 0 : waitFlag);
1364
+ if ("error" in waitParsed) return fail("STIM_BAD_ARG", waitParsed.error, "Pass a whole number of seconds, e.g. --wait 90. `--wait 0` refuses a leased device at once.");
1365
+ const waitSeconds = waitParsed.seconds;
1366
+ const remoteBackend = physical ? null : commandRemoteBackend ?? remoteAndroidSetting(settings);
1367
+ const imageRefusal = systemImageRefusal({
1368
+ slot,
1369
+ flag: systemImageFlag,
1370
+ resolved: systemImage,
1371
+ physical,
1372
+ remoteBackend,
1373
+ listImages: listSystemImages
1374
+ });
1375
+ if (imageRefusal) return fail(imageRefusal.code, imageRefusal.message, imageRefusal.remedy);
1376
+ const target = physical ? {
1377
+ kind: "physical",
1378
+ serial: typeof deviceFlag === "string" ? deviceFlag : null,
1379
+ lease: {
1380
+ waitSeconds,
1381
+ noWait
1382
+ }
1383
+ } : remoteBackend ? {
1384
+ kind: "remote",
1385
+ backend: remoteBackend,
1386
+ systemImage
1387
+ } : {
1388
+ kind: "emulator",
1389
+ systemImage
1390
+ };
1391
+ return {
1392
+ ok: true,
1393
+ plan: {
1394
+ build: {
1395
+ variant,
1396
+ release,
1397
+ profile: buildProfile,
1398
+ cas,
1399
+ cache: cachePolicy,
1400
+ compilerCache: optimizations.android.compilerCache,
1401
+ gradleBuildCache: optimizations.android.gradleBuildCache,
1402
+ pch: optimizations.android.pch,
1403
+ targetAbiOnly: optimizations.android.targetAbiOnly
1404
+ },
1405
+ target,
1406
+ isExpo,
1407
+ metroWarmup: optimizations.metroWarmup,
1408
+ cacheProviderConfig
1409
+ }
1410
+ };
1411
+ }
1412
+ //#endregion
1413
+ //#region src/commands/android.ts
1414
+ const FALLBACK_LINES = 5;
1305
1415
  function androidCommand(program) {
1306
1416
  registerAndroid(program);
1307
1417
  }
@@ -1448,6 +1558,25 @@ function androidSlotOptions(options) {
1448
1558
  writeLaunch: ((projectRoot, platform, record) => base.writeLaunch(projectRoot, platform, record, slot))
1449
1559
  };
1450
1560
  }
1561
+ function avdSetupFailure(error, root, logFile) {
1562
+ const refusal = (0, ownership_claim_exports.claimFailure)(error, "stim android");
1563
+ if (refusal) return refusal;
1564
+ const diag = noDeviceDiagnostic({
1565
+ reason: `Could not ensure an owned Android emulator: ${error?.message || error}`,
1566
+ logFile,
1567
+ localEmulator: !(error instanceof AvdRecoveryError),
1568
+ remedy: error instanceof AvdBootError ? error.remedy : "Check that JAVA_HOME and ANDROID_HOME are set correctly, and that an arm64 system image is installed (`sdkmanager \"system-images;android-36;google_apis;arm64-v8a\"`)."
1569
+ });
1570
+ return {
1571
+ code: NO_DEVICE,
1572
+ message: diag.message,
1573
+ remedy: diag.remedy,
1574
+ extra: {
1575
+ lines: diag.lines,
1576
+ logPath: diag.logPath ? displayPath(root, diag.logPath) : null
1577
+ }
1578
+ };
1579
+ }
1451
1580
  async function runAndroid(options = {}) {
1452
1581
  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);
1453
1582
  const slot = validateDeviceSlot(options.slot);
@@ -1596,64 +1725,30 @@ async function runAndroid(options = {}) {
1596
1725
  platform: PLATFORM
1597
1726
  });
1598
1727
  const settings = resolveSettingsFor(settingsContext);
1599
- const [shapeError, ...moreShapeErrors] = [parkedMaxSetting("android").error, ...settingShapeErrors(settings)].filter((error) => Boolean(error));
1600
- if (shapeError) return fail("STIM_BAD_ARG", shapeError, SETTING_SHAPE_REMEDY, { lines: moreShapeErrors });
1601
- for (const key of unknownSettingKeys(settings)) out(phaseLine("setting", chalk.yellow(`Warning: setting "${key}" is not read by Stim and will be ignored.`)));
1602
- let optimizations;
1603
- try {
1604
- optimizations = resolveOptimizations(settings);
1605
- } catch (error) {
1606
- return fail("STIM_BAD_ARG", `Could not configure Android build: ${error.message}`, SETTING_SHAPE_REMEDY);
1607
- }
1608
- const compilerCache = androidCompilerCache({
1609
- root,
1610
- optimizations,
1611
- settingsContext
1612
- });
1613
- const cas = compilerCache.cas;
1614
- optimizations = compilerCache.optimizations;
1615
- if (compilerCache.warning) out(phaseLine("cache", chalk.yellow(compilerCache.warning)));
1616
- const buildProfile = optimizationBuildProfile("android", optimizations);
1617
- const cacheProviderConfig = resolveCacheProvider(settingsContext);
1618
- const cacheProviderError = cacheProviderSettingError(settings);
1619
- if (cacheProviderError) out(phaseLine("cache", chalk.yellow(`${cacheProviderError} Using the local cache.`)));
1620
- const dataPartitionSizeError = androidDataPartitionSizeGbSettingError(settings);
1621
- if (dataPartitionSizeError) return fail("STIM_BAD_ARG", dataPartitionSizeError, "Set android.dataPartitionSizeGb to a whole number of GiB from 6 through 16384.");
1622
- const avdConfigError = androidAvdConfigSettingError(settings, settingsRoot);
1623
- if (avdConfigError) return fail("STIM_BAD_ARG", avdConfigError, "Use only documented android.avdConfig keys, or an android.avdConfigFile fragment contained by the app directory.");
1624
- const remoteSettingError = remoteDeviceSettingError(settings);
1625
- if (remoteSettingError) return fail("STIM_BAD_ARG", remoteSettingError, `Set ios.remote and android.remote to one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}.`);
1626
- const systemImage = resolveSystemImage(systemImageFlag, settings);
1627
- const variant = easProfile !== void 0 ? "debug" : resolveVariant(variantFlag, settings);
1628
- const flavorRefusal = productFlavorRefusal({
1629
- flavors: readProductFlavors(root),
1630
- variant
1631
- });
1632
- if (flavorRefusal) return fail(flavorRefusal.code, flavorRefusal.reason, flavorRefusal.remedy);
1633
- const release = isReleaseVariant(variant);
1634
- const cachePolicy = artifactCachePolicy(optimizations, requestedBuildCache, release);
1635
- const useBuildCache = cachePolicy.read;
1636
- const isExpo = detectIsExpo(root);
1637
- const physical = isPhysicalDeviceRequest(deviceFlag);
1638
- if (physical && deviceFlag === "") return fail("STIM_BAD_ARG", "--device was given an empty serial.", "Pass `--device` on its own to take the first connected device this workspace can lease, or `--device <serial>` to name one.");
1639
- if (physical && commandRemoteBackend) return fail("STIM_BAD_ARG", "--device installs on a device connected to this machine, and --remote installs on a remote one.", "Pass only one of --device and --remote.");
1640
- const noWait = waitFlag === false;
1641
- const waitFlagged = waitFlag !== void 0;
1642
- if (waitConflict) return fail("STIM_BAD_ARG", "--wait and --no-wait ask for opposite things.", "Pass `--wait <seconds>` to wait for the lease, or `--no-wait` to install without one.");
1643
- if (waitFlagged && !physical) return fail("STIM_BAD_ARG", "--wait and --no-wait only apply to a `--device` run.", "This workspace owns its emulator, so nothing contends for it. Drop the flag, or pass `--device`.");
1644
- const waitParsed = parseDeviceWait(noWait ? void 0 : waitFlag);
1645
- if ("error" in waitParsed) return fail("STIM_BAD_ARG", waitParsed.error, "Pass a whole number of seconds, e.g. --wait 90. `--wait 0` refuses a leased device at once.");
1646
- const waitSeconds = waitParsed.seconds;
1647
- const remoteBackend = physical ? null : commandRemoteBackend ?? remoteAndroidSetting(settings);
1648
- const imageRefusal = systemImageRefusal({
1728
+ const planned = resolveAndroidRunPlan({
1729
+ settings,
1730
+ settingsContext,
1649
1731
  slot,
1650
- flag: systemImageFlag,
1651
- resolved: systemImage,
1652
- physical,
1653
- remoteBackend,
1654
- listImages: listSystemImages
1732
+ easProfile,
1733
+ variant: variantFlag,
1734
+ systemImage: systemImageFlag,
1735
+ device: deviceFlag,
1736
+ wait: waitFlag,
1737
+ waitConflict,
1738
+ remote: commandRemoteBackend,
1739
+ buildCache: requestedBuildCache
1740
+ }, {
1741
+ resolveCacheProvider,
1742
+ listSystemImages,
1743
+ warn: (label, message) => out(phaseLine(label, chalk.yellow(message)))
1655
1744
  });
1656
- if (imageRefusal) return fail(imageRefusal.code, imageRefusal.message, imageRefusal.remedy);
1745
+ if (!planned.ok) return fail(planned.code, planned.message, planned.remedy, { lines: planned.lines });
1746
+ const { plan } = planned;
1747
+ const { build: buildPlan, target, isExpo, cacheProviderConfig } = plan;
1748
+ const { variant, release, profile: buildProfile, cas, cache: cachePolicy } = buildPlan;
1749
+ const useBuildCache = cachePolicy.read;
1750
+ const physical = target.kind === "physical";
1751
+ const remoteBackend = target.kind === "remote" ? target.backend : null;
1657
1752
  const easBuild = await resolveEasBuild({
1658
1753
  root,
1659
1754
  platform: PLATFORM,
@@ -1665,7 +1760,6 @@ async function runAndroid(options = {}) {
1665
1760
  buildCache: requestedBuildCache
1666
1761
  });
1667
1762
  if (isEasBuildFailure(easBuild)) return fail(easBuild.code, easBuild.message, easBuild.remedy);
1668
- const requestedSerial = typeof deviceFlag === "string" ? deviceFlag : null;
1669
1763
  let androidPackage = detectAndroidPackage(root);
1670
1764
  record.bundleId = androidPackage;
1671
1765
  const registerProject = () => upsertProject(root, {
@@ -1741,15 +1835,15 @@ async function runAndroid(options = {}) {
1741
1835
  let device;
1742
1836
  let bootDuration = "";
1743
1837
  let bootPromise;
1744
- if (physical && !requestedSerial) {
1838
+ if (target.kind === "physical" && !target.serial) {
1745
1839
  const pooled = await pooledAndroidDevice({
1746
1840
  root,
1747
1841
  selectPool,
1748
1842
  listDevices,
1749
1843
  isEmulatorDevice,
1750
1844
  deviceModel,
1751
- waitSeconds,
1752
- noWait,
1845
+ waitSeconds: target.lease.waitSeconds,
1846
+ noWait: target.lease.noWait,
1753
1847
  now,
1754
1848
  warn: (line) => out(phaseLine("lease", chalk.yellow(line)))
1755
1849
  });
@@ -1759,8 +1853,8 @@ async function runAndroid(options = {}) {
1759
1853
  ok: true,
1760
1854
  serial: pooled.device.serial
1761
1855
  });
1762
- } else if (physical) {
1763
- const resolved = resolvePhysicalDevice(requestedSerial, listDevices(), isEmulatorDevice);
1856
+ } else if (target.kind === "physical") {
1857
+ const resolved = resolvePhysicalDevice(target.serial, listDevices(), isEmulatorDevice);
1764
1858
  if (!resolved.serial) return fail(NO_DEVICE, resolved.error, resolved.remedy);
1765
1859
  device = {
1766
1860
  serial: resolved.serial,
@@ -1786,22 +1880,14 @@ async function runAndroid(options = {}) {
1786
1880
  projectPath: root,
1787
1881
  settingsRoot,
1788
1882
  settings,
1789
- flags: { systemImage },
1883
+ flags: { systemImage: target.systemImage },
1790
1884
  note: out,
1791
1885
  out,
1792
1886
  logFile: emuLog
1793
1887
  });
1794
1888
  } catch (err) {
1795
- const diag = noDeviceDiagnostic({
1796
- reason: `Could not ensure an owned Android emulator: ${err?.message || err}`,
1797
- logFile: emuLog,
1798
- localEmulator: !(err instanceof AvdRecoveryError),
1799
- remedy: err instanceof AvdRecoveryError ? err.remedy : "Check that JAVA_HOME and ANDROID_HOME are set correctly, and that an arm64 system image is installed (`sdkmanager \"system-images;android-36;google_apis;arm64-v8a\"`)."
1800
- });
1801
- return fail(NO_DEVICE, diag.message, diag.remedy, {
1802
- lines: diag.lines,
1803
- logPath: diag.logPath ? displayPath(root, diag.logPath) : null
1804
- });
1889
+ const failure = avdSetupFailure(err, root, emuLog);
1890
+ return fail(failure.code, failure.message, failure.remedy, failure.extra);
1805
1891
  }
1806
1892
  const prepareMs = prepare();
1807
1893
  if (device.created || prepareMs >= 2e3) phase("device", `${device.avdName || device.deviceName || label} ${device.created ? "created" : "prepared"} (${formatDuration(prepareMs)})`);
@@ -1858,7 +1944,7 @@ async function runAndroid(options = {}) {
1858
1944
  deviceAbi,
1859
1945
  compiler: cas?.id,
1860
1946
  buildProfile,
1861
- targetAbiOnly: optimizations.android.targetAbiOnly
1947
+ targetAbiOnly: buildPlan.targetAbiOnly
1862
1948
  });
1863
1949
  let hash = "";
1864
1950
  let providerUpload = null;
@@ -1960,7 +2046,7 @@ async function runAndroid(options = {}) {
1960
2046
  return true;
1961
2047
  }
1962
2048
  const runFromFingerprint = async () => {
1963
- if (metroCheck && metroPort !== null && optimizations.metroWarmup) prewarmMetro({
2049
+ if (metroCheck && metroPort !== null && plan.metroWarmup) prewarmMetro({
1964
2050
  port: metroPort,
1965
2051
  platform: "android",
1966
2052
  isExpo,
@@ -2036,7 +2122,7 @@ async function runAndroid(options = {}) {
2036
2122
  logFile: buildLog
2037
2123
  });
2038
2124
  } catch (err) {
2039
- const refusal = claimFailure(err, "stim android");
2125
+ const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
2040
2126
  if (refusal) {
2041
2127
  phaseFailure = fail(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
2042
2128
  return false;
@@ -2067,7 +2153,7 @@ async function runAndroid(options = {}) {
2067
2153
  ceilingMs
2068
2154
  });
2069
2155
  } catch (err) {
2070
- const refusal = claimFailure(err, "stim android");
2156
+ const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
2071
2157
  if (refusal) {
2072
2158
  phaseFailure = fail(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
2073
2159
  return false;
@@ -2149,7 +2235,7 @@ async function runAndroid(options = {}) {
2149
2235
  out
2150
2236
  });
2151
2237
  } catch (err) {
2152
- const refusal = claimFailure(err, "stim android");
2238
+ const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
2153
2239
  if (refusal) {
2154
2240
  phaseFailure = fail(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
2155
2241
  return false;
@@ -2206,19 +2292,19 @@ async function runAndroid(options = {}) {
2206
2292
  abi: buildAbi
2207
2293
  }, {
2208
2294
  estimateMs: estimates().coldBuildMs,
2209
- ccache: optimizations.android.compilerCache === "ccache" ? ccacheFor({
2295
+ ccache: buildPlan.compilerCache === "ccache" ? ccacheFor({
2210
2296
  root,
2211
2297
  onNote: out
2212
2298
  }) : null,
2213
2299
  cas,
2214
- buildCache: optimizations.android.gradleBuildCache,
2215
- pch: optimizations.android.pch,
2216
- compilerCacheDisabled: optimizations.android.compilerCache === "none"
2300
+ buildCache: buildPlan.gradleBuildCache,
2301
+ pch: buildPlan.pch,
2302
+ compilerCacheDisabled: buildPlan.compilerCache === "none"
2217
2303
  });
2218
2304
  ccacheActivity = built.ccache ?? CCACHE_UNAVAILABLE;
2219
2305
  phase("cache", `compilation cache ${ccacheActivityLine(ccacheActivity)}`);
2220
- if (built.failed) {
2221
- const diagnostics = built.diagnostics || [];
2306
+ if (!built.ok) {
2307
+ const diagnostics = built.diagnostics;
2222
2308
  for (const diag of diagnostics) writer.write({
2223
2309
  src: "build",
2224
2310
  level: "error",
@@ -2227,7 +2313,7 @@ async function runAndroid(options = {}) {
2227
2313
  });
2228
2314
  phase("build", chalk.red(`FAILED after ${formatDuration(built.durationMs)}`));
2229
2315
  const extracted = diagnostics.map(formatDiagnostic);
2230
- if ((built.truncated ?? 0) > 0) extracted.push(`... and ${built.truncated} more diagnostic(s) in the log`);
2316
+ if (built.truncated > 0) extracted.push(`... and ${built.truncated} more diagnostic(s) in the log`);
2231
2317
  phaseFailure = fail(built.code, built.reason, diagnostics.find((d) => d.remedy)?.remedy || built.remedy || null, {
2232
2318
  lastBuildStatus: true,
2233
2319
  diagnostics: extracted,
@@ -2236,8 +2322,8 @@ async function runAndroid(options = {}) {
2236
2322
  });
2237
2323
  return false;
2238
2324
  }
2239
- apkPath = built.apkPath ?? null;
2240
- stats.setBuildMs(built.durationMs ?? 0);
2325
+ apkPath = built.apkPath;
2326
+ stats.setBuildMs(built.durationMs);
2241
2327
  phase("build", `ok (${formatDuration(built.durationMs)})`);
2242
2328
  if (built.apkNote) phase("build", chalk.yellow(built.apkNote));
2243
2329
  const beforeBuildHash = storeHash;
@@ -2329,8 +2415,8 @@ async function runAndroid(options = {}) {
2329
2415
  id: device.serial,
2330
2416
  deviceName: device.deviceName ?? null,
2331
2417
  idLabel: "serial",
2332
- waitSeconds,
2333
- noWait,
2418
+ waitSeconds: target.lease.waitSeconds,
2419
+ noWait: target.lease.noWait,
2334
2420
  installBoundMs: ADB_INSTALL_TIMEOUT_MS,
2335
2421
  appId: androidPackage,
2336
2422
  holderAppId: (holder) => getProject(holder)?.androidPackage ?? null,