stim 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +1 -1
  2. package/dist/{android-BkYmndPp.mjs → android-DWssqHQ8.mjs} +165 -661
  3. package/dist/{android-DLJB8ljN.mjs → android-Da-G_o3n.mjs} +646 -548
  4. package/dist/{android-BW8i_YxU.mjs → android-DeYdjjBP.mjs} +6 -2
  5. package/dist/{android-cas-CrzI_Zha.mjs → android-cas-SWIjzeuL.mjs} +54 -18
  6. package/dist/android-cas-compiler.mjs +1 -1
  7. package/dist/{app-install--PAocWvq.mjs → app-install-DJg7bVv-.mjs} +36 -10
  8. package/dist/build-lock-CgtsB6TV.mjs +604 -0
  9. package/dist/{build-slots-CuPszJDu.mjs → build-slots-C-oAHRT1.mjs} +2 -2
  10. package/dist/{cache-manifest-uoOKhgUD.mjs → cache-manifest-CYJi8-Hx.mjs} +2 -2
  11. package/dist/cache-manifest.d.mts +1 -1
  12. package/dist/cache-manifest.mjs +1 -1
  13. package/dist/cli.mjs +14 -14
  14. package/dist/collector-run.d.mts +2 -0
  15. package/dist/collector-run.mjs +7 -7
  16. package/dist/{command-output-BuIb_ZOI.mjs → command-output-B3ycpsYv.mjs} +2 -2
  17. package/dist/{config-DMP7L7H6.mjs → config-B7iyKWkL.mjs} +56 -16
  18. package/dist/{dependency-state-CTL8A9p3.mjs → dependency-state-D0M7oKA7.mjs} +2 -2
  19. package/dist/{deps-C1zGnr61.mjs → deps-oCXVNEqQ.mjs} +6 -6
  20. package/dist/detached-entry-Bpo6ZY_I.mjs +65 -0
  21. package/dist/{dev-client-BaTXAAnC.mjs → dev-client-DDUkWgJL.mjs} +12 -12
  22. package/dist/{device-xoQxw8hR.mjs → device-CBSaC6BU.mjs} +7 -7
  23. package/dist/{device-XY_5eGdN.mjs → device-CLeDp-KH.mjs} +17 -15
  24. package/dist/{device-lease-DBW-IL4p.mjs → device-lease-Ffz_M5t0.mjs} +3 -3
  25. package/dist/{device-pool-C0s_83Og.mjs → device-pool-DNSHL5z8.mjs} +4 -4
  26. package/dist/{device-remote-D9wN3CdE.mjs → device-remote-BprxZp-m.mjs} +11 -11
  27. package/dist/{doctor-BRO8UKlK.mjs → doctor-CTAx9gMu.mjs} +90 -46
  28. package/dist/{error-diagnostics-B5STzxqN.mjs → error-diagnostics-D1Kaf-eT.mjs} +23 -17
  29. package/dist/{exec-CyylIdq9.mjs → exec-B29XNuhP.mjs} +14 -2
  30. package/dist/{gc-C-6grAQI.mjs → gc-CX0KRfLG.mjs} +17 -16
  31. package/dist/{guide-CYtd7xbA.mjs → guide-CPmAG9me.mjs} +38 -15
  32. package/dist/{guide-status-BF6RsJJp.mjs → guide-status-Craq9z0t.mjs} +3 -3
  33. package/dist/{ios-Ccm6c8Hj.mjs → ios-BRXYSxSc.mjs} +34 -37
  34. package/dist/{ios-device-DJOi9FR5.mjs → ios-device-CDbvn5H2.mjs} +2 -2
  35. package/dist/{ios-device-D3pzTKQE.mjs → ios-device-CFXA46ez.mjs} +1 -1
  36. package/dist/{ios-Deh5js5V.mjs → ios-lEJ5pKsq.mjs} +3 -3
  37. package/dist/{logs-D4Poojxy.mjs → logs-CakdBTtm.mjs} +6 -6
  38. package/dist/{logs-query-Cdp5aaCH.mjs → logs-query-BGL-Pl12.mjs} +1 -1
  39. package/dist/metro-C7bcJ0uo.mjs +233 -0
  40. package/dist/{named-ports-DH9SXd0f.mjs → named-ports-DzMOlmaX.mjs} +57 -42
  41. package/dist/{ownership-CD_XnpDu.mjs → ownership-Cp52QAK1.mjs} +3 -3
  42. package/dist/{ownership-CVY9cqCr.mjs → ownership-D5vkdm8H.mjs} +13 -13
  43. package/dist/{ports-A6uTJSPj.mjs → ports-BIbJaKNW.mjs} +3 -3
  44. package/dist/{process-output-C7IlIzd7.mjs → process-output-Dl5uKE03.mjs} +1 -1
  45. package/dist/{project-B_SDt4ip.mjs → project-DDZWf_16.mjs} +3 -3
  46. package/dist/{reclaim-WXTbXPX4.mjs → reclaim-D614jI6_.mjs} +11 -11
  47. package/dist/{reload-xlQvHKfN.mjs → reload-ChW46VDb.mjs} +9 -9
  48. package/dist/{server-bare-CCLb90FS.mjs → server-bare-BQkyxa_b.mjs} +1 -1
  49. package/dist/{server-expo-CX-zkgWl.mjs → server-expo-BVAA1OqJ.mjs} +23 -11
  50. package/dist/server-expo-YDDG3eP7.mjs +2 -0
  51. package/dist/{settings-CGHTt5vb.mjs → settings-BXXorVM2.mjs} +3 -3
  52. package/dist/{slot-launch-BYjnljAs.mjs → slot-launch-DbLvyrup.mjs} +4 -4
  53. package/dist/{start-DHYAyGzm.mjs → start-TTVILlUi.mjs} +111 -38
  54. package/dist/{state-CnWzvCWP.mjs → state-DZobzxLW.mjs} +2 -2
  55. package/dist/{stats-Fo9ACIv7.mjs → stats-BYcZ6n1h.mjs} +1 -1
  56. package/dist/{stats-DHrO0frH.mjs → stats-CLg5RuDS.mjs} +4 -4
  57. package/dist/{status-QeLZb8SM.mjs → status-DUNy_HZs.mjs} +14 -15
  58. package/dist/{stop-CHTaw65F.mjs → stop-CnUpnFT5.mjs} +2 -2
  59. package/dist/{stop-DsdOx9Hj.mjs → stop-ZrfODZ51.mjs} +13 -13
  60. package/dist/supervisor-run.mjs +7 -5
  61. package/dist/{teardown-sm1FwF6V.mjs → teardown-5J5UXz2P.mjs} +14 -17
  62. package/dist/{workspace-process-lock-Bz19_R9R.mjs → workspace-process-lock-BYfZz_S-.mjs} +1 -1
  63. package/dist/{workspace-state-WVwWw_hB.mjs → workspace-state-BT5E3SA2.mjs} +3 -3
  64. package/dist/{worktree-CCNPg_f0.mjs → worktree-BfE4lzHK.mjs} +21 -16
  65. package/dist/{worktree-DJnBbQZ9.mjs → worktree-FG_ZKKdb.mjs} +13 -9
  66. package/dist/{xcode-DUF7jrXj.mjs → xcode-B6YaWeg0.mjs} +8 -8
  67. package/package.json +12 -8
  68. package/shim/android-optimizations.gradle +6 -0
  69. package/dist/metro-ato6ef3J.mjs +0 -99
  70. package/dist/ports-BZprTJRa.mjs +0 -90
  71. package/dist/server-expo-CjeTJ273.mjs +0 -2
@@ -1,39 +1,39 @@
1
- import { t as getExecutor } from "./exec-CyylIdq9.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-DMP7L7H6.mjs";
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
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-WVwWw_hB.mjs";
5
- import { T as removeTemporaryEntry, g as repoRoot, s as gitCommonDir, w as makeTemporaryDirectory } from "./worktree-DJnBbQZ9.mjs";
6
- import { a as findProjectRoot, c as projectShortcut, i as detectIsExpo, n as detectAndroidPackage, r as detectBundleId, t as appProjectProblem } from "./project-B_SDt4ip.mjs";
7
- import { n as MODE_EXPO, p as writeWorkspaceLaunch, t as MODE_BARE } from "./state-CnWzvCWP.mjs";
8
- import { r as waitForChild, t as createLineReader } from "./process-output-C7IlIzd7.mjs";
9
- import { i as resolveProjectMetro, r as pidExists } from "./metro-ato6ef3J.mjs";
10
- import { n as verifyCollectorOwnership, r as readCollectors } from "./ownership-CD_XnpDu.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-BkYmndPp.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-CGHTt5vb.mjs";
14
- import { t as cleanLine } from "./server-expo-CX-zkgWl.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-sm1FwF6V.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-BaTXAAnC.mjs";
17
- import { t as withWorkspaceProcessLock } from "./workspace-process-lock-Bz19_R9R.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-C0s_83Og.mjs";
20
- import { r as releaseBuildSlot, t as acquireBuildSlot } from "./build-slots-CuPszJDu.mjs";
21
- import { a as statsProjectKey, i as recordRunStats, n as readRunEstimates, t as createRunRecorder } from "./stats-Fo9ACIv7.mjs";
22
- import { i as ensureOwnedDevice, l as AvdBootError, o as unknownAndroidSystemImageRefusal, r as ensureBooted, t as checkDeviceCapacity, u as AvdRecoveryError } from "./device-XY_5eGdN.mjs";
23
- import { a as ensureMetroReachable, b as ownedSessionName, l as resolveRemoteContext, n as binOnPath, o as ensureRemoteBootOwned, s as remoteAndroidDeps } from "./device-remote-D9wN3CdE.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-CrzI_Zha.mjs";
25
- import { d as startBuildHeartbeat, f as tailLines, n as HEARTBEAT_INTERVAL_MS } from "./xcode-DUF7jrXj.mjs";
26
- import { n as nativeRunCommand, t as launchSlotScope } from "./slot-launch-BYjnljAs.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-5J5UXz2P.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-DDUkWgJL.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-DJg7bVv-.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-DNSHL5z8.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-CLeDp-KH.mjs";
24
+ import { a as ensureMetroReachable, b as ownedSessionName, l as resolveRemoteContext, n as binOnPath, o as ensureRemoteBootOwned, s as remoteAndroidDeps } from "./device-remote-BprxZp-m.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-DbLvyrup.mjs";
27
28
  import { t as spawnEntry } from "./spawn-entry-nCE6m770.mjs";
28
- import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-B5STzxqN.mjs";
29
- import { existsSync, mkdirSync, openSync, readFileSync, readdirSync, rmSync } from "node:fs";
29
+ import { a as printNativeCrashReport, r as captureNativeCrashes, t as errorDiagnostics } from "./error-diagnostics-D1Kaf-eT.mjs";
30
+ import { constants, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, renameSync, rmSync } from "node:fs";
30
31
  import { InvalidArgumentError } from "commander";
31
- import { basename, dirname, join, relative } from "node:path";
32
+ import { basename, dirname, isAbsolute, join, relative } from "node:path";
32
33
  import { createWarnOnce, loadCacheProvider, resolveTieredBuild, storeTieredBuild } from "@stim-cli/cache";
33
34
  import chalk from "chalk";
34
35
  //#region src/engine/apk-swap.ts
35
36
  const ANDROID_BUNDLE_NAME = "index.android.bundle";
36
- const ANDROID_BUNDLE_ENTRY = `assets/${ANDROID_BUNDLE_NAME}`;
37
37
  const LAST_LINES = 5;
38
38
  function hermesEnabledFromGradleProperties(text) {
39
39
  if (typeof text !== "string") return true;
@@ -54,16 +54,21 @@ function readAndroidHermesEnabled(root) {
54
54
  }
55
55
  }
56
56
  function hermescBinDir(platform = process.platform) {
57
- return platform === "darwin" ? "osx-bin" : "linux64-bin";
57
+ if (platform === "darwin") return "osx-bin";
58
+ return platform === "win32" ? "win64-bin" : "linux64-bin";
59
+ }
60
+ function hermescName(platform) {
61
+ return platform === "win32" ? "hermesc.exe" : "hermesc";
58
62
  }
59
63
  function hermescCandidates(root, { platform = process.platform, reactNativePath = null } = {}) {
60
64
  const bin = hermescBinDir(platform);
65
+ const name = hermescName(platform);
61
66
  const rn = reactNativePath ?? join(root, "node_modules", "react-native");
62
67
  return [.../* @__PURE__ */ new Set([
63
- join(dirname(rn), "hermes-compiler", "hermesc", bin, "hermesc"),
64
- join(root, "node_modules", "hermes-compiler", "hermesc", bin, "hermesc"),
65
- join(root, "node_modules", "react-native", "sdks", "hermesc", bin, "hermesc"),
66
- join(root, "node_modules", "react-native", "sdks", "hermes", "build", "bin", "hermesc")
68
+ join(dirname(rn), "hermes-compiler", "hermesc", bin, name),
69
+ join(root, "node_modules", "hermes-compiler", "hermesc", bin, name),
70
+ join(root, "node_modules", "react-native", "sdks", "hermesc", bin, name),
71
+ join(root, "node_modules", "react-native", "sdks", "hermes", "build", "bin", name)
67
72
  ])];
68
73
  }
69
74
  function androidHermescPath(root, { exists = existsSync, platform = process.platform, reactNativePath = null } = {}) {
@@ -117,8 +122,8 @@ function androidBundleCommand({ isExpo, entryFile, bundleOutput, assetsDest }) {
117
122
  ]
118
123
  };
119
124
  }
120
- function isNothingToDelete(text) {
121
- return /nothing to do|name not matched|no matches found/i.test(String(text ?? ""));
125
+ function jarPath({ javaHome = process.env.JAVA_HOME } = {}) {
126
+ return javaHome ? join(javaHome, "bin", "jar") : "jar";
122
127
  }
123
128
  function zipalignArgs({ buildToolsMajor, input, output }) {
124
129
  return [
@@ -141,7 +146,7 @@ function resolveKeystore(root, settings) {
141
146
  const bag = android && typeof android === "object" && !Array.isArray(android) ? android : {};
142
147
  const configured = bag["keystore"];
143
148
  return {
144
- path: typeof configured === "string" && configured.trim() !== "" ? configured.trim().startsWith("/") ? configured.trim() : join(root, configured.trim()) : join(root, "android", "app", "debug.keystore"),
149
+ path: typeof configured === "string" && configured.trim() !== "" ? isAbsolute(configured.trim()) ? configured.trim() : join(root, configured.trim()) : join(root, "android", "app", "debug.keystore"),
145
150
  pass: keystorePassArg(bag["keystorePassword"])
146
151
  };
147
152
  }
@@ -155,7 +160,7 @@ function apksignerArgs({ keystore, apkPath }) {
155
160
  apkPath
156
161
  ];
157
162
  }
158
- async function swapApkBundle({ root, isExpo, cachedApkPath, keystore, logWriter = null, exec = null, spawnFn = null, mkdtemp = () => makeTemporaryDirectory(cachedApkPath, "stim-apk-swap-"), exists = existsSync, hermesEnabled = null, buildTools = null, findTool = findBuildTool, storedAssets = null, readManifest = readAssetManifest, now = Date.now, heartbeatMs = HEARTBEAT_INTERVAL_MS, onHeartbeat = (line) => console.error(line) }) {
163
+ async function swapApkBundle({ root, isExpo, cachedApkPath, keystore, logWriter = null, exec = null, spawnFn = null, mkdtemp = () => makeTemporaryDirectory(cachedApkPath, "stim-apk-swap-"), exists = existsSync, hermesEnabled = null, buildTools = null, findTool = findBuildTool, storedAssets = null, readManifest = readAssetManifest, now = Date.now, heartbeatMs = HEARTBEAT_INTERVAL_MS, onHeartbeat = (line) => console.error(line), platform = process.platform }) {
159
164
  const e = exec || getExecutor();
160
165
  const startedAt = now();
161
166
  const elapsed = () => now() - startedAt;
@@ -183,15 +188,7 @@ async function swapApkBundle({ root, isExpo, cachedApkPath, keystore, logWriter
183
188
  const base = basename(cachedApkPath);
184
189
  work = join(tmp, `unaligned-${base}`);
185
190
  final = join(tmp, base);
186
- try {
187
- e.runFile("cp", [
188
- "-c",
189
- cachedApkPath,
190
- work
191
- ]);
192
- } catch {
193
- e.runFile("cp", [cachedApkPath, work]);
194
- }
191
+ copyApk(e, cachedApkPath, work, platform);
195
192
  } catch (err) {
196
193
  return fail("copy", `could not copy ${cachedApkPath} aside: ${describe(err)}`);
197
194
  }
@@ -287,7 +284,7 @@ async function swapApkBundle({ root, isExpo, cachedApkPath, keystore, logWriter
287
284
  bundle: bundleOutput,
288
285
  out: hbc
289
286
  }));
290
- e.runFile("mv", [hbc, bundleOutput]);
287
+ renameSync(hbc, bundleOutput);
291
288
  hermes = true;
292
289
  } catch (err) {
293
290
  return fail("hermesc", `hermesc failed on ${bundleOutput}: ${describe(err)}`);
@@ -315,24 +312,19 @@ async function swapApkBundle({ root, isExpo, cachedApkPath, keystore, logWriter
315
312
  if (!fresh) return fail("assets", `could not hash the assets emitted into ${assetsDest}, so the asset set cannot be verified`);
316
313
  const diff = compareAssetManifests(fresh, storedAssets);
317
314
  if (!diff.same) return refuse(assetDiffReason(diff), diff);
315
+ const jar = jarPath();
318
316
  try {
319
- e.runFile("zip", [
320
- "-d",
321
- work,
322
- ANDROID_BUNDLE_ENTRY
323
- ]);
324
- } catch (err) {
325
- if (!isNothingToDelete(describe(err))) return fail("zip", `zip -d ${ANDROID_BUNDLE_ENTRY} failed on ${work}: ${describe(err)}`);
326
- }
327
- try {
328
- e.runFile("zip", [
329
- "-0",
330
- "-r",
317
+ e.runFile(jar, [
318
+ "--update",
319
+ "--file",
331
320
  work,
321
+ "--no-compress",
322
+ "-C",
323
+ stage,
332
324
  "assets"
333
- ], { cwd: stage });
325
+ ]);
334
326
  } catch (err) {
335
- return fail("zip", `zip -0 -r ${work} assets failed: ${describe(err)}`);
327
+ return fail("zip", `${jar} --update ${work} failed: ${describe(err)}`);
336
328
  }
337
329
  const tools = buildTools ?? findTool(["zipalign"]);
338
330
  if (!tools) return fail("zipalign", "no zipalign found under the Android SDK build-tools; install one with `sdkmanager \"build-tools;36.0.0\"`");
@@ -347,7 +339,7 @@ async function swapApkBundle({ root, isExpo, cachedApkPath, keystore, logWriter
347
339
  }
348
340
  const signer = buildTools ? {
349
341
  ...buildTools,
350
- path: join(dirname(buildTools.path), "apksigner")
342
+ path: join(dirname(buildTools.path), androidBuildToolName("apksigner"))
351
343
  } : findTool(["apksigner"]);
352
344
  if (!signer) return fail("apksigner", "no apksigner found under the Android SDK build-tools; install one with `sdkmanager \"build-tools;36.0.0\"`");
353
345
  try {
@@ -374,6 +366,17 @@ async function swapApkBundle({ root, isExpo, cachedApkPath, keystore, logWriter
374
366
  if (note) result.note = note;
375
367
  return result;
376
368
  }
369
+ function copyApk(e, from, to, platform) {
370
+ if (platform === "darwin") try {
371
+ e.runFile("cp", [
372
+ "-c",
373
+ from,
374
+ to
375
+ ]);
376
+ return;
377
+ } catch {}
378
+ copyFileSync(from, to, constants.COPYFILE_FICLONE);
379
+ }
377
380
  function describe(err) {
378
381
  const e = err;
379
382
  const streams = [e?.stdout, e?.stderr].map((s) => s ? String(s).trim() : "").filter(Boolean).join("\n");
@@ -630,6 +633,519 @@ function androidBuildOptions({ release, physical, device, variant, deviceAbi, co
630
633
  };
631
634
  }
632
635
  //#endregion
636
+ //#region src/commands/android/artifact.ts
637
+ const FALLBACK_LINES = 5;
638
+ function fail$1(code, message, remedy, extra = {}) {
639
+ return {
640
+ code,
641
+ message,
642
+ remedy,
643
+ extra
644
+ };
645
+ }
646
+ 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 { variant, release, profile: buildProfile, cas, cache: cachePolicy } = buildPlan;
649
+ const useBuildCache = cachePolicy.read;
650
+ let androidPackage = initialPackage;
651
+ let ccacheActivity = CCACHE_NOT_RUN;
652
+ let phaseFailure = null;
653
+ const refused = () => ({
654
+ ok: false,
655
+ failure: phaseFailure,
656
+ ccache: ccacheActivity
657
+ });
658
+ let buildLock = null;
659
+ const releaseHeldLock = () => {
660
+ if (!buildLock) return;
661
+ const held = buildLock;
662
+ buildLock = null;
663
+ try {
664
+ releaseLock(held);
665
+ } catch (err) {
666
+ out(phaseLine("build", chalk.dim(`could not release the build lock at ${held.path}: ${err?.message || err}`)));
667
+ }
668
+ };
669
+ let buildSlot = null;
670
+ const releaseHeldSlot = () => {
671
+ if (!buildSlot) return;
672
+ const held = buildSlot;
673
+ buildSlot = null;
674
+ try {
675
+ releaseSlot(held);
676
+ } catch (err) {
677
+ out(phaseLine("build", chalk.dim(`could not release the build slot: ${err?.message || err}`)));
678
+ }
679
+ };
680
+ const { abi: buildAbi, runOptions: buildRunOptions, remoteRunOptions } = androidBuildOptions({
681
+ release,
682
+ physical,
683
+ device,
684
+ variant,
685
+ deviceAbi,
686
+ compiler: cas?.id,
687
+ buildProfile,
688
+ targetAbiOnly: buildPlan.targetAbiOnly
689
+ });
690
+ let hash = "";
691
+ let providerUpload = null;
692
+ let providerName = null;
693
+ let providerLoad = null;
694
+ const cacheWarn = createWarnOnce((line) => phase("cache", chalk.yellow(line)));
695
+ const loadTieredProvider = cachePolicy.remote && cacheProviderConfig ? () => providerLoad ??= loadCacheProviderModule({
696
+ projectRoot: root,
697
+ config: cacheProviderConfig
698
+ }) : null;
699
+ let fingerprintSources = [];
700
+ let cacheKey = "";
701
+ let storeHash = "";
702
+ let storeKey = "";
703
+ let storeSources = [];
704
+ let apkPath = null;
705
+ async function resolveInitialFingerprint() {
706
+ if (easBuild?.ok) {
707
+ hash = storeHash = easBuild.fingerprint;
708
+ cacheKey = storeKey = easBuild.cacheKey;
709
+ apkPath = easBuild.path;
710
+ record.fingerprint = hash;
711
+ record.cacheKey = cacheKey;
712
+ record.cacheHit = easBuild.cacheHit;
713
+ record.cacheSkipped = !useBuildCache;
714
+ providerName = "eas";
715
+ stats.setCacheKey(cacheKey);
716
+ return true;
717
+ }
718
+ const fingerprintTimer = stepTimer(now);
719
+ try {
720
+ const computed = await fingerprint(root, { platform: PLATFORM });
721
+ hash = computed?.hash ?? "";
722
+ fingerprintSources = computed?.sources ?? [];
723
+ } catch (err) {
724
+ phaseFailure = fail$1(NO_FINGERPRINT, `@expo/fingerprint could not fingerprint ${root}: ${err?.message || err}`, "Fix the @expo/fingerprint error above, then retry.");
725
+ return false;
726
+ }
727
+ if (!hash) {
728
+ phaseFailure = fail$1(NO_FINGERPRINT, `@expo/fingerprint returned no hash for ${root}, so the build cache cannot be addressed.`, "Check the project native inputs and the @expo/fingerprint error above, then retry.");
729
+ return false;
730
+ }
731
+ record.fingerprint = hash;
732
+ cacheKey = buildCacheKey(PLATFORM, hash, buildRunOptions);
733
+ stats.setCacheKey(cacheKey);
734
+ record.cacheKey = cacheKey;
735
+ storeHash = hash;
736
+ storeKey = cacheKey;
737
+ storeSources = fingerprintSources;
738
+ const found = await resolveTieredBuild({
739
+ local: filesystemBuildCapability({
740
+ resolve: resolveCached,
741
+ store: storeCached,
742
+ sources: fingerprintSources
743
+ }),
744
+ loadProvider: loadTieredProvider,
745
+ target: {
746
+ projectRoot: root,
747
+ platform: PLATFORM,
748
+ key: cacheKey
749
+ },
750
+ destinationDir: providerDownloadPath(workspaceDir(root)),
751
+ ensureDestination: prepareProviderDownloadDir,
752
+ skipRead: !useBuildCache,
753
+ warn: cacheWarn
754
+ });
755
+ const cached = found?.tier === "local" ? found.path : null;
756
+ record.cacheHit = cached ? "local" : false;
757
+ record.cacheSkipped = !useBuildCache;
758
+ let missDiff = "";
759
+ let missUntracked = null;
760
+ if (!cached) {
761
+ const lastBuild = readState(root)?.lastBuild ?? null;
762
+ const miss = describeFingerprintMiss({
763
+ platform: PLATFORM,
764
+ current: {
765
+ hash,
766
+ sources: fingerprintSources
767
+ },
768
+ lastBuild
769
+ });
770
+ if (miss) {
771
+ missDiff = fingerprintDiffSuffix(miss.changed);
772
+ writer.write(fingerprintDiffRecord({
773
+ changed: miss.changed,
774
+ previousHash: miss.previousHash,
775
+ hash
776
+ }));
777
+ } else if (useBuildCache) missUntracked = untrackedMissLine(untracked({ projectRoot: root }));
778
+ }
779
+ phase("fingerprint", `${shortHash(hash)} ${cached ? "hit" : "miss"}${useBuildCache ? "" : !requestedBuildCache ? " (--no-build-cache)" : " (cache reuse off in config)"} ${fingerprintTimer()}${missDiff}`);
780
+ if (missUntracked) phase("fingerprint", chalk.dim(missUntracked));
781
+ if (found?.tier === "provider") {
782
+ record.cacheHit = "remote";
783
+ providerName = found.providerName ?? null;
784
+ phase("cache", `provider hit (${providerName})${found.storedLocally ? " -> stored locally" : ""}`);
785
+ }
786
+ apkPath = found?.path ?? null;
787
+ return true;
788
+ }
789
+ if (!await resolveInitialFingerprint()) return refused();
790
+ let remote = null;
791
+ let abandonedRemote = false;
792
+ let uploadPending = null;
793
+ async function resolveRemoteArtifact() {
794
+ if (buildAbi || cas || buildProfile || !cachePolicy.remote) return;
795
+ if (!apkPath) {
796
+ const loaded = await loadProvider(root, { isExpo });
797
+ if (loaded?.unavailable) phase("cache", chalk.yellow(`provider not usable: ${loaded.unavailable}`));
798
+ else if (loaded?.provider) remote = loaded;
799
+ if (remote?.name === "eas") {
800
+ const auth = easAuth({
801
+ projectRoot: root,
802
+ owner: loaded?.owner || null
803
+ });
804
+ const authNote = easAuthNote(auth);
805
+ if (authNote) phase("cache", chalk.yellow(authNote));
806
+ if (auth?.code === "logged-out") remote = null;
807
+ }
808
+ }
809
+ if (remote && useBuildCache) {
810
+ const remoteTimer = stepTimer(now);
811
+ const hit = await resolveRemoteBuild({
812
+ logWriter: writer,
813
+ provider: remote.provider,
814
+ platform: PLATFORM,
815
+ projectRoot: root,
816
+ fingerprintHash: hash,
817
+ runOptions: remoteRunOptions
818
+ });
819
+ if (hit?.appPath) {
820
+ let stored = null;
821
+ try {
822
+ stored = storeCached(PLATFORM, cacheKey, hit.appPath, { sources: fingerprintSources });
823
+ } catch (err) {
824
+ phase("cache", chalk.yellow(`remote hit could not be stored locally: ${err?.message || err}`));
825
+ }
826
+ apkPath = stored || hit.appPath;
827
+ record.cacheHit = "remote";
828
+ phase("cache", `remote hit (${remote.name})${stored ? " -> stored locally" : ""} ${remoteTimer()}`);
829
+ } else if (hit?.timedOut) {
830
+ abandonedRemote = true;
831
+ phase("cache", chalk.yellow(`${remote.name} did not answer within ${formatDuration(RESOLVE_TIMEOUT_MS)}; building instead`));
832
+ } else if (hit?.failed) {
833
+ const authNote = remote.name === "eas" && isEasAuthFailureText(hit.failed) ? easAuthNote({
834
+ code: "logged-out",
835
+ reason: hit.failed
836
+ }) : null;
837
+ phase("cache", chalk.yellow(authNote || `${remote.name} could not be used: ${hit.failed}; building instead`));
838
+ } else phase("cache", `remote miss (${remote.name}) ${remoteTimer()}`);
839
+ }
840
+ }
841
+ if (!easBuild) await resolveRemoteArtifact();
842
+ let waitedForBuild = null;
843
+ 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;
922
+ }
923
+ return true;
924
+ }
925
+ if (!easBuild && !await waitForSharedBuild()) return refused();
926
+ let swapDir = null;
927
+ let swapFellBack = false;
928
+ const installableCachedApk = async (key, cachedPath) => {
929
+ if (!release) return cachedPath;
930
+ phase("swap", `regenerating this workspace's JS for the cached ${variant} APK`);
931
+ const swap = await swapApk({
932
+ root,
933
+ isExpo,
934
+ cachedApkPath: cachedPath,
935
+ keystore: resolveKeystore(root, settings),
936
+ logWriter: writer,
937
+ storedAssets: storedAssets(PLATFORM, key)
938
+ });
939
+ if (swap?.ok && swap.apkPath) {
940
+ if (swap.note) phase("swap", chalk.yellow(swap.note));
941
+ swapDir = swap.tmpDir ?? null;
942
+ phase("swap", `${swap.hermes ? "hermes bytecode" : "plain JS"} repacked (store), zipaligned and re-signed (${formatDuration(swap.durationMs)})`);
943
+ return swap.apkPath;
944
+ }
945
+ if (swap?.assetMismatch) phase("swap", chalk.yellow(`${swap.reason} -- building fresh instead` + (swap.assetDiff ? " (an APK cannot be made to carry an asset AAPT did not package)" : "")));
946
+ else phase("swap", chalk.yellow(`failed at ${swap?.step || "unknown step"}: ${swap?.reason || "unknown reason"} -- building fresh instead (a cached ${variant} APK carries its builder's JS; it is never installed after a failed swap)`));
947
+ for (const line of swap?.lastLines ?? []) phase("", chalk.dim(line));
948
+ swapFellBack = true;
949
+ return null;
950
+ };
951
+ async function prepareCachedArtifact() {
952
+ if (apkPath && record.cacheHit) {
953
+ const prepared = await installableCachedApk(cacheKey, apkPath);
954
+ apkPath = prepared;
955
+ if (!prepared) {
956
+ record.cacheHit = false;
957
+ waitedForBuild = null;
958
+ }
959
+ }
960
+ }
961
+ async function buildArtifact() {
962
+ if (!apkPath) try {
963
+ if (maxBuilds) try {
964
+ buildSlot = await acquireSlot({
965
+ max: maxBuilds,
966
+ root,
967
+ logFile: buildLog,
968
+ out
969
+ });
970
+ } catch (err) {
971
+ const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
972
+ if (refusal) {
973
+ phaseFailure = fail$1(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
974
+ return false;
975
+ }
976
+ phase("build", chalk.yellow(`could not take a build slot: ${err?.message || err}; building anyway`));
977
+ }
978
+ if (needsPrebuildFor(root, "android", isExpo)) {
979
+ const pre = await prebuild(root, PLATFORM, writer, { isExpo });
980
+ if (pre.failed) {
981
+ phaseFailure = fail$1(pre.code, pre.reason, pre.remedy, {
982
+ lastBuildStatus: true,
983
+ lines: tail(pre.lastLines),
984
+ logPath: displayPath(root, buildLog)
985
+ });
986
+ return false;
987
+ }
988
+ phase("prebuild", `android/ generated (${formatDuration(pre.durationMs)})`);
989
+ androidPackage = androidPackage || detectAndroidPackage(root);
990
+ record.bundleId = androidPackage;
991
+ const after = await refingerprintAfterMutation({
992
+ projectRoot: root,
993
+ platform: PLATFORM,
994
+ previousHash: hash,
995
+ fingerprint
996
+ });
997
+ if (after?.moved) {
998
+ storeHash = after.hash;
999
+ storeSources = after.sources;
1000
+ storeKey = buildCacheKey(PLATFORM, after.hash, buildRunOptions);
1001
+ record.fingerprint = storeHash;
1002
+ record.cacheKey = storeKey;
1003
+ phase("fingerprint", chalk.dim(`${shortHash(hash)} -> ${shortHash(storeHash)} (after prebuild)`));
1004
+ const late = useBuildCache ? resolveCached(PLATFORM, storeKey) : null;
1005
+ if (late) {
1006
+ const prepared = await installableCachedApk(storeKey, late);
1007
+ if (prepared) {
1008
+ apkPath = prepared;
1009
+ record.cacheHit = "local";
1010
+ phase("cache", `hit ${shortHash(storeHash)} (post-prebuild key)`);
1011
+ if (releasedWait) {
1012
+ waitedForBuild = releasedWait.facts;
1013
+ phase("build", `waited ${formatDuration(waitedForBuild.ms)} for ${releasedWait.who}'s build -> installed from cache -- stim guide lifecycle concurrency`);
1014
+ }
1015
+ }
1016
+ }
1017
+ }
1018
+ }
1019
+ if (!apkPath) {
1020
+ phase("build", `compiling ${variant || "debug"} with Gradle`);
1021
+ const built = await build({
1022
+ root,
1023
+ logWriter: writer,
1024
+ variant,
1025
+ abi: buildAbi
1026
+ }, {
1027
+ estimateMs: estimates().coldBuildMs,
1028
+ ccache: buildPlan.compilerCache === "ccache" ? ccacheFor({
1029
+ root,
1030
+ onNote: out
1031
+ }) : null,
1032
+ cas,
1033
+ buildCache: buildPlan.gradleBuildCache,
1034
+ pch: buildPlan.pch,
1035
+ compilerCacheDisabled: buildPlan.compilerCache === "none"
1036
+ });
1037
+ ccacheActivity = built.ccache ?? CCACHE_UNAVAILABLE;
1038
+ phase("cache", `compilation cache ${ccacheActivityLine(ccacheActivity)}`);
1039
+ if (!built.ok) {
1040
+ const diagnostics = built.diagnostics;
1041
+ for (const diag of diagnostics) writer.write({
1042
+ src: "build",
1043
+ level: "error",
1044
+ event: "gradle_diagnostic",
1045
+ msg: formatDiagnostic(diag)
1046
+ });
1047
+ phase("build", chalk.red(`FAILED after ${formatDuration(built.durationMs)}`));
1048
+ const extracted = diagnostics.map(formatDiagnostic);
1049
+ if (built.truncated > 0) extracted.push(`... and ${built.truncated} more diagnostic(s) in the log`);
1050
+ phaseFailure = fail$1(built.code, built.reason, diagnostics.find((d) => d.remedy)?.remedy || built.remedy || null, {
1051
+ lastBuildStatus: true,
1052
+ diagnostics: extracted,
1053
+ lines: extracted.length ? [] : tail(built.lastLines),
1054
+ logPath: displayPath(root, buildLog)
1055
+ });
1056
+ return false;
1057
+ }
1058
+ apkPath = built.apkPath;
1059
+ stats.setBuildMs(built.durationMs);
1060
+ phase("build", `ok (${formatDuration(built.durationMs)})`);
1061
+ if (built.apkNote) phase("build", chalk.yellow(built.apkNote));
1062
+ const beforeBuildHash = storeHash;
1063
+ const afterBuild = await refingerprintAfterMutation({
1064
+ projectRoot: root,
1065
+ platform: PLATFORM,
1066
+ previousHash: beforeBuildHash,
1067
+ fingerprint
1068
+ });
1069
+ if (!afterBuild) {
1070
+ record.fingerprint = null;
1071
+ record.cacheKey = null;
1072
+ phase("fingerprint", chalk.yellow("unavailable after Gradle; the build will be installed but not cached"));
1073
+ } else {
1074
+ if (afterBuild.moved) {
1075
+ storeHash = afterBuild.hash;
1076
+ storeSources = afterBuild.sources;
1077
+ storeKey = buildCacheKey(PLATFORM, afterBuild.hash, buildRunOptions);
1078
+ record.fingerprint = storeHash;
1079
+ record.cacheKey = storeKey;
1080
+ phase("fingerprint", chalk.dim(`${shortHash(beforeBuildHash)} -> ${shortHash(storeHash)} (after Gradle)`));
1081
+ }
1082
+ if (cachePolicy.write) {
1083
+ const assetManifest = release ? captureAssets(root, { variant }) : null;
1084
+ try {
1085
+ const stored = await storeTieredBuild({
1086
+ local: filesystemBuildCapability({
1087
+ resolve: resolveCached,
1088
+ store: storeCached,
1089
+ sources: storeSources,
1090
+ assetManifest
1091
+ }),
1092
+ loadProvider: loadTieredProvider,
1093
+ target: {
1094
+ projectRoot: root,
1095
+ platform: PLATFORM,
1096
+ key: storeKey
1097
+ },
1098
+ sourcePath: apkPath,
1099
+ overwrite: !useBuildCache || swapFellBack,
1100
+ warn: cacheWarn
1101
+ });
1102
+ providerUpload = stored.providerUpload;
1103
+ providerName = stored.providerName ?? providerName;
1104
+ } catch (err) {
1105
+ phase("cache", chalk.yellow(`could not store the build: ${err?.message || err}`));
1106
+ }
1107
+ }
1108
+ if (remote) uploadPending = uploadRemoteBuild({
1109
+ logWriter: writer,
1110
+ provider: remote.provider,
1111
+ platform: PLATFORM,
1112
+ projectRoot: root,
1113
+ fingerprintHash: storeHash,
1114
+ buildPath: apkPath,
1115
+ runOptions: remoteRunOptions
1116
+ });
1117
+ }
1118
+ }
1119
+ } finally {
1120
+ releaseHeldLock();
1121
+ releaseHeldSlot();
1122
+ }
1123
+ return true;
1124
+ }
1125
+ if (!easBuild) {
1126
+ await prepareCachedArtifact();
1127
+ if (!await buildArtifact()) return refused();
1128
+ }
1129
+ return {
1130
+ ok: true,
1131
+ artifact: {
1132
+ apkPath,
1133
+ androidPackage,
1134
+ swapDir,
1135
+ waitedForBuild,
1136
+ ccache: ccacheActivity,
1137
+ uploadPending,
1138
+ providerUpload,
1139
+ providerName,
1140
+ remote,
1141
+ abandonedRemote
1142
+ }
1143
+ };
1144
+ }
1145
+ function tail(lines, n = FALLBACK_LINES) {
1146
+ return (Array.isArray(lines) ? lines : []).slice(-n);
1147
+ }
1148
+ //#endregion
633
1149
  //#region src/commands/android/result.ts
634
1150
  function androidFacts({ slot, serial, avdName = null, deviceName = null, systemImage = null, fingerprint, cacheKey = null, variant = null, metroPort = null, cacheHit, cacheSkipped = false, waitedForBuild = null, appPath, bundleId, installSkipped = false, launched, ccache = CCACHE_UNAVAILABLE, logs, debugHttpHost = null, debugHttpHostNote = null, devClientUrl = null, durationMs, lease }) {
635
1151
  return {
@@ -775,11 +1291,11 @@ function collectorEntry() {
775
1291
  return spawnEntry("collector-run");
776
1292
  }
777
1293
  function collectorLogFile(root, slot = "default") {
778
- return join(workspaceLogsDir(root), `collector-${deviceSlotKey(PLATFORM, slot)}.log`);
1294
+ return join(workspaceLogsDir(root), `collector-${deviceSlotFileKey(PLATFORM, slot)}.log`);
779
1295
  }
780
1296
  const COLLECTOR_EXIT_WAIT_MS = 2e3;
781
1297
  const COLLECTOR_POLL_MS = 25;
782
- function killPreviousCollector(root, { platform = PLATFORM, slot = "default", kill = (pid, signal) => process.kill(pid, signal), collectors = null, verify = verifyCollectorOwnership, isAlive = pidExists, note = (_line) => {} } = {}) {
1298
+ function killPreviousCollector(root, { platform = PLATFORM, slot = "default", kill = (pid, signal) => signalProcessTree(pid, signal), collectors = null, verify = verifyCollectorOwnership, isAlive = pidExists, note = (_line) => {} } = {}) {
783
1299
  const record = (collectors ?? readCollectors(root))?.[deviceSlotKey(platform, slot)];
784
1300
  const pid = Number(record?.pid);
785
1301
  if (!Number.isFinite(pid) || pid <= 0 || pid === process.pid) return null;
@@ -837,7 +1353,7 @@ async function startCollector({ root, slot = "default", serial, packageName, spa
837
1353
  "--package",
838
1354
  packageName
839
1355
  ], {
840
- cwd: root,
1356
+ cwd: androidToolCwd() ?? root,
841
1357
  detached: true,
842
1358
  stdio,
843
1359
  env: process.env
@@ -965,6 +1481,7 @@ async function verifyAndroidRun({ root, slot, release, remoteRelease, remoteDevi
965
1481
  serial,
966
1482
  component,
967
1483
  devClientUrl: scheme ? androidDevClientUrl(scheme, metroPort ?? 8081, physical) : null,
1484
+ devClient: Boolean(scheme),
968
1485
  mode: isExpo ? MODE_EXPO : MODE_BARE
969
1486
  })) phase("", chalk.yellow(line));
970
1487
  return { state: LAUNCH_UNVERIFIED };
@@ -1411,7 +1928,6 @@ function resolveAndroidRunPlan({ settings, settingsContext, slot, easProfile, va
1411
1928
  }
1412
1929
  //#endregion
1413
1930
  //#region src/commands/android.ts
1414
- const FALLBACK_LINES = 5;
1415
1931
  function androidCommand(program) {
1416
1932
  registerAndroid(program);
1417
1933
  }
@@ -1624,7 +2140,7 @@ async function runAndroid(options = {}) {
1624
2140
  };
1625
2141
  }
1626
2142
  const logsDir = workspaceLogsDir(root);
1627
- const buildLog = join(logsDir, `build-${deviceSlotKey("android", slot)}.ndjson`);
2143
+ const buildLog = join(logsDir, `build-${deviceSlotFileKey("android", slot)}.ndjson`);
1628
2144
  const writer = createWriter(buildLog, {
1629
2145
  truncate: true,
1630
2146
  fields: { slot }
@@ -1638,28 +2154,6 @@ async function runAndroid(options = {}) {
1638
2154
  avdName: null,
1639
2155
  deviceName: null
1640
2156
  };
1641
- let buildLock = null;
1642
- const releaseHeldLock = () => {
1643
- if (!buildLock) return;
1644
- const held = buildLock;
1645
- buildLock = null;
1646
- try {
1647
- releaseLock(held);
1648
- } catch (err) {
1649
- out(phaseLine("build", chalk.dim(`could not release the build lock at ${held.path}: ${err?.message || err}`)));
1650
- }
1651
- };
1652
- let buildSlot = null;
1653
- const releaseHeldSlot = () => {
1654
- if (!buildSlot) return;
1655
- const held = buildSlot;
1656
- buildSlot = null;
1657
- try {
1658
- releaseSlot(held);
1659
- } catch (err) {
1660
- out(phaseLine("build", chalk.dim(`could not release the build slot: ${err?.message || err}`)));
1661
- }
1662
- };
1663
2157
  const phase = (label, text) => out(phaseLine(label, text));
1664
2158
  const stats = createRunRecorder({
1665
2159
  platform: PLATFORM,
@@ -1745,7 +2239,7 @@ async function runAndroid(options = {}) {
1745
2239
  if (!planned.ok) return fail(planned.code, planned.message, planned.remedy, { lines: planned.lines });
1746
2240
  const { plan } = planned;
1747
2241
  const { build: buildPlan, target, isExpo, cacheProviderConfig } = plan;
1748
- const { variant, release, profile: buildProfile, cas, cache: cachePolicy } = buildPlan;
2242
+ const { variant, release, cache: cachePolicy } = buildPlan;
1749
2243
  const useBuildCache = cachePolicy.read;
1750
2244
  const physical = target.kind === "physical";
1751
2245
  const remoteBackend = target.kind === "remote" ? target.backend : null;
@@ -1936,115 +2430,6 @@ async function runAndroid(options = {}) {
1936
2430
  record.avdName = device.avdName ?? null;
1937
2431
  record.deviceName = device.deviceName ?? device.avdName ?? null;
1938
2432
  record.systemImage = device.systemImage;
1939
- const { abi: buildAbi, runOptions: buildRunOptions, remoteRunOptions } = androidBuildOptions({
1940
- release,
1941
- physical,
1942
- device,
1943
- variant,
1944
- deviceAbi,
1945
- compiler: cas?.id,
1946
- buildProfile,
1947
- targetAbiOnly: buildPlan.targetAbiOnly
1948
- });
1949
- let hash = "";
1950
- let providerUpload = null;
1951
- let providerName = null;
1952
- let providerLoad = null;
1953
- const cacheWarn = createWarnOnce((line) => phase("cache", chalk.yellow(line)));
1954
- const loadTieredProvider = cachePolicy.remote && cacheProviderConfig ? () => providerLoad ??= loadCacheProviderModule({
1955
- projectRoot: root,
1956
- config: cacheProviderConfig
1957
- }) : null;
1958
- let fingerprintSources = [];
1959
- let cacheKey = "";
1960
- let storeHash = "";
1961
- let storeKey = "";
1962
- let storeSources = [];
1963
- let apkPath = null;
1964
- async function resolveInitialFingerprint() {
1965
- if (easBuild?.ok) {
1966
- hash = storeHash = easBuild.fingerprint;
1967
- cacheKey = storeKey = easBuild.cacheKey;
1968
- apkPath = easBuild.path;
1969
- record.fingerprint = hash;
1970
- record.cacheKey = cacheKey;
1971
- record.cacheHit = easBuild.cacheHit;
1972
- record.cacheSkipped = !useBuildCache;
1973
- providerName = "eas";
1974
- stats.setCacheKey(cacheKey);
1975
- return true;
1976
- }
1977
- const fingerprintTimer = stepTimer(now);
1978
- try {
1979
- const computed = await fingerprint(root, { platform: PLATFORM });
1980
- hash = computed?.hash ?? "";
1981
- fingerprintSources = computed?.sources ?? [];
1982
- } catch (err) {
1983
- phaseFailure = fail(NO_FINGERPRINT, `@expo/fingerprint could not fingerprint ${root}: ${err?.message || err}`, "Fix the @expo/fingerprint error above, then retry.");
1984
- return false;
1985
- }
1986
- if (!hash) {
1987
- phaseFailure = fail(NO_FINGERPRINT, `@expo/fingerprint returned no hash for ${root}, so the build cache cannot be addressed.`, "Check the project native inputs and the @expo/fingerprint error above, then retry.");
1988
- return false;
1989
- }
1990
- record.fingerprint = hash;
1991
- cacheKey = buildCacheKey(PLATFORM, hash, buildRunOptions);
1992
- stats.setCacheKey(cacheKey);
1993
- record.cacheKey = cacheKey;
1994
- storeHash = hash;
1995
- storeKey = cacheKey;
1996
- storeSources = fingerprintSources;
1997
- const found = await resolveTieredBuild({
1998
- local: filesystemBuildCapability({
1999
- resolve: resolveCached,
2000
- store: storeCached,
2001
- sources: fingerprintSources
2002
- }),
2003
- loadProvider: loadTieredProvider,
2004
- target: {
2005
- projectRoot: root,
2006
- platform: PLATFORM,
2007
- key: cacheKey
2008
- },
2009
- destinationDir: providerDownloadPath(workspaceDir(root)),
2010
- ensureDestination: prepareProviderDownloadDir,
2011
- skipRead: !useBuildCache,
2012
- warn: cacheWarn
2013
- });
2014
- const cached = found?.tier === "local" ? found.path : null;
2015
- record.cacheHit = cached ? "local" : false;
2016
- record.cacheSkipped = !useBuildCache;
2017
- let missDiff = "";
2018
- let missUntracked = null;
2019
- if (!cached) {
2020
- const lastBuild = readState(root)?.lastBuild ?? null;
2021
- const miss = describeFingerprintMiss({
2022
- platform: PLATFORM,
2023
- current: {
2024
- hash,
2025
- sources: fingerprintSources
2026
- },
2027
- lastBuild
2028
- });
2029
- if (miss) {
2030
- missDiff = fingerprintDiffSuffix(miss.changed);
2031
- writer.write(fingerprintDiffRecord({
2032
- changed: miss.changed,
2033
- previousHash: miss.previousHash,
2034
- hash
2035
- }));
2036
- } else if (useBuildCache) missUntracked = untrackedMissLine(untracked({ projectRoot: root }));
2037
- }
2038
- phase("fingerprint", `${shortHash(hash)} ${cached ? "hit" : "miss"}${useBuildCache ? "" : !requestedBuildCache ? " (--no-build-cache)" : " (cache reuse off in config)"} ${fingerprintTimer()}${missDiff}`);
2039
- if (missUntracked) phase("fingerprint", chalk.dim(missUntracked));
2040
- if (found?.tier === "provider") {
2041
- record.cacheHit = "remote";
2042
- providerName = found.providerName ?? null;
2043
- phase("cache", `provider hit (${providerName})${found.storedLocally ? " -> stored locally" : ""}`);
2044
- }
2045
- apkPath = found?.path ?? null;
2046
- return true;
2047
- }
2048
2433
  const runFromFingerprint = async () => {
2049
2434
  if (metroCheck && metroPort !== null && plan.metroWarmup) prewarmMetro({
2050
2435
  port: metroPort,
@@ -2053,346 +2438,62 @@ async function runAndroid(options = {}) {
2053
2438
  appId: androidPackage,
2054
2439
  bundleUrl: metroWarmupUrlSetting(settings, "android")
2055
2440
  });
2056
- if (!await resolveInitialFingerprint()) return phaseFailure;
2057
- let remote = null;
2058
- let abandonedRemote = false;
2059
- let uploadPending = null;
2060
- async function resolveRemoteArtifact() {
2061
- if (buildAbi || cas || buildProfile || !cachePolicy.remote) return;
2062
- if (!apkPath) {
2063
- const loaded = await loadProvider(root, { isExpo });
2064
- if (loaded?.unavailable) phase("cache", chalk.yellow(`provider not usable: ${loaded.unavailable}`));
2065
- else if (loaded?.provider) remote = loaded;
2066
- if (remote?.name === "eas") {
2067
- const auth = easAuth({
2068
- projectRoot: root,
2069
- owner: loaded?.owner || null
2070
- });
2071
- const authNote = easAuthNote(auth);
2072
- if (authNote) phase("cache", chalk.yellow(authNote));
2073
- if (auth?.code === "logged-out") remote = null;
2074
- }
2075
- }
2076
- if (remote && useBuildCache) {
2077
- const remoteTimer = stepTimer(now);
2078
- const hit = await resolveRemoteBuild({
2079
- logWriter: writer,
2080
- provider: remote.provider,
2081
- platform: PLATFORM,
2082
- projectRoot: root,
2083
- fingerprintHash: hash,
2084
- runOptions: remoteRunOptions
2085
- });
2086
- if (hit?.appPath) {
2087
- let stored = null;
2088
- try {
2089
- stored = storeCached(PLATFORM, cacheKey, hit.appPath, { sources: fingerprintSources });
2090
- } catch (err) {
2091
- phase("cache", chalk.yellow(`remote hit could not be stored locally: ${err?.message || err}`));
2092
- }
2093
- apkPath = stored || hit.appPath;
2094
- record.cacheHit = "remote";
2095
- phase("cache", `remote hit (${remote.name})${stored ? " -> stored locally" : ""} ${remoteTimer()}`);
2096
- } else if (hit?.timedOut) {
2097
- abandonedRemote = true;
2098
- phase("cache", chalk.yellow(`${remote.name} did not answer within ${formatDuration(RESOLVE_TIMEOUT_MS)}; building instead`));
2099
- } else if (hit?.failed) {
2100
- const authNote = remote.name === "eas" && isEasAuthFailureText(hit.failed) ? easAuthNote({
2101
- code: "logged-out",
2102
- reason: hit.failed
2103
- }) : null;
2104
- phase("cache", chalk.yellow(authNote || `${remote.name} could not be used: ${hit.failed}; building instead`));
2105
- } else phase("cache", `remote miss (${remote.name}) ${remoteTimer()}`);
2106
- }
2107
- }
2108
- if (!easBuild) await resolveRemoteArtifact();
2109
- let waitedForBuild = null;
2110
- let releasedWait = null;
2111
- async function waitForSharedBuild() {
2112
- if (!useBuildCache) return true;
2113
- const waitStarted = now();
2114
- let failedHolder;
2115
- while (!apkPath) {
2116
- let attempt = null;
2117
- try {
2118
- attempt = acquireLock({
2119
- platform: PLATFORM,
2120
- key: cacheKey,
2121
- root,
2122
- logFile: buildLog
2123
- });
2124
- } catch (err) {
2125
- const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
2126
- if (refusal) {
2127
- phaseFailure = fail(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
2128
- return false;
2129
- }
2130
- phase("build", chalk.yellow(`could not take the build lock: ${err?.message || err}; building anyway`));
2131
- }
2132
- if (attempt?.acquired) {
2133
- buildLock = attempt;
2134
- const previous = attempt.tookOver ?? failedHolder;
2135
- if (previous) phase("build", chalk.yellow(takeoverLine(previous)));
2136
- break;
2137
- } else if (attempt?.held) {
2138
- releasedWait = null;
2139
- const holder = attempt.held;
2140
- const who = holder.projectRoot || "another workspace";
2141
- phase("build", `${who} is already building ${shortHash(hash)} (pid ${holder.pid})${holder.logFile ? ` -- tail ${holder.logFile}` : ""} -- stim guide lifecycle concurrency`);
2142
- let waited = null;
2143
- try {
2144
- const ceilingMs = WAIT_CEILING_MS - (now() - waitStarted);
2145
- 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}.`), {
2146
- code: "STIM_BUILD_WAIT_TIMEOUT",
2147
- lockPath: attempt.path
2148
- });
2149
- waited = await waitForBuild({
2150
- platform: PLATFORM,
2151
- key: cacheKey,
2152
- out,
2153
- ceilingMs
2154
- });
2155
- } catch (err) {
2156
- const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
2157
- if (refusal) {
2158
- phaseFailure = fail(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
2159
- return false;
2160
- }
2161
- const wtErr = err;
2162
- if (wtErr?.code !== "STIM_BUILD_WAIT_TIMEOUT") throw err;
2163
- phaseFailure = fail("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 });
2164
- return false;
2165
- }
2166
- if (waited?.hit) {
2167
- apkPath = waited.hit ?? null;
2168
- record.cacheHit = "local";
2169
- waitedForBuild = {
2170
- pid: holder.pid,
2171
- ms: waited.waitedMs
2172
- };
2173
- phase("build", `waited ${formatDuration(waited.waitedMs)} for ${who}'s build -> installed from cache -- stim guide lifecycle concurrency`);
2174
- } else if (waited?.lockReleased) {
2175
- failedHolder = void 0;
2176
- releasedWait = {
2177
- facts: {
2178
- pid: holder.pid,
2179
- ms: waited.waitedMs
2180
- },
2181
- who
2182
- };
2183
- phase("build", `${who}'s build lock was released; rechecking this workspace before building`);
2184
- } else {
2185
- phase("build", chalk.yellow(`${who}'s build ended without an artifact (${waited?.builderFailed}); retrying the build lock`));
2186
- failedHolder = holder;
2187
- }
2188
- } else break;
2189
- }
2190
- return true;
2191
- }
2192
- if (!easBuild && !await waitForSharedBuild()) return phaseFailure;
2193
- let swapDir = null;
2194
- let swapFellBack = false;
2195
- const installableCachedApk = async (key, cachedPath) => {
2196
- if (!release) return cachedPath;
2197
- phase("swap", `regenerating this workspace's JS for the cached ${variant} APK`);
2198
- const swap = await swapApk({
2199
- root,
2200
- isExpo,
2201
- cachedApkPath: cachedPath,
2202
- keystore: resolveKeystore(root, settings),
2203
- logWriter: writer,
2204
- storedAssets: storedAssets(PLATFORM, key)
2205
- });
2206
- if (swap?.ok && swap.apkPath) {
2207
- if (swap.note) phase("swap", chalk.yellow(swap.note));
2208
- swapDir = swap.tmpDir ?? null;
2209
- phase("swap", `${swap.hermes ? "hermes bytecode" : "plain JS"} repacked (store), zipaligned and re-signed (${formatDuration(swap.durationMs)})`);
2210
- return swap.apkPath;
2211
- }
2212
- if (swap?.assetMismatch) phase("swap", chalk.yellow(`${swap.reason} -- building fresh instead` + (swap.assetDiff ? " (an APK cannot be made to carry an asset AAPT did not package)" : "")));
2213
- else phase("swap", chalk.yellow(`failed at ${swap?.step || "unknown step"}: ${swap?.reason || "unknown reason"} -- building fresh instead (a cached ${variant} APK carries its builder's JS; it is never installed after a failed swap)`));
2214
- for (const line of swap?.lastLines ?? []) phase("", chalk.dim(line));
2215
- swapFellBack = true;
2216
- return null;
2217
- };
2218
- async function prepareCachedArtifact() {
2219
- if (apkPath && record.cacheHit) {
2220
- const prepared = await installableCachedApk(cacheKey, apkPath);
2221
- apkPath = prepared;
2222
- if (!prepared) {
2223
- record.cacheHit = false;
2224
- waitedForBuild = null;
2225
- }
2226
- }
2227
- }
2228
- async function buildArtifact() {
2229
- if (!apkPath) try {
2230
- if (limits.maxBuilds) try {
2231
- buildSlot = await acquireSlot({
2232
- max: limits.maxBuilds,
2233
- root,
2234
- logFile: buildLog,
2235
- out
2236
- });
2237
- } catch (err) {
2238
- const refusal = (0, ownership_claim_exports.claimFailure)(err, "stim android");
2239
- if (refusal) {
2240
- phaseFailure = fail(refusal.code, refusal.message, refusal.remedy, { lastBuildStatus: true });
2241
- return false;
2242
- }
2243
- phase("build", chalk.yellow(`could not take a build slot: ${err?.message || err}; building anyway`));
2244
- }
2245
- if (needsPrebuildFor(root, "android", isExpo)) {
2246
- const pre = await prebuild(root, PLATFORM, writer, { isExpo });
2247
- if (pre.failed) {
2248
- phaseFailure = fail(pre.code, pre.reason, pre.remedy, {
2249
- lastBuildStatus: true,
2250
- lines: tail(pre.lastLines),
2251
- logPath: displayPath(root, buildLog)
2252
- });
2253
- return false;
2254
- }
2255
- phase("prebuild", `android/ generated (${formatDuration(pre.durationMs)})`);
2256
- androidPackage = androidPackage || detectAndroidPackage(root);
2257
- record.bundleId = androidPackage;
2258
- const after = await refingerprintAfterMutation({
2259
- projectRoot: root,
2260
- platform: PLATFORM,
2261
- previousHash: hash,
2262
- fingerprint
2263
- });
2264
- if (after?.moved) {
2265
- storeHash = after.hash;
2266
- storeSources = after.sources;
2267
- storeKey = buildCacheKey(PLATFORM, after.hash, buildRunOptions);
2268
- record.fingerprint = storeHash;
2269
- record.cacheKey = storeKey;
2270
- phase("fingerprint", chalk.dim(`${shortHash(hash)} -> ${shortHash(storeHash)} (after prebuild)`));
2271
- const late = useBuildCache ? resolveCached(PLATFORM, storeKey) : null;
2272
- if (late) {
2273
- const prepared = await installableCachedApk(storeKey, late);
2274
- if (prepared) {
2275
- apkPath = prepared;
2276
- record.cacheHit = "local";
2277
- phase("cache", `hit ${shortHash(storeHash)} (post-prebuild key)`);
2278
- if (releasedWait) {
2279
- waitedForBuild = releasedWait.facts;
2280
- phase("build", `waited ${formatDuration(waitedForBuild.ms)} for ${releasedWait.who}'s build -> installed from cache -- stim guide lifecycle concurrency`);
2281
- }
2282
- }
2283
- }
2284
- }
2285
- }
2286
- if (!apkPath) {
2287
- phase("build", `compiling ${variant || "debug"} with Gradle`);
2288
- const built = await build({
2289
- root,
2290
- logWriter: writer,
2291
- variant,
2292
- abi: buildAbi
2293
- }, {
2294
- estimateMs: estimates().coldBuildMs,
2295
- ccache: buildPlan.compilerCache === "ccache" ? ccacheFor({
2296
- root,
2297
- onNote: out
2298
- }) : null,
2299
- cas,
2300
- buildCache: buildPlan.gradleBuildCache,
2301
- pch: buildPlan.pch,
2302
- compilerCacheDisabled: buildPlan.compilerCache === "none"
2303
- });
2304
- ccacheActivity = built.ccache ?? CCACHE_UNAVAILABLE;
2305
- phase("cache", `compilation cache ${ccacheActivityLine(ccacheActivity)}`);
2306
- if (!built.ok) {
2307
- const diagnostics = built.diagnostics;
2308
- for (const diag of diagnostics) writer.write({
2309
- src: "build",
2310
- level: "error",
2311
- event: "gradle_diagnostic",
2312
- msg: formatDiagnostic(diag)
2313
- });
2314
- phase("build", chalk.red(`FAILED after ${formatDuration(built.durationMs)}`));
2315
- const extracted = diagnostics.map(formatDiagnostic);
2316
- if (built.truncated > 0) extracted.push(`... and ${built.truncated} more diagnostic(s) in the log`);
2317
- phaseFailure = fail(built.code, built.reason, diagnostics.find((d) => d.remedy)?.remedy || built.remedy || null, {
2318
- lastBuildStatus: true,
2319
- diagnostics: extracted,
2320
- lines: extracted.length ? [] : tail(built.lastLines),
2321
- logPath: displayPath(root, buildLog)
2322
- });
2323
- return false;
2324
- }
2325
- apkPath = built.apkPath;
2326
- stats.setBuildMs(built.durationMs);
2327
- phase("build", `ok (${formatDuration(built.durationMs)})`);
2328
- if (built.apkNote) phase("build", chalk.yellow(built.apkNote));
2329
- const beforeBuildHash = storeHash;
2330
- const afterBuild = await refingerprintAfterMutation({
2331
- projectRoot: root,
2332
- platform: PLATFORM,
2333
- previousHash: beforeBuildHash,
2334
- fingerprint
2335
- });
2336
- if (!afterBuild) {
2337
- record.fingerprint = null;
2338
- record.cacheKey = null;
2339
- phase("fingerprint", chalk.yellow("unavailable after Gradle; the build will be installed but not cached"));
2340
- } else {
2341
- if (afterBuild.moved) {
2342
- storeHash = afterBuild.hash;
2343
- storeSources = afterBuild.sources;
2344
- storeKey = buildCacheKey(PLATFORM, afterBuild.hash, buildRunOptions);
2345
- record.fingerprint = storeHash;
2346
- record.cacheKey = storeKey;
2347
- phase("fingerprint", chalk.dim(`${shortHash(beforeBuildHash)} -> ${shortHash(storeHash)} (after Gradle)`));
2348
- }
2349
- if (cachePolicy.write) {
2350
- const assetManifest = release ? captureAssets(root, { variant }) : null;
2351
- try {
2352
- const stored = await storeTieredBuild({
2353
- local: filesystemBuildCapability({
2354
- resolve: resolveCached,
2355
- store: storeCached,
2356
- sources: storeSources,
2357
- assetManifest
2358
- }),
2359
- loadProvider: loadTieredProvider,
2360
- target: {
2361
- projectRoot: root,
2362
- platform: PLATFORM,
2363
- key: storeKey
2364
- },
2365
- sourcePath: apkPath,
2366
- overwrite: !useBuildCache || swapFellBack,
2367
- warn: cacheWarn
2368
- });
2369
- providerUpload = stored.providerUpload;
2370
- providerName = stored.providerName ?? providerName;
2371
- } catch (err) {
2372
- phase("cache", chalk.yellow(`could not store the build: ${err?.message || err}`));
2373
- }
2374
- }
2375
- if (remote) uploadPending = uploadRemoteBuild({
2376
- logWriter: writer,
2377
- provider: remote.provider,
2378
- platform: PLATFORM,
2379
- projectRoot: root,
2380
- fingerprintHash: storeHash,
2381
- buildPath: apkPath,
2382
- runOptions: remoteRunOptions
2383
- });
2384
- }
2385
- }
2386
- } finally {
2387
- releaseHeldLock();
2388
- releaseHeldSlot();
2441
+ const acquiredArtifact = await acquireAndroidArtifact({
2442
+ root,
2443
+ buildLog,
2444
+ writer,
2445
+ settings,
2446
+ isExpo,
2447
+ device,
2448
+ physical,
2449
+ buildPlan,
2450
+ cacheProviderConfig,
2451
+ requestedBuildCache,
2452
+ easBuild,
2453
+ androidPackage,
2454
+ record,
2455
+ maxBuilds: limits.maxBuilds,
2456
+ progress: {
2457
+ phase,
2458
+ out,
2459
+ estimates,
2460
+ stats
2389
2461
  }
2390
- return true;
2391
- }
2392
- if (!easBuild) {
2393
- await prepareCachedArtifact();
2394
- if (!await buildArtifact()) return phaseFailure;
2462
+ }, {
2463
+ deviceAbi,
2464
+ fingerprint,
2465
+ untracked,
2466
+ resolveCached,
2467
+ storeCached,
2468
+ storedAssets,
2469
+ captureAssets,
2470
+ acquireLock,
2471
+ releaseLock,
2472
+ waitForBuild,
2473
+ loadProvider,
2474
+ easAuth,
2475
+ resolveRemoteBuild,
2476
+ uploadRemoteBuild,
2477
+ loadCacheProviderModule,
2478
+ acquireSlot,
2479
+ releaseSlot,
2480
+ needsPrebuildFor,
2481
+ prebuild,
2482
+ build,
2483
+ ccacheFor,
2484
+ swapApk,
2485
+ readState,
2486
+ now
2487
+ });
2488
+ if (!acquiredArtifact.ok) {
2489
+ ccacheActivity = acquiredArtifact.ccache;
2490
+ const { failure } = acquiredArtifact;
2491
+ return fail(failure.code, failure.message, failure.remedy, failure.extra);
2395
2492
  }
2493
+ const { artifact } = acquiredArtifact;
2494
+ const { apkPath } = artifact;
2495
+ ccacheActivity = artifact.ccache;
2496
+ androidPackage = artifact.androidPackage;
2396
2497
  record.appPath = apkPath;
2397
2498
  let leaseHandle = null;
2398
2499
  let stopLeaseSignals = null;
@@ -2459,15 +2560,15 @@ async function runAndroid(options = {}) {
2459
2560
  bootDuration: () => bootDuration,
2460
2561
  apkPath,
2461
2562
  androidPackage,
2462
- swapDir,
2563
+ swapDir: artifact.swapDir,
2463
2564
  record,
2464
- waitedForBuild,
2565
+ waitedForBuild: artifact.waitedForBuild,
2465
2566
  ccache: ccacheActivity,
2466
- uploadPending,
2467
- providerUpload,
2468
- providerName,
2469
- remote,
2470
- abandonedRemote,
2567
+ uploadPending: artifact.uploadPending,
2568
+ providerUpload: artifact.providerUpload,
2569
+ providerName: artifact.providerName,
2570
+ remote: artifact.remote,
2571
+ abandonedRemote: artifact.abandonedRemote,
2471
2572
  started,
2472
2573
  startedAt,
2473
2574
  writer,
@@ -2505,8 +2606,5 @@ async function runAndroid(options = {}) {
2505
2606
  throw error;
2506
2607
  }
2507
2608
  }
2508
- function tail(lines, n = FALLBACK_LINES) {
2509
- return (Array.isArray(lines) ? lines : []).slice(-n);
2510
- }
2511
2609
  //#endregion
2512
2610
  export { NO_DEVICE, NO_FINGERPRINT, NO_METRO, androidDevClientScheme, androidFacts, androidSystemImageSetting, androidVariantSetting, apkDevClientFacts, apkPackage, collectorLogFile, androidCommand as default, displayPath, dumpApkManifest, findAapt, formatDuration, isReleaseVariant, killPreviousCollector, lastBuildRecord, noDeviceDiagnostic, parseXmltree, phaseLine, registerAndroid, resolveSystemImage, resolveVariant, runAndroid, shortHash, startCollector };