stim 1.3.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/README.md +13 -0
  2. package/dist/{android-CT-PkXUh.mjs → android-BTR1KTMP.mjs} +197 -114
  3. package/dist/{android-hMj83VpN.mjs → android-BkM4qTaN.mjs} +65 -72
  4. package/dist/{android-cas-DXorPCi_.mjs → android-cas-BjLRZyks.mjs} +15 -12
  5. package/dist/{app-install-DIp9riNx.mjs → app-install--PAocWvq.mjs} +2 -2
  6. package/dist/{build-slots-C4AouyNz.mjs → build-slots-D6sDzOe-.mjs} +7 -8
  7. package/dist/{cache-manifest-CXR-Y8pH.mjs → cache-manifest-D02TpYjA.mjs} +1 -1
  8. package/dist/cache-manifest.mjs +1 -1
  9. package/dist/cli.mjs +14 -13
  10. package/dist/collector-run.mjs +4 -4
  11. package/dist/{command-output-8wHMEde0.mjs → command-output-BuIb_ZOI.mjs} +1 -4
  12. package/dist/{config-CcefMY2Q.mjs → config-6vdGngKa.mjs} +51 -51
  13. package/dist/dependency-state-BMirHEPD.mjs +76 -0
  14. package/dist/{deps-0Yt0Inly.mjs → deps-Ck4wuTwa.mjs} +1 -1
  15. package/dist/{dev-client-VfSZNLT4.mjs → dev-client-Cd0v7iZk.mjs} +12 -12
  16. package/dist/{device-Bbv45Z9Z.mjs → device-CKXLnRRb.mjs} +7 -7
  17. package/dist/{xcode-xnxyvJkD.mjs → device-CMDFzx7C.mjs} +208 -967
  18. package/dist/{device-lease-CJwK7OZr.mjs → device-lease-DDJr85OK.mjs} +3 -3
  19. package/dist/{device-pool-CcBNTvM7.mjs → device-pool-DJvbIBGW.mjs} +4 -4
  20. package/dist/{device-remote-DAA0Zm6Z.mjs → device-remote-CMfHhoc8.mjs} +7 -6
  21. package/dist/{doctor-B4EITgQB.mjs → doctor-B_mfURdt.mjs} +536 -114
  22. package/dist/{error-diagnostics-DwhNBFDU.mjs → error-diagnostics-_VmkqRZo.mjs} +7 -6
  23. package/dist/{gc-BRu62v4o.mjs → gc-0AyxmECv.mjs} +61 -27
  24. package/dist/{guide-D95E0u3i.mjs → guide-CGxdmQ0K.mjs} +126 -7
  25. package/dist/{ios-DAju3TVy.mjs → ios-CtmGLx3X.mjs} +2667 -2607
  26. package/dist/{ios-device-CWOM_S44.mjs → ios-device-DJOi9FR5.mjs} +1 -1
  27. package/dist/{logs-CAdwW6c2.mjs → logs-BF3qPA7w.mjs} +5 -5
  28. package/dist/metro-DFLmaVz8.mjs +99 -0
  29. package/dist/named-ports-BO7Zpjp0.mjs +137 -0
  30. package/dist/{ownership-8O073GIC.mjs → ownership-BvSpZVBk.mjs} +5 -5
  31. package/dist/{ownership-DXcol4oU.mjs → ownership-C08Yzrp7.mjs} +6 -6
  32. package/dist/ports-BiKqP92J.mjs +39 -0
  33. package/dist/ports-ClqG8GPt.mjs +90 -0
  34. package/dist/{project-fKmuIaMI.mjs → project-CBFCwnN5.mjs} +2 -2
  35. package/dist/{reclaim-CUmwucWG.mjs → reclaim-DpDVo-AP.mjs} +20 -13
  36. package/dist/{reload-CWpXoOgN.mjs → reload-ByTqg6ZI.mjs} +8 -8
  37. package/dist/rolldown-runtime-BsOwBuO_.mjs +30 -0
  38. package/dist/{server-bare-DEzPZLvf.mjs → server-bare-Buk-k8tI.mjs} +3 -6
  39. package/dist/{server-expo-DizT4OMk.mjs → server-expo-D4P7iTvf.mjs} +3 -3
  40. package/dist/server-expo-Dho5Raiw.mjs +2 -0
  41. package/dist/{settings-DEJf6_kJ.mjs → settings-DwfltIXQ.mjs} +2 -2
  42. package/dist/{slot-launch-CZklomj6.mjs → slot-launch-DeKen9e5.mjs} +4 -4
  43. package/dist/{start-B5G4NjiA.mjs → start-BpKUHcU_.mjs} +15 -13
  44. package/dist/state-CDbvmZdt.mjs +131 -0
  45. package/dist/{stats-BR-kwj0B.mjs → stats-C59QLKm2.mjs} +1 -1
  46. package/dist/{stats-CaaQr4yN.mjs → stats-k5qMjyxo.mjs} +4 -4
  47. package/dist/{status-CI9clRSb.mjs → status-Y2ij3dm6.mjs} +12 -11
  48. package/dist/{stop-CjlgDk0a.mjs → stop-_P2OQwmZ.mjs} +15 -14
  49. package/dist/{stop-CQhffpmX.mjs → stop-vKmmxhea.mjs} +2 -2
  50. package/dist/supervisor-run.d.mts +3 -27
  51. package/dist/supervisor-run.mjs +7 -6
  52. package/dist/{teardown--gESs9ck.mjs → teardown-KsPpvldq.mjs} +148 -109
  53. package/dist/{workspace-process-lock-IBj1Towr.mjs → workspace-process-lock-UEIrvZMJ.mjs} +4 -4
  54. package/dist/workspace-state-Do1e2mOH.mjs +146 -0
  55. package/dist/{worktree-DtQGO9-s.mjs → worktree-BvcAIMdJ.mjs} +24 -25
  56. package/dist/{worktree-Eq6TLH8N.mjs → worktree-DWNx4tUg.mjs} +32 -2
  57. package/dist/xcode-D9tVItl-.mjs +860 -0
  58. package/package.json +7 -7
  59. package/dist/doctor-BueDfLYG.mjs +0 -530
  60. package/dist/metro-CORqXyTL.mjs +0 -184
  61. package/dist/ownership-claim-BtPtJVmq.mjs +0 -584
  62. package/dist/server-expo-44em5Pmg.mjs +0 -2
  63. package/dist/state-B3XA2wp2.mjs +0 -268
