@stim-cli/expo-build-cache 1.0.0-rc.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Janic Duplessis
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # @stim-cli/expo-build-cache
2
+
3
+ > Commands use `stim`. If it is not installed globally, replace `stim` with
4
+ > `npx stim-cli`.
5
+
6
+ A local Expo build cache provider. When no native input has changed, the CLI
7
+ installs a cached `.app` / `.apk` instead of compiling — which is the difference
8
+ between a JS-only change costing a simulator boot and costing a full native
9
+ build.
10
+
11
+ Local on purpose: a directory under `$HOME` shared by every worktree on the
12
+ machine. No account, no network, and a second worktree building the same commit
13
+ is a hit rather than a second five-minute build.
14
+
15
+ ```bash
16
+ npm i -D @stim-cli/expo-build-cache
17
+ ```
18
+
19
+ Point your Expo config at it. **Which key depends on the SDK, and the wrong one
20
+ is a silent no-op rather than an error:**
21
+
22
+ ```jsonc
23
+ // SDK 54+
24
+ { "expo": { "buildCacheProvider": { "plugin": "@stim-cli/expo-build-cache" } } }
25
+
26
+ // SDK 53 — reads only the experiments key and ignores the top-level one
27
+ { "expo": { "experiments": { "buildCacheProvider": { "plugin": "@stim-cli/expo-build-cache" } } } }
28
+ ```
29
+
30
+ Add a `.fingerprintignore` for anything that changes without changing the build.
31
+ `ios/Podfile.lock` is the usual culprit: pod checksums can embed absolute paths,
32
+ which makes the fingerprint differ per machine and the cache never hit.
33
+
34
+ Watch for `[build-cache] hit` or `miss` in the output. A miss means you changed
35
+ something native — or that you are the first workspace on this commit.
36
+
37
+ ### Housekeeping
38
+
39
+ The cache registers itself with [`stim-cli`](https://www.npmjs.com/package/stim-cli)
40
+ if it is installed, so it can be reported and trimmed:
41
+
42
+ ```bash
43
+ stim gc # what it has grown to (reported on every run)
44
+ stim gc --delete --older-than 30 # drop entries unused for 30 days
45
+ ```
46
+
47
+ stim-cli is an optional peer. Without it the cache works exactly the same; it is
48
+ just invisible to housekeeping.
49
+
50
+ The location can be overridden by `STIM_CLI_BUILD_CACHE`, or machine-wide by `caches.buildCache` in `~/.stim-cli/config.json` (an absolute path; the env var wins). The CLI and this package resolve both identically, so they always share one store.
@@ -0,0 +1,16 @@
1
+ import { BuildRunOptions, buildCacheKey } from "@stim-cli/core";
2
+ //#region index.d.ts
3
+ declare function cacheRoot(): string;
4
+ declare function resolveBuildCache({ platform, fingerprintHash, runOptions }: {
5
+ platform: string;
6
+ fingerprintHash: string;
7
+ runOptions?: BuildRunOptions;
8
+ }): Promise<string | null>;
9
+ declare function uploadBuildCache({ platform, fingerprintHash, buildPath, runOptions }: {
10
+ platform: string;
11
+ fingerprintHash: string;
12
+ buildPath?: string;
13
+ runOptions?: BuildRunOptions;
14
+ }): Promise<string | null>;
15
+ //#endregion
16
+ export { buildCacheKey, cacheRoot, resolveBuildCache, uploadBuildCache };
package/dist/index.mjs ADDED
@@ -0,0 +1,95 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { buildCacheKey, buildCacheRoot, registerCache } from "@stim-cli/core";
4
+ import { execFileSync } from "node:child_process";
5
+ //#region index.ts
6
+ let registeredDir = null;
7
+ function cacheRoot() {
8
+ return buildCacheRoot();
9
+ }
10
+ function entryDir(platform, key) {
11
+ return path.join(cacheRoot(), platform, key);
12
+ }
13
+ function shortKey(key, fingerprintHash) {
14
+ return `${String(fingerprintHash).slice(0, 12)}${key.slice(String(fingerprintHash).length)}`;
15
+ }
16
+ function artifactIn(dir) {
17
+ if (!fs.existsSync(dir)) return null;
18
+ let found;
19
+ try {
20
+ found = fs.readdirSync(dir).find((f) => f.endsWith(".app") || f.endsWith(".apk"));
21
+ } catch {
22
+ return null;
23
+ }
24
+ return found ? path.join(dir, found) : null;
25
+ }
26
+ function registerOnce() {
27
+ const root = cacheRoot();
28
+ if (registeredDir === root) return;
29
+ registeredDir = root;
30
+ registerCache({
31
+ dir: root,
32
+ name: "Expo build cache",
33
+ prune: "entries",
34
+ entriesDepth: 2,
35
+ note: "built .app/.apk keyed on the native fingerprint"
36
+ });
37
+ }
38
+ async function resolveBuildCache({ platform, fingerprintHash, runOptions }) {
39
+ registerOnce();
40
+ const key = buildCacheKey(platform, fingerprintHash, runOptions);
41
+ const hit = artifactIn(entryDir(platform, key));
42
+ if (hit) {
43
+ console.log(`[build-cache] hit ${platform} ${shortKey(key, fingerprintHash)}`);
44
+ try {
45
+ fs.utimesSync(path.dirname(hit), /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date());
46
+ } catch {}
47
+ return hit;
48
+ }
49
+ console.log(`[build-cache] miss ${platform} ${shortKey(key, fingerprintHash)}`);
50
+ return null;
51
+ }
52
+ async function uploadBuildCache({ platform, fingerprintHash, buildPath, runOptions }) {
53
+ registerOnce();
54
+ if (!buildPath || !fs.existsSync(buildPath)) return null;
55
+ const key = buildCacheKey(platform, fingerprintHash, runOptions);
56
+ const dest = entryDir(platform, key);
57
+ if (artifactIn(dest)) return artifactIn(dest);
58
+ const staging = `${dest}.staging-${process.pid}`;
59
+ fs.rmSync(staging, {
60
+ recursive: true,
61
+ force: true
62
+ });
63
+ fs.mkdirSync(staging, { recursive: true });
64
+ try {
65
+ execFileSync("cp", [
66
+ "-c",
67
+ "-R",
68
+ buildPath,
69
+ path.join(staging, path.basename(buildPath))
70
+ ]);
71
+ } catch {
72
+ execFileSync("cp", [
73
+ "-R",
74
+ buildPath,
75
+ path.join(staging, path.basename(buildPath))
76
+ ]);
77
+ }
78
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
79
+ fs.rmSync(dest, {
80
+ recursive: true,
81
+ force: true
82
+ });
83
+ try {
84
+ fs.renameSync(staging, dest);
85
+ } catch {
86
+ fs.rmSync(staging, {
87
+ recursive: true,
88
+ force: true
89
+ });
90
+ }
91
+ console.log(`[build-cache] stored ${platform} ${shortKey(key, fingerprintHash)}`);
92
+ return artifactIn(dest);
93
+ }
94
+ //#endregion
95
+ export { buildCacheKey, cacheRoot, resolveBuildCache, uploadBuildCache };
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@stim-cli/expo-build-cache",
3
+ "version": "1.0.0-rc.0",
4
+ "description": "Local Expo build cache provider: installs a cached .app/.apk instead of compiling when no native input changed.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/appandflow/stim-cli.git",
9
+ "directory": "packages/expo-build-cache"
10
+ },
11
+ "files": [
12
+ "dist",
13
+ "README.md",
14
+ "LICENSE"
15
+ ],
16
+ "type": "module",
17
+ "main": "dist/index.mjs",
18
+ "types": "dist/index.d.mts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.mts",
22
+ "module-sync": "./dist/index.mjs",
23
+ "default": "./dist/index.mjs"
24
+ }
25
+ },
26
+ "dependencies": {
27
+ "@stim-cli/core": "^1.0.0-rc.0"
28
+ },
29
+ "engines": {
30
+ "node": "^20.19.4 || >=22.12.0"
31
+ },
32
+ "scripts": {
33
+ "test": "node --test test/*.test.js",
34
+ "build": "tsdown"
35
+ }
36
+ }