@stim-cli/expo-build-cache 1.8.0-rc.1 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +12 -1
  2. package/dist/index.mjs +88 -10
  3. package/package.json +2 -3
package/README.md CHANGED
@@ -28,10 +28,21 @@ For current Expo SDK versions:
28
28
 
29
29
  Expo SDK 53 reads this value under `expo.experiments.buildCacheProvider`.
30
30
 
31
+ Expo builds a debug APK without `--all-arch` only for the ABI of the device it
32
+ targets. The provider keys those builds on that ABI, which it reads with `adb`
33
+ (from `ANDROID_HOME`, `ANDROID_SDK_ROOT`, the default SDK location, or `PATH`)
34
+ when exactly one device is online. With several devices online, or when `adb`
35
+ does not answer, it skips the cache for that run and logs `[build-cache] skip
36
+ android`. `--all-arch` builds have their own entry. These keys match the
37
+ per-ABI builds `stim android` makes by default; a universal debug build that
38
+ `stim android` makes with `optimizations.android.targetAbiOnly: false` is not
39
+ shared with Expo runs.
40
+
31
41
  The provider works without the `stim` npm package. When Stim is available,
32
42
  the provider registers its cache so `stim gc` can report and trim it.
33
43
 
34
- Set `STIM_BUILD_CACHE` to override the cache location.
44
+ Set `STIM_BUILD_CACHE` to an absolute path to override the cache location. A
45
+ relative value is ignored with a warning.
35
46
 
36
47
  See the [cache package documentation](https://stim.appandflow.com/docs/cache-packages)
37
48
  for current setup and cleanup guidance.
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import fs from "node:fs";
2
+ import os from "node:os";
2
3
  import path from "node:path";
3
4
  import { artifactIn, buildCacheKey, buildCacheRoot, registerCache, resolveArtifact, storeArtifact } from "@stim-cli/core";
4
5
  import { execFileSync } from "node:child_process";
@@ -13,6 +14,67 @@ function entryDir(platform, key) {
13
14
  function shortKey(key, fingerprintHash) {
14
15
  return `${String(fingerprintHash).slice(0, 12)}${key.slice(String(fingerprintHash).length)}`;
15
16
  }
17
+ const ANDROID_ABIS = [
18
+ "armeabi-v7a",
19
+ "arm64-v8a",
20
+ "x86",
21
+ "x86_64"
22
+ ];
23
+ function adbPath() {
24
+ const configured = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
25
+ const home = os.homedir();
26
+ const sdk = configured ? configured : [
27
+ path.join(home, "Library", "Android", "sdk"),
28
+ path.join(home, "Android", "Sdk"),
29
+ path.join(home, "Android", "sdk"),
30
+ path.join(home, "AppData", "Local", "Android", "Sdk")
31
+ ].find((candidate) => fs.existsSync(path.join(candidate, "platform-tools")));
32
+ return sdk ? path.join(sdk, "platform-tools", "adb") : "adb";
33
+ }
34
+ function connectedDeviceAbi() {
35
+ try {
36
+ const adb = adbPath();
37
+ const serials = execFileSync(adb, ["devices"], {
38
+ encoding: "utf8",
39
+ timeout: 1e4,
40
+ stdio: [
41
+ "ignore",
42
+ "pipe",
43
+ "ignore"
44
+ ]
45
+ }).split("\n").slice(1).map((line) => line.trim().split(/\s+/)).filter(([, state]) => state === "device").map(([serial]) => serial);
46
+ if (serials.length !== 1) return null;
47
+ return execFileSync(adb, [
48
+ "-s",
49
+ serials[0],
50
+ "shell",
51
+ "getprop",
52
+ "ro.product.cpu.abilist"
53
+ ], {
54
+ encoding: "utf8",
55
+ timeout: 1e4,
56
+ stdio: [
57
+ "ignore",
58
+ "pipe",
59
+ "ignore"
60
+ ]
61
+ }).trim().split(",").find((abi) => ANDROID_ABIS.includes(abi)) ?? null;
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+ function keyOptions(platform, runOptions = {}) {
67
+ if (platform !== "android" || runOptions.abi || runOptions.allArch) return runOptions;
68
+ const parts = (runOptions.variant || "debug").split(/(?=[A-Z])/);
69
+ let buildType = parts.pop();
70
+ if (parts.length > 0 && buildType === "Optimized") buildType = parts.pop() + buildType;
71
+ if (!/^debug(optimized)?$/i.test(buildType)) return runOptions;
72
+ const abi = connectedDeviceAbi();
73
+ return abi ? {
74
+ ...runOptions,
75
+ abi
76
+ } : null;
77
+ }
16
78
  function registerOnce() {
17
79
  const root = cacheRoot();
18
80
  if (registeredDir === root) return;
@@ -27,7 +89,12 @@ function registerOnce() {
27
89
  }
28
90
  async function resolveBuildCache({ platform, fingerprintHash, runOptions }) {
29
91
  registerOnce();
30
- const key = buildCacheKey(platform, fingerprintHash, runOptions);
92
+ const options = keyOptions(platform, runOptions);
93
+ if (!options) {
94
+ console.log(`[build-cache] skip ${platform}: cannot tell which ABI this debug build targets`);
95
+ return null;
96
+ }
97
+ const key = buildCacheKey(platform, fingerprintHash, options);
31
98
  const hit = resolveArtifact(entryDir(platform, key));
32
99
  if (hit) {
33
100
  console.log(`[build-cache] hit ${platform} ${shortKey(key, fingerprintHash)}`);
@@ -39,17 +106,28 @@ async function resolveBuildCache({ platform, fingerprintHash, runOptions }) {
39
106
  async function uploadBuildCache({ platform, fingerprintHash, buildPath, runOptions }) {
40
107
  registerOnce();
41
108
  if (!buildPath || !fs.existsSync(buildPath)) return null;
42
- const key = buildCacheKey(platform, fingerprintHash, runOptions);
109
+ const options = keyOptions(platform, runOptions);
110
+ if (!options) {
111
+ console.log(`[build-cache] skip ${platform}: cannot tell which ABI this debug build targets`);
112
+ return null;
113
+ }
114
+ const key = buildCacheKey(platform, fingerprintHash, options);
43
115
  const dest = entryDir(platform, key);
44
116
  if (artifactIn(dest)) return artifactIn(dest);
45
- const stored = storeArtifact(dest, buildPath, {
46
- runFile: execFileSync,
47
- onRenameError: (staging) => fs.rmSync(staging, {
48
- recursive: true,
49
- force: true
50
- })
51
- });
52
- console.log(`[build-cache] stored ${platform} ${shortKey(key, fingerprintHash)}`);
117
+ let stored;
118
+ try {
119
+ stored = storeArtifact(dest, buildPath, {
120
+ runFile: execFileSync,
121
+ onRenameError: (staging) => fs.rmSync(staging, {
122
+ recursive: true,
123
+ force: true
124
+ })
125
+ });
126
+ } catch (error) {
127
+ console.warn(`[build-cache] could not store ${platform} ${shortKey(key, fingerprintHash)}: ${error.message}`);
128
+ return null;
129
+ }
130
+ if (stored) console.log(`[build-cache] stored ${platform} ${shortKey(key, fingerprintHash)}`);
53
131
  return stored;
54
132
  }
55
133
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stim-cli/expo-build-cache",
3
- "version": "1.8.0-rc.1",
3
+ "version": "1.9.0",
4
4
  "description": "Local Expo build cache provider: installs a cached .app/.apk instead of compiling when no native input changed.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -24,13 +24,12 @@
24
24
  }
25
25
  },
26
26
  "dependencies": {
27
- "@stim-cli/core": "^1.8.0-rc.1"
27
+ "@stim-cli/core": "^1.9.0"
28
28
  },
29
29
  "engines": {
30
30
  "node": ">=22.12.0"
31
31
  },
32
32
  "scripts": {
33
- "test": "node --test test/*.test.js",
34
33
  "build": "tsdown"
35
34
  }
36
35
  }