package/README.md CHANGED
@@ -198,3 +198,16 @@ stim stop --slot tablet
198
198
  `--slot` preserves the shared server and other devices. `stim reload ios` or
199
199
  `stim reload android` can reload multiple connected devices. Read
200
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-BtPtJVmq.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-hMj83VpN.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--gESs9ck.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-VfSZNLT4.mjs";
17
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-IBj1Towr.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-CcBNTvM7.mjs";
20
- import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-C4AouyNz.mjs";
21
- import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-BR-kwj0B.mjs";
22
- import { S as ensureOwnedDevice, _ as AvdBootError, f as startBuildHeartbeat, p as tailLines, r as HEARTBEAT_INTERVAL_MS, v as AvdRecoveryError, w as unknownAndroidSystemImageRefusal, x as ensureBooted, y as checkDeviceCapacity } from "./xcode-xnxyvJkD.mjs";
23
- import { a as ensureMetroReachable, b as ownedSessionName, l as resolveRemoteContext, n as binOnPath, o as ensureRemoteBootOwned, s as remoteAndroidDeps } from "./device-remote-DAA0Zm6Z.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-DXorPCi_.mjs";
25
- import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-CZklomj6.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-DwhNBFDU.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";
@@ -1279,8 +1280,16 @@ function diagnoseBootFailure(booted, logFile, runCommand, physical) {
1279
1280
  });
1280
1281
  }
1281
1282
  //#endregion
1282
- //#region src/commands/android.ts
1283
- 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
+ }
1284
1293
  function androidCompilerCache({ root, optimizations, settingsContext }) {
1285
1294
  const { cas, optimizations: resolved } = resolveAndroidCompilerCache({
1286
1295
  optimizations,
@@ -1305,6 +1314,104 @@ function androidCompilerCache({ root, optimizations, settingsContext }) {
1305
1314
  })}`
1306
1315
  };
1307
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;
1308
1415
  function androidCommand(program) {
1309
1416
  registerAndroid(program);
1310
1417
  }
@@ -1451,6 +1558,25 @@ function androidSlotOptions(options) {
1451
1558
  writeLaunch: ((projectRoot, platform, record) => base.writeLaunch(projectRoot, platform, record, slot))
1452
1559
  };
1453
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
+ }
1454
1580
  async function runAndroid(options = {}) {
1455
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);
1456
1582
  const slot = validateDeviceSlot(options.slot);
@@ -1599,64 +1725,30 @@ async function runAndroid(options = {}) {
1599
1725
  platform: PLATFORM
1600
1726
  });
1601
1727
  const settings = resolveSettingsFor(settingsContext);
1602
- const [shapeError, ...moreShapeErrors] = [parkedMaxSetting("android").error, ...settingShapeErrors(settings)].filter((error) => Boolean(error));
1603
- if (shapeError) return fail("STIM_BAD_ARG", shapeError, SETTING_SHAPE_REMEDY, { lines: moreShapeErrors });
1604
- for (const key of unknownSettingKeys(settings)) out(phaseLine("setting", chalk.yellow(`Warning: setting "${key}" is not read by Stim and will be ignored.`)));
1605
- let optimizations;
1606
- try {
1607
- optimizations = resolveOptimizations(settings);
1608
- } catch (error) {
1609
- return fail("STIM_BAD_ARG", `Could not configure Android build: ${error.message}`, SETTING_SHAPE_REMEDY);
1610
- }
1611
- const compilerCache = androidCompilerCache({
1612
- root,
1613
- optimizations,
1614
- settingsContext
1615
- });
1616
- const cas = compilerCache.cas;
1617
- optimizations = compilerCache.optimizations;
1618
- if (compilerCache.warning) out(phaseLine("cache", chalk.yellow(compilerCache.warning)));
1619
- const buildProfile = optimizationBuildProfile("android", optimizations);
1620
- const cacheProviderConfig = resolveCacheProvider(settingsContext);
1621
- const cacheProviderError = cacheProviderSettingError(settings);
1622
- if (cacheProviderError) out(phaseLine("cache", chalk.yellow(`${cacheProviderError} Using the local cache.`)));
1623
- const dataPartitionSizeError = androidDataPartitionSizeGbSettingError(settings);
1624
- if (dataPartitionSizeError) return fail("STIM_BAD_ARG", dataPartitionSizeError, "Set android.dataPartitionSizeGb to a whole number of GiB from 6 through 16384.");
1625
- const avdConfigError = androidAvdConfigSettingError(settings, settingsRoot);
1626
- if (avdConfigError) return fail("STIM_BAD_ARG", avdConfigError, "Use only documented android.avdConfig keys, or an android.avdConfigFile fragment contained by the app directory.");
1627
- const remoteSettingError = remoteDeviceSettingError(settings);
1628
- if (remoteSettingError) return fail("STIM_BAD_ARG", remoteSettingError, `Set ios.remote and android.remote to one of: ${REMOTE_DEVICE_BACKENDS.join(", ")}.`);
1629
- const systemImage = resolveSystemImage(systemImageFlag, settings);
1630
- const variant = easProfile !== void 0 ? "debug" : resolveVariant(variantFlag, settings);
1631
- const flavorRefusal = productFlavorRefusal({
1632
- flavors: readProductFlavors(root),
1633
- variant
1634
- });
1635
- if (flavorRefusal) return fail(flavorRefusal.code, flavorRefusal.reason, flavorRefusal.remedy);
1636
- const release = isReleaseVariant(variant);
1637
- const cachePolicy = artifactCachePolicy(optimizations, requestedBuildCache, release);
1638
- const useBuildCache = cachePolicy.read;
1639
- const isExpo = detectIsExpo(root);
1640
- const physical = isPhysicalDeviceRequest(deviceFlag);
1641
- 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.");
1642
- 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.");
1643
- const noWait = waitFlag === false;
1644
- const waitFlagged = waitFlag !== void 0;
1645
- 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.");
1646
- 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`.");
1647
- const waitParsed = parseDeviceWait(noWait ? void 0 : waitFlag);
1648
- 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.");
1649
- const waitSeconds = waitParsed.seconds;
1650
- const remoteBackend = physical ? null : commandRemoteBackend ?? remoteAndroidSetting(settings);
1651
- const imageRefusal = systemImageRefusal({
1728
+ const planned = resolveAndroidRunPlan({
1729
+ settings,
1730
+ settingsContext,
1652
1731
  slot,
1653
- flag: systemImageFlag,
1654
- resolved: systemImage,
1655
- physical,
1656
- remoteBackend,
1657
- 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)))
1658
1744
  });
1659
- 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;
1660
1752
  const easBuild = await resolveEasBuild({
1661
1753
  root,
1662
1754
  platform: PLATFORM,
@@ -1668,7 +1760,6 @@ async function runAndroid(options = {}) {
1668
1760
  buildCache: requestedBuildCache
1669
1761
  });
1670
1762
  if (isEasBuildFailure(easBuild)) return fail(easBuild.code, easBuild.message, easBuild.remedy);
1671
- const requestedSerial = typeof deviceFlag === "string" ? deviceFlag : null;
1672
1763
  let androidPackage = detectAndroidPackage(root);
1673
1764
  record.bundleId = androidPackage;
1674
1765
  const registerProject = () => upsertProject(root, {
@@ -1744,15 +1835,15 @@ async function runAndroid(options = {}) {
1744
1835
  let device;
1745
1836
  let bootDuration = "";
1746
1837
  let bootPromise;
1747
- if (physical && !requestedSerial) {
1838
+ if (target.kind === "physical" && !target.serial) {
1748
1839
  const pooled = await pooledAndroidDevice({
1749
1840
  root,
1750
1841
  selectPool,
1751
1842
  listDevices,
1752
1843
  isEmulatorDevice,
1753
1844
  deviceModel,
1754
- waitSeconds,
1755
- noWait,
1845
+ waitSeconds: target.lease.waitSeconds,
1846
+ noWait: target.lease.noWait,
1756
1847
  now,
1757
1848
  warn: (line) => out(phaseLine("lease", chalk.yellow(line)))
1758
1849
  });
@@ -1762,8 +1853,8 @@ async function runAndroid(options = {}) {
1762
1853
  ok: true,
1763
1854
  serial: pooled.device.serial
1764
1855
  });
1765
- } else if (physical) {
1766
- const resolved = resolvePhysicalDevice(requestedSerial, listDevices(), isEmulatorDevice);
1856
+ } else if (target.kind === "physical") {
1857
+ const resolved = resolvePhysicalDevice(target.serial, listDevices(), isEmulatorDevice);
1767
1858
  if (!resolved.serial) return fail(NO_DEVICE, resolved.error, resolved.remedy);
1768
1859
  device = {
1769
1860
  serial: resolved.serial,
@@ -1789,22 +1880,14 @@ async function runAndroid(options = {}) {
1789
1880
  projectPath: root,
1790
1881
  settingsRoot,
1791
1882
  settings,
1792
- flags: { systemImage },
1883
+ flags: { systemImage: target.systemImage },
1793
1884
  note: out,
1794
1885
  out,
1795
1886
  logFile: emuLog
1796
1887
  });
1797
1888
  } catch (err) {
1798
- const diag = noDeviceDiagnostic({
1799
- reason: `Could not ensure an owned Android emulator: ${err?.message || err}`,
1800
- logFile: emuLog,
1801
- localEmulator: !(err instanceof AvdRecoveryError),
1802
- remedy: err instanceof AvdBootError ? 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\"`)."
1803
- });
1804
- return fail(NO_DEVICE, diag.message, diag.remedy, {
1805
- lines: diag.lines,
1806
- logPath: diag.logPath ? displayPath(root, diag.logPath) : null
1807
- });
1889
+ const failure = avdSetupFailure(err, root, emuLog);
1890
+ return fail(failure.code, failure.message, failure.remedy, failure.extra);
1808
1891
  }
1809
1892
  const prepareMs = prepare();
1810
1893
  if (device.created || prepareMs >= 2e3) phase("device", `${device.avdName || device.deviceName || label} ${device.created ? "created" : "prepared"} (${formatDuration(prepareMs)})`);
@@ -1861,7 +1944,7 @@ async function runAndroid(options = {}) {
1861
1944
  deviceAbi,
1862
1945
  compiler: cas?.id,
1863
1946
  buildProfile,
1864
- targetAbiOnly: optimizations.android.targetAbiOnly
1947
+ targetAbiOnly: buildPlan.targetAbiOnly
1865
1948
  });
1866
1949
  let hash = "";
1867
1950
  let providerUpload = null;
@@ -1963,7 +2046,7 @@ async function runAndroid(options = {}) {
1963
2046
  return true;
1964
2047
  }
1965
2048
  const runFromFingerprint = async () => {
1966
- if (metroCheck && metroPort !== null && optimizations.metroWarmup) prewarmMetro({
2049
+ if (metroCheck && metroPort !== null && plan.metroWarmup) prewarmMetro({
1967
2050
  port: metroPort,
1968
2051
  platform: "android",
1969
2052
  isExpo,
@@ -2039,7 +2122,7 @@ async function runAndroid(options = {}) {
2039
2122
  logFile: buildLog
2040
2123
  });
2041
2124
  } catch (err) {
2042
- const refusal = claimFailure(err, "stim android");
2125
+ const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
2043
2126
  if (refusal) {
2044
2127
  phaseFailure = fail(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
2045
2128
  return false;
@@ -2070,7 +2153,7 @@ async function runAndroid(options = {}) {
2070
2153
  ceilingMs
2071
2154
  });
2072
2155
  } catch (err) {
2073
- const refusal = claimFailure(err, "stim android");
2156
+ const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
2074
2157
  if (refusal) {
2075
2158
  phaseFailure = fail(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
2076
2159
  return false;
@@ -2152,7 +2235,7 @@ async function runAndroid(options = {}) {
2152
2235
  out
2153
2236
  });
2154
2237
  } catch (err) {
2155
- const refusal = claimFailure(err, "stim android");
2238
+ const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
2156
2239
  if (refusal) {
2157
2240
  phaseFailure = fail(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
2158
2241
  return false;
@@ -2209,19 +2292,19 @@ async function runAndroid(options = {}) {
2209
2292
  abi: buildAbi
2210
2293
  }, {
2211
2294
  estimateMs: estimates().coldBuildMs,
2212
- ccache: optimizations.android.compilerCache === "ccache" ? ccacheFor({
2295
+ ccache: buildPlan.compilerCache === "ccache" ? ccacheFor({
2213
2296
  root,
2214
2297
  onNote: out
2215
2298
  }) : null,
2216
2299
  cas,
2217
- buildCache: optimizations.android.gradleBuildCache,
2218
- pch: optimizations.android.pch,
2219
- compilerCacheDisabled: optimizations.android.compilerCache === "none"
2300
+ buildCache: buildPlan.gradleBuildCache,
2301
+ pch: buildPlan.pch,
2302
+ compilerCacheDisabled: buildPlan.compilerCache === "none"
2220
2303
  });
2221
2304
  ccacheActivity = built.ccache ?? CCACHE_UNAVAILABLE;
2222
2305
  phase("cache", `compilation cache ${ccacheActivityLine(ccacheActivity)}`);
2223
- if (built.failed) {
2224
- const diagnostics = built.diagnostics || [];
2306
+ if (!built.ok) {
2307
+ const diagnostics = built.diagnostics;
2225
2308
  for (const diag of diagnostics) writer.write({
2226
2309
  src: "build",
2227
2310
  level: "error",
@@ -2230,7 +2313,7 @@ async function runAndroid(options = {}) {
2230
2313
  });
2231
2314
  phase("build", chalk.red(`FAILED after ${formatDuration(built.durationMs)}`));
2232
2315
  const extracted = diagnostics.map(formatDiagnostic);
2233
- 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`);
2234
2317
  phaseFailure = fail(built.code, built.reason, diagnostics.find((d) => d.remedy)?.remedy || built.remedy || null, {
2235
2318
  lastBuildStatus: true,
2236
2319
  diagnostics: extracted,
@@ -2239,8 +2322,8 @@ async function runAndroid(options = {}) {
2239
2322
  });
2240
2323
  return false;
2241
2324
  }
2242
- apkPath = built.apkPath ?? null;
2243
- stats.setBuildMs(built.durationMs ?? 0);
2325
+ apkPath = built.apkPath;
2326
+ stats.setBuildMs(built.durationMs);
2244
2327
  phase("build", `ok (${formatDuration(built.durationMs)})`);
2245
2328
  if (built.apkNote) phase("build", chalk.yellow(built.apkNote));
2246
2329
  const beforeBuildHash = storeHash;
@@ -2332,8 +2415,8 @@ async function runAndroid(options = {}) {
2332
2415
  id: device.serial,
2333
2416
  deviceName: device.deviceName ?? null,
2334
2417
  idLabel: "serial",
2335
- waitSeconds,
2336
- noWait,
2418
+ waitSeconds: target.lease.waitSeconds,
2419
+ noWait: target.lease.noWait,
2337
2420
  installBoundMs: ADB_INSTALL_TIMEOUT_MS,
2338
2421
  appId: androidPackage,
2339
2422
  holderAppId: (holder) => getProject(holder)?.androidPackage ?? null,