@tamagui/native-ci 1.139.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 (81) hide show
  1. package/README.md +320 -0
  2. package/action.yml +98 -0
  3. package/actions/fingerprint/action.yml +110 -0
  4. package/actions/test-detox-android/action.yml +70 -0
  5. package/actions/test-detox-ios/action.yml +66 -0
  6. package/dist/cache.js +71 -0
  7. package/dist/cache.js.map +6 -0
  8. package/dist/cache.mjs +73 -0
  9. package/dist/cache.mjs.map +1 -0
  10. package/dist/cli.js +275 -0
  11. package/dist/cli.js.map +6 -0
  12. package/dist/cli.mjs +306 -0
  13. package/dist/cli.mjs.map +1 -0
  14. package/dist/constants.js +12 -0
  15. package/dist/constants.js.map +6 -0
  16. package/dist/constants.mjs +10 -0
  17. package/dist/constants.mjs.map +1 -0
  18. package/dist/deps.js +44 -0
  19. package/dist/deps.js.map +6 -0
  20. package/dist/deps.mjs +53 -0
  21. package/dist/deps.mjs.map +1 -0
  22. package/dist/detox.js +49 -0
  23. package/dist/detox.js.map +6 -0
  24. package/dist/detox.mjs +55 -0
  25. package/dist/detox.mjs.map +1 -0
  26. package/dist/fingerprint.js +43 -0
  27. package/dist/fingerprint.js.map +6 -0
  28. package/dist/fingerprint.mjs +40 -0
  29. package/dist/fingerprint.mjs.map +1 -0
  30. package/dist/index.js +90 -0
  31. package/dist/index.js.map +6 -0
  32. package/dist/index.mjs +11 -0
  33. package/dist/index.mjs.map +1 -0
  34. package/dist/metro.js +79 -0
  35. package/dist/metro.js.map +6 -0
  36. package/dist/metro.mjs +75 -0
  37. package/dist/metro.mjs.map +1 -0
  38. package/dist/runner.js +73 -0
  39. package/dist/runner.js.map +6 -0
  40. package/dist/runner.mjs +73 -0
  41. package/dist/runner.mjs.map +1 -0
  42. package/package.json +50 -0
  43. package/src/android.ts +103 -0
  44. package/src/cache.ts +144 -0
  45. package/src/cli.ts +513 -0
  46. package/src/constants.ts +30 -0
  47. package/src/deps.ts +109 -0
  48. package/src/detox.ts +102 -0
  49. package/src/fingerprint.ts +77 -0
  50. package/src/index.ts +86 -0
  51. package/src/ios.ts +38 -0
  52. package/src/metro.ts +157 -0
  53. package/src/run-detox-android.ts +49 -0
  54. package/src/run-detox-ios.ts +40 -0
  55. package/src/runner.ts +123 -0
  56. package/types/android.d.ts +32 -0
  57. package/types/android.d.ts.map +1 -0
  58. package/types/cache.d.ts +41 -0
  59. package/types/cache.d.ts.map +1 -0
  60. package/types/cli.d.ts +11 -0
  61. package/types/cli.d.ts.map +1 -0
  62. package/types/constants.d.ts +18 -0
  63. package/types/constants.d.ts.map +1 -0
  64. package/types/deps.d.ts +32 -0
  65. package/types/deps.d.ts.map +1 -0
  66. package/types/detox.d.ts +39 -0
  67. package/types/detox.d.ts.map +1 -0
  68. package/types/fingerprint.d.ts +21 -0
  69. package/types/fingerprint.d.ts.map +1 -0
  70. package/types/index.d.ts +16 -0
  71. package/types/index.d.ts.map +1 -0
  72. package/types/ios.d.ts +18 -0
  73. package/types/ios.d.ts.map +1 -0
  74. package/types/metro.d.ts +51 -0
  75. package/types/metro.d.ts.map +1 -0
  76. package/types/run-detox-android.d.ts +15 -0
  77. package/types/run-detox-android.d.ts.map +1 -0
  78. package/types/run-detox-ios.d.ts +14 -0
  79. package/types/run-detox-ios.d.ts.map +1 -0
  80. package/types/runner.d.ts +35 -0
  81. package/types/runner.d.ts.map +1 -0
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/fingerprint.ts"],
4
+ "mappings": "AAAA,SAAS,gBAAsC;AAC/C,SAAS,kBAAkB;AAC3B,SAAS,YAAY,oBAAoB;AACzC,SAAS,YAAY;AAkBrB,eAAsB,oBACpB,SAC4B;AAC5B,QAAM,EAAE,UAAU,cAAc,QAAQ,IAAI,GAAG,QAAQ,GAAM,IAAI,SAG3D,cAAc,KAAK,aAAa,UAAU;AAChD,MAAI,CAAC,WAAW,WAAW;AACzB,UAAM,IAAI,MAAM,wBAAwB,WAAW,4BAA4B;AAGjF,QAAM,cAA+B;AAAA,IACnC,KAAK;AAAA,IACL,UAAU;AAAA,IACV,OAAO,QAAQ,YAAY,CAAC,QAAQ,QAAQ,MAAM;AAAA,EACpD;AAEA,MAAI;AACF,UAAM,SAAS;AAAA,MACb,yDAAyD,QAAQ;AAAA,MACjE;AAAA,IACF,GAEM,SAAS,KAAK,MAAM,MAAM;AAEhC,WAAO;AAAA,MACL,MAAM,OAAO;AAAA,MACb,SAAS,OAAO,WAAW,CAAC;AAAA,IAC9B;AAAA,EACF,SAAS,OAAO;AACd,UAAM,MAAM;AACZ,UAAM,IAAI;AAAA,MACR,sBAAsB,QAAQ,iBAAiB,IAAI,OAAO,GAAG,IAAI,SAAS;AAAA,EAAK,IAAI,MAAM,KAAK,EAAE;AAAA,IAClG;AAAA,EACF;AACF;AAMO,SAAS,2BACd,OACA,cAAsB,QAAQ,IAAI,GAC1B;AACR,QAAM,OAAO,WAAW,QAAQ;AAEhC,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAW,KAAK,aAAa,IAAI;AACvC,IAAI,WAAW,QAAQ,KACrB,KAAK,OAAO,aAAa,QAAQ,CAAC;AAAA,EAEtC;AAEA,SAAO,KAAK,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AACvC;",
5
+ "names": []
6
+ }
@@ -0,0 +1,40 @@
1
+ import { execSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import { existsSync, readFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ async function generateFingerprint(options) {
6
+ const {
7
+ platform,
8
+ projectRoot = process.cwd(),
9
+ debug = !1
10
+ } = options,
11
+ appJsonPath = join(projectRoot, "app.json");
12
+ if (!existsSync(appJsonPath)) throw new Error(`No app.json found at ${projectRoot}. Is this an Expo project?`);
13
+ const execOptions = {
14
+ cwd: projectRoot,
15
+ encoding: "utf-8",
16
+ stdio: debug ? "inherit" : ["pipe", "pipe", "pipe"]
17
+ };
18
+ try {
19
+ const result = execSync(`npx @expo/fingerprint fingerprint:generate --platform ${platform}`, execOptions),
20
+ parsed = JSON.parse(result);
21
+ return {
22
+ hash: parsed.hash,
23
+ sources: parsed.sources || []
24
+ };
25
+ } catch (error) {
26
+ const err = error;
27
+ throw new Error(`Failed to generate ${platform} fingerprint: ${err.message}${err.stderr ? `
28
+ ${err.stderr}` : ""}`);
29
+ }
30
+ }
31
+ function generatePreFingerprintHash(files, projectRoot = process.cwd()) {
32
+ const hash = createHash("sha256");
33
+ for (const file of files) {
34
+ const filePath = join(projectRoot, file);
35
+ existsSync(filePath) && hash.update(readFileSync(filePath));
36
+ }
37
+ return hash.digest("hex").slice(0, 16);
38
+ }
39
+ export { generateFingerprint, generatePreFingerprintHash };
40
+ //# sourceMappingURL=fingerprint.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["execSync","createHash","existsSync","readFileSync","join","generateFingerprint","options","platform","projectRoot","process","cwd","debug","appJsonPath","Error","execOptions","encoding","stdio","result","parsed","JSON","parse","hash","sources","error","err","message","stderr","generatePreFingerprintHash","files","file","filePath","update","digest","slice"],"sources":["../src/fingerprint.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,QAAA,QAAsC;AAC/C,SAASC,UAAA,QAAkB;AAC3B,SAASC,UAAA,EAAYC,YAAA,QAAoB;AACzC,SAASC,IAAA,QAAY;AAkBrB,eAAsBC,oBACpBC,OAAA,EAC4B;EAC5B,MAAM;MAAEC,QAAA;MAAUC,WAAA,GAAcC,OAAA,CAAQC,GAAA,CAAI;MAAGC,KAAA,GAAQ;IAAM,IAAIL,OAAA;IAG3DM,WAAA,GAAcR,IAAA,CAAKI,WAAA,EAAa,UAAU;EAChD,IAAI,CAACN,UAAA,CAAWU,WAAW,GACzB,MAAM,IAAIC,KAAA,CAAM,wBAAwBL,WAAW,4BAA4B;EAGjF,MAAMM,WAAA,GAA+B;IACnCJ,GAAA,EAAKF,WAAA;IACLO,QAAA,EAAU;IACVC,KAAA,EAAOL,KAAA,GAAQ,YAAY,CAAC,QAAQ,QAAQ,MAAM;EACpD;EAEA,IAAI;IACF,MAAMM,MAAA,GAASjB,QAAA,CACb,yDAAyDO,QAAQ,IACjEO,WACF;MAEMI,MAAA,GAASC,IAAA,CAAKC,KAAA,CAAMH,MAAM;IAEhC,OAAO;MACLI,IAAA,EAAMH,MAAA,CAAOG,IAAA;MACbC,OAAA,EAASJ,MAAA,CAAOI,OAAA,IAAW;IAC7B;EACF,SAASC,KAAA,EAAO;IACd,MAAMC,GAAA,GAAMD,KAAA;IACZ,MAAM,IAAIV,KAAA,CACR,sBAAsBN,QAAQ,iBAAiBiB,GAAA,CAAIC,OAAO,GAAGD,GAAA,CAAIE,MAAA,GAAS;AAAA,EAAKF,GAAA,CAAIE,MAAM,KAAK,EAAE,EAClG;EACF;AACF;AAMO,SAASC,2BACdC,KAAA,EACApB,WAAA,GAAsBC,OAAA,CAAQC,GAAA,CAAI,GAC1B;EACR,MAAMW,IAAA,GAAOpB,UAAA,CAAW,QAAQ;EAEhC,WAAW4B,IAAA,IAAQD,KAAA,EAAO;IACxB,MAAME,QAAA,GAAW1B,IAAA,CAAKI,WAAA,EAAaqB,IAAI;IACnC3B,UAAA,CAAW4B,QAAQ,KACrBT,IAAA,CAAKU,MAAA,CAAO5B,YAAA,CAAa2B,QAAQ,CAAC;EAEtC;EAEA,OAAOT,IAAA,CAAKW,MAAA,CAAO,KAAK,EAAEC,KAAA,CAAM,GAAG,EAAE;AACvC","ignoreList":[]}
package/dist/index.js ADDED
@@ -0,0 +1,90 @@
1
+ import {
2
+ METRO_HOST,
3
+ METRO_PORT,
4
+ METRO_URL,
5
+ DETOX_SERVER_PORT,
6
+ DEFAULT_METRO_WAIT_ATTEMPTS,
7
+ DEFAULT_METRO_WAIT_INTERVAL_MS,
8
+ DEFAULT_METRO_TIMEOUT_MS,
9
+ DEFAULT_KV_TTL_SECONDS
10
+ } from "./constants";
11
+ import {
12
+ generateFingerprint,
13
+ generatePreFingerprintHash
14
+ } from "./fingerprint";
15
+ import {
16
+ createCacheKey,
17
+ saveFingerprintToKV,
18
+ getFingerprintFromKV,
19
+ extendKVTTL,
20
+ saveCache,
21
+ loadCache
22
+ } from "./cache";
23
+ import {
24
+ runWithCache,
25
+ setGitHubOutput,
26
+ isGitHubActions,
27
+ isCI
28
+ } from "./runner";
29
+ import {
30
+ waitForMetro,
31
+ prewarmBundle,
32
+ startMetro,
33
+ setupSignalHandlers,
34
+ withMetro
35
+ } from "./metro";
36
+ import {
37
+ parseDetoxArgs,
38
+ buildDetoxArgs,
39
+ runDetoxTests
40
+ } from "./detox";
41
+ import { waitForDevice, setupAdbReverse, setupAndroidDevice, ensureAndroidFolder } from "./android";
42
+ import { ensureIOSFolder } from "./ios";
43
+ import {
44
+ checkDeps,
45
+ ensureIosDeps,
46
+ ensureAndroidDeps,
47
+ ensureMaestro,
48
+ printDepsStatus
49
+ } from "./deps";
50
+ export {
51
+ DEFAULT_KV_TTL_SECONDS,
52
+ DEFAULT_METRO_TIMEOUT_MS,
53
+ DEFAULT_METRO_WAIT_ATTEMPTS,
54
+ DEFAULT_METRO_WAIT_INTERVAL_MS,
55
+ DETOX_SERVER_PORT,
56
+ METRO_HOST,
57
+ METRO_PORT,
58
+ METRO_URL,
59
+ buildDetoxArgs,
60
+ checkDeps,
61
+ createCacheKey,
62
+ ensureAndroidDeps,
63
+ ensureAndroidFolder,
64
+ ensureIOSFolder,
65
+ ensureIosDeps,
66
+ ensureMaestro,
67
+ extendKVTTL,
68
+ generateFingerprint,
69
+ generatePreFingerprintHash,
70
+ getFingerprintFromKV,
71
+ isCI,
72
+ isGitHubActions,
73
+ loadCache,
74
+ parseDetoxArgs,
75
+ prewarmBundle,
76
+ printDepsStatus,
77
+ runDetoxTests,
78
+ runWithCache,
79
+ saveCache,
80
+ saveFingerprintToKV,
81
+ setGitHubOutput,
82
+ setupAdbReverse,
83
+ setupAndroidDevice,
84
+ setupSignalHandlers,
85
+ startMetro,
86
+ waitForDevice,
87
+ waitForMetro,
88
+ withMetro
89
+ };
90
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.ts"],
4
+ "mappings": "AAQA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAGP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAGP,SAAS,eAAe,iBAAiB,oBAAoB,2BAA2B;AAGxF,SAAS,uBAAuB;AAGhC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;",
5
+ "names": []
6
+ }
package/dist/index.mjs ADDED
@@ -0,0 +1,11 @@
1
+ import { METRO_HOST, METRO_PORT, METRO_URL, DETOX_SERVER_PORT, DEFAULT_METRO_WAIT_ATTEMPTS, DEFAULT_METRO_WAIT_INTERVAL_MS, DEFAULT_METRO_TIMEOUT_MS, DEFAULT_KV_TTL_SECONDS } from "./constants.mjs";
2
+ import { generateFingerprint, generatePreFingerprintHash } from "./fingerprint.mjs";
3
+ import { createCacheKey, saveFingerprintToKV, getFingerprintFromKV, extendKVTTL, saveCache, loadCache } from "./cache.mjs";
4
+ import { runWithCache, setGitHubOutput, isGitHubActions, isCI } from "./runner.mjs";
5
+ import { waitForMetro, prewarmBundle, startMetro, setupSignalHandlers, withMetro } from "./metro.mjs";
6
+ import { parseDetoxArgs, buildDetoxArgs, runDetoxTests } from "./detox.mjs";
7
+ import { waitForDevice, setupAdbReverse, setupAndroidDevice, ensureAndroidFolder } from "./android";
8
+ import { ensureIOSFolder } from "./ios";
9
+ import { checkDeps, ensureIosDeps, ensureAndroidDeps, ensureMaestro, printDepsStatus } from "./deps.mjs";
10
+ export { DEFAULT_KV_TTL_SECONDS, DEFAULT_METRO_TIMEOUT_MS, DEFAULT_METRO_WAIT_ATTEMPTS, DEFAULT_METRO_WAIT_INTERVAL_MS, DETOX_SERVER_PORT, METRO_HOST, METRO_PORT, METRO_URL, buildDetoxArgs, checkDeps, createCacheKey, ensureAndroidDeps, ensureAndroidFolder, ensureIOSFolder, ensureIosDeps, ensureMaestro, extendKVTTL, generateFingerprint, generatePreFingerprintHash, getFingerprintFromKV, isCI, isGitHubActions, loadCache, parseDetoxArgs, prewarmBundle, printDepsStatus, runDetoxTests, runWithCache, saveCache, saveFingerprintToKV, setGitHubOutput, setupAdbReverse, setupAndroidDevice, setupSignalHandlers, startMetro, waitForDevice, waitForMetro, withMetro };
11
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["METRO_HOST","METRO_PORT","METRO_URL","DETOX_SERVER_PORT","DEFAULT_METRO_WAIT_ATTEMPTS","DEFAULT_METRO_WAIT_INTERVAL_MS","DEFAULT_METRO_TIMEOUT_MS","DEFAULT_KV_TTL_SECONDS","generateFingerprint","generatePreFingerprintHash","createCacheKey","saveFingerprintToKV","getFingerprintFromKV","extendKVTTL","saveCache","loadCache","runWithCache","setGitHubOutput","isGitHubActions","isCI","waitForMetro","prewarmBundle","startMetro","setupSignalHandlers","withMetro","parseDetoxArgs","buildDetoxArgs","runDetoxTests","waitForDevice","setupAdbReverse","setupAndroidDevice","ensureAndroidFolder","ensureIOSFolder","checkDeps","ensureIosDeps","ensureAndroidDeps","ensureMaestro","printDepsStatus"],"sources":["../src/index.ts"],"sourcesContent":[null],"mappings":"AAQA,SACEA,UAAA,EACAC,UAAA,EACAC,SAAA,EACAC,iBAAA,EACAC,2BAAA,EACAC,8BAAA,EACAC,wBAAA,EACAC,sBAAA,QAGK;AAGP,SACEC,mBAAA,EACAC,0BAAA,QAGK;AAGP,SACEC,cAAA,EACAC,mBAAA,EACAC,oBAAA,EACAC,WAAA,EACAC,SAAA,EACAC,SAAA,QAIK;AAGP,SACEC,YAAA,EACAC,eAAA,EACAC,eAAA,EACAC,IAAA,QAGK;AAGP,SACEC,YAAA,EACAC,aAAA,EACAC,UAAA,EACAC,mBAAA,EACAC,SAAA,QAGK;AAGP,SACEC,cAAA,EACAC,cAAA,EACAC,aAAA,QAEK;AAGP,SAASC,aAAA,EAAeC,eAAA,EAAiBC,kBAAA,EAAoBC,mBAAA,QAA2B;AAGxF,SAASC,eAAA,QAAuB;AAGhC,SACEC,SAAA,EACAC,aAAA,EACAC,iBAAA,EACAC,aAAA,EACAC,eAAA,QAEK","ignoreList":[]}
package/dist/metro.js ADDED
@@ -0,0 +1,79 @@
1
+ import {
2
+ METRO_URL,
3
+ DEFAULT_METRO_WAIT_ATTEMPTS,
4
+ DEFAULT_METRO_WAIT_INTERVAL_MS
5
+ } from "./constants";
6
+ async function waitForMetro(options) {
7
+ const {
8
+ platform,
9
+ maxAttempts = DEFAULT_METRO_WAIT_ATTEMPTS,
10
+ intervalMs = DEFAULT_METRO_WAIT_INTERVAL_MS
11
+ } = options;
12
+ console.info("Waiting for Metro to start...");
13
+ for (let i = 1; i <= maxAttempts; i++) {
14
+ try {
15
+ if ((await fetch(`${METRO_URL}/`, {
16
+ headers: { "Expo-Platform": platform }
17
+ })).ok)
18
+ return console.info("Metro is responding!"), !0;
19
+ } catch {
20
+ }
21
+ console.info(`Waiting for Metro... (${i}/${maxAttempts})`), await Bun.sleep(intervalMs);
22
+ }
23
+ return !1;
24
+ }
25
+ async function prewarmBundle(platform) {
26
+ console.info("Pre-warming bundle...");
27
+ try {
28
+ const response = await fetch(`${METRO_URL}/`, {
29
+ headers: { "Expo-Platform": platform }
30
+ });
31
+ if (response.ok) {
32
+ const bundleUrl = (await response.json())?.launchAsset?.url;
33
+ bundleUrl ? (console.info(`Fetching bundle from: ${bundleUrl}`), await fetch(bundleUrl), console.info("Bundle pre-warmed!")) : console.info("No bundle URL found in manifest, skipping pre-warm");
34
+ }
35
+ } catch {
36
+ console.info("Bundle pre-warm completed (with error, continuing)");
37
+ }
38
+ }
39
+ function startMetro() {
40
+ console.info(`
41
+ --- Starting Metro bundler ---`);
42
+ const proc = Bun.spawn(["yarn", "expo", "start", "--dev-client", "--offline", "--clear"], {
43
+ env: { ...process.env, EXPO_NO_TELEMETRY: "true" },
44
+ stdout: "inherit",
45
+ stderr: "inherit"
46
+ });
47
+ return {
48
+ proc,
49
+ kill: () => {
50
+ proc.kill(), console.info("Metro stopped");
51
+ }
52
+ };
53
+ }
54
+ function setupSignalHandlers(metro) {
55
+ const cleanup = (signal) => {
56
+ console.info(`
57
+ Received ${signal}, cleaning up...`), metro.kill(), process.exit(signal === "SIGINT" ? 130 : 143);
58
+ };
59
+ process.on("SIGINT", () => cleanup("SIGINT")), process.on("SIGTERM", () => cleanup("SIGTERM"));
60
+ }
61
+ async function withMetro(platform, fn) {
62
+ const metro = startMetro();
63
+ setupSignalHandlers(metro);
64
+ try {
65
+ if (!await waitForMetro({ platform }))
66
+ throw new Error("Metro failed to start within timeout");
67
+ return await prewarmBundle(platform), await fn();
68
+ } finally {
69
+ metro.kill();
70
+ }
71
+ }
72
+ export {
73
+ prewarmBundle,
74
+ setupSignalHandlers,
75
+ startMetro,
76
+ waitForMetro,
77
+ withMetro
78
+ };
79
+ //# sourceMappingURL=metro.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/metro.ts"],
4
+ "mappings": "AAQA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAuBP,eAAsB,aAAa,SAAyC;AAC1E,QAAM;AAAA,IACJ;AAAA,IACA,cAAc;AAAA,IACd,aAAa;AAAA,EACf,IAAI;AAEJ,UAAQ,KAAK,+BAA+B;AAE5C,WAAS,IAAI,GAAG,KAAK,aAAa,KAAK;AACrC,QAAI;AAIF,WAHiB,MAAM,MAAM,GAAG,SAAS,KAAK;AAAA,QAC5C,SAAS,EAAE,iBAAiB,SAAS;AAAA,MACvC,CAAC,GACY;AACX,uBAAQ,KAAK,sBAAsB,GAC5B;AAAA,IAEX,QAAQ;AAAA,IAER;AACA,YAAQ,KAAK,yBAAyB,CAAC,IAAI,WAAW,GAAG,GACzD,MAAM,IAAI,MAAM,UAAU;AAAA,EAC5B;AAEA,SAAO;AACT;AAMA,eAAsB,cAAc,UAAmC;AACrE,UAAQ,KAAK,uBAAuB;AAEpC,MAAI;AACF,UAAM,WAAW,MAAM,MAAM,GAAG,SAAS,KAAK;AAAA,MAC5C,SAAS,EAAE,iBAAiB,SAAS;AAAA,IACvC,CAAC;AAED,QAAI,SAAS,IAAI;AAEf,YAAM,aADY,MAAM,SAAS,KAAK,IACV,aAAa;AAEzC,MAAI,aACF,QAAQ,KAAK,yBAAyB,SAAS,EAAE,GACjD,MAAM,MAAM,SAAS,GACrB,QAAQ,KAAK,oBAAoB,KAEjC,QAAQ,KAAK,oDAAoD;AAAA,IAErE;AAAA,EACF,QAAgB;AAEd,YAAQ,KAAK,oDAAoD;AAAA,EACnE;AACF;AAOO,SAAS,aAA2B;AACzC,UAAQ,KAAK;AAAA,+BAAkC;AAI/C,QAAM,OAAO,IAAI,MAAM,CAAC,QAAQ,QAAQ,SAAS,gBAAgB,aAAa,SAAS,GAAG;AAAA,IACxF,KAAK,EAAE,GAAG,QAAQ,KAAK,mBAAmB,OAAO;AAAA,IACjD,QAAQ;AAAA,IACR,QAAQ;AAAA,EACV,CAAC;AAED,SAAO;AAAA,IACL;AAAA,IACA,MAAM,MAAM;AACV,WAAK,KAAK,GACV,QAAQ,KAAK,eAAe;AAAA,IAC9B;AAAA,EACF;AACF;AAMO,SAAS,oBAAoB,OAA2B;AAC7D,QAAM,UAAU,CAAC,WAAmB;AAClC,YAAQ,KAAK;AAAA,WAAc,MAAM,kBAAkB,GACnD,MAAM,KAAK,GACX,QAAQ,KAAK,WAAW,WAAW,MAAM,GAAG;AAAA,EAC9C;AAEA,UAAQ,GAAG,UAAU,MAAM,QAAQ,QAAQ,CAAC,GAC5C,QAAQ,GAAG,WAAW,MAAM,QAAQ,SAAS,CAAC;AAChD;AAOA,eAAsB,UAAa,UAAoB,IAAkC;AACvF,QAAM,QAAQ,WAAW;AACzB,sBAAoB,KAAK;AAEzB,MAAI;AAEF,QAAI,CADe,MAAM,aAAa,EAAE,SAAS,CAAC;AAEhD,YAAM,IAAI,MAAM,sCAAsC;AAGxD,iBAAM,cAAc,QAAQ,GAErB,MAAM,GAAG;AAAA,EAClB,UAAE;AACA,UAAM,KAAK;AAAA,EACb;AACF;",
5
+ "names": []
6
+ }
package/dist/metro.mjs ADDED
@@ -0,0 +1,75 @@
1
+ import { METRO_URL, DEFAULT_METRO_WAIT_ATTEMPTS, DEFAULT_METRO_WAIT_INTERVAL_MS } from "./constants.mjs";
2
+ async function waitForMetro(options) {
3
+ const {
4
+ platform,
5
+ maxAttempts = DEFAULT_METRO_WAIT_ATTEMPTS,
6
+ intervalMs = DEFAULT_METRO_WAIT_INTERVAL_MS
7
+ } = options;
8
+ console.info("Waiting for Metro to start...");
9
+ for (let i = 1; i <= maxAttempts; i++) {
10
+ try {
11
+ if ((await fetch(`${METRO_URL}/`, {
12
+ headers: {
13
+ "Expo-Platform": platform
14
+ }
15
+ })).ok) return console.info("Metro is responding!"), !0;
16
+ } catch {}
17
+ console.info(`Waiting for Metro... (${i}/${maxAttempts})`), await Bun.sleep(intervalMs);
18
+ }
19
+ return !1;
20
+ }
21
+ async function prewarmBundle(platform) {
22
+ console.info("Pre-warming bundle...");
23
+ try {
24
+ const response = await fetch(`${METRO_URL}/`, {
25
+ headers: {
26
+ "Expo-Platform": platform
27
+ }
28
+ });
29
+ if (response.ok) {
30
+ const bundleUrl = (await response.json())?.launchAsset?.url;
31
+ bundleUrl ? (console.info(`Fetching bundle from: ${bundleUrl}`), await fetch(bundleUrl), console.info("Bundle pre-warmed!")) : console.info("No bundle URL found in manifest, skipping pre-warm");
32
+ }
33
+ } catch {
34
+ console.info("Bundle pre-warm completed (with error, continuing)");
35
+ }
36
+ }
37
+ function startMetro() {
38
+ console.info(`
39
+ --- Starting Metro bundler ---`);
40
+ const proc = Bun.spawn(["yarn", "expo", "start", "--dev-client", "--offline", "--clear"], {
41
+ env: {
42
+ ...process.env,
43
+ EXPO_NO_TELEMETRY: "true"
44
+ },
45
+ stdout: "inherit",
46
+ stderr: "inherit"
47
+ });
48
+ return {
49
+ proc,
50
+ kill: () => {
51
+ proc.kill(), console.info("Metro stopped");
52
+ }
53
+ };
54
+ }
55
+ function setupSignalHandlers(metro) {
56
+ const cleanup = signal => {
57
+ console.info(`
58
+ Received ${signal}, cleaning up...`), metro.kill(), process.exit(signal === "SIGINT" ? 130 : 143);
59
+ };
60
+ process.on("SIGINT", () => cleanup("SIGINT")), process.on("SIGTERM", () => cleanup("SIGTERM"));
61
+ }
62
+ async function withMetro(platform, fn) {
63
+ const metro = startMetro();
64
+ setupSignalHandlers(metro);
65
+ try {
66
+ if (!(await waitForMetro({
67
+ platform
68
+ }))) throw new Error("Metro failed to start within timeout");
69
+ return await prewarmBundle(platform), await fn();
70
+ } finally {
71
+ metro.kill();
72
+ }
73
+ }
74
+ export { prewarmBundle, setupSignalHandlers, startMetro, waitForMetro, withMetro };
75
+ //# sourceMappingURL=metro.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["METRO_URL","DEFAULT_METRO_WAIT_ATTEMPTS","DEFAULT_METRO_WAIT_INTERVAL_MS","waitForMetro","options","platform","maxAttempts","intervalMs","console","info","i","fetch","headers","ok","Bun","sleep","prewarmBundle","response","bundleUrl","json","launchAsset","url","startMetro","proc","spawn","env","process","EXPO_NO_TELEMETRY","stdout","stderr","kill","setupSignalHandlers","metro","cleanup","signal","exit","on","withMetro","fn","Error"],"sources":["../src/metro.ts"],"sourcesContent":[null],"mappings":"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","ignoreList":[]}
package/dist/runner.js ADDED
@@ -0,0 +1,73 @@
1
+ import { execSync } from "node:child_process";
2
+ import { appendFileSync } from "node:fs";
3
+ import { createCacheKey, loadCache, saveCache } from "./cache";
4
+ import { generateFingerprint } from "./fingerprint";
5
+ async function runWithCache(options) {
6
+ const {
7
+ platform,
8
+ buildCommand,
9
+ outputPaths,
10
+ projectRoot = process.cwd(),
11
+ cachePrefix = "native-build",
12
+ debug = !1
13
+ } = options, log = debug ? console.log : () => {
14
+ };
15
+ log(`Generating ${platform} fingerprint...`);
16
+ const { hash: fingerprint } = await generateFingerprint({
17
+ platform,
18
+ projectRoot,
19
+ debug
20
+ });
21
+ log(`Fingerprint: ${fingerprint}`);
22
+ const cacheKey = createCacheKey({ platform, fingerprint, prefix: cachePrefix });
23
+ log(`Cache key: ${cacheKey}`);
24
+ const cached = loadCache(cacheKey);
25
+ if (cached && cached.fingerprint === fingerprint)
26
+ return log("Cache hit! Skipping build."), {
27
+ cacheHit: !0,
28
+ fingerprint,
29
+ cacheKey,
30
+ outputPaths
31
+ };
32
+ log(`Running build: ${buildCommand}`);
33
+ const execOptions = {
34
+ cwd: projectRoot,
35
+ stdio: debug ? "inherit" : ["pipe", "pipe", "pipe"]
36
+ };
37
+ try {
38
+ execSync(buildCommand, execOptions);
39
+ } catch (error) {
40
+ const err = error;
41
+ throw new Error(`Build failed: ${err.message}${err.stderr ? `
42
+ ${err.stderr}` : ""}`);
43
+ }
44
+ return saveCache(cacheKey, {
45
+ fingerprint,
46
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
47
+ platform,
48
+ outputPaths
49
+ }), {
50
+ cacheHit: !1,
51
+ fingerprint,
52
+ cacheKey,
53
+ outputPaths
54
+ };
55
+ }
56
+ function setGitHubOutput(name, value) {
57
+ const outputFile = process.env.GITHUB_OUTPUT;
58
+ outputFile ? appendFileSync(outputFile, `${name}=${value}
59
+ `) : console.info(`[GitHub Output] ${name}=${value}`);
60
+ }
61
+ function isGitHubActions() {
62
+ return !!process.env.GITHUB_ACTIONS;
63
+ }
64
+ function isCI() {
65
+ return !!process.env.CI;
66
+ }
67
+ export {
68
+ isCI,
69
+ isGitHubActions,
70
+ runWithCache,
71
+ setGitHubOutput
72
+ };
73
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1,6 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/runner.ts"],
4
+ "mappings": "AAAA,SAAS,gBAAsC;AAC/C,SAAS,sBAAsB;AAC/B,SAAS,gBAAgB,WAAW,iBAAiB;AACrD,SAAS,2BAA2B;AAwBpC,eAAsB,aACpB,SAC6B;AAC7B,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA,cAAc,QAAQ,IAAI;AAAA,IAC1B,cAAc;AAAA,IACd,QAAQ;AAAA,EACV,IAAI,SAEE,MAAM,QAAQ,QAAQ,MAAM,MAAM;AAAA,EAAC;AAGzC,MAAI,cAAc,QAAQ,iBAAiB;AAC3C,QAAM,EAAE,MAAM,YAAY,IAAI,MAAM,oBAAoB;AAAA,IACtD;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,MAAI,gBAAgB,WAAW,EAAE;AAGjC,QAAM,WAAW,eAAe,EAAE,UAAU,aAAa,QAAQ,YAAY,CAAC;AAC9E,MAAI,cAAc,QAAQ,EAAE;AAE5B,QAAM,SAAS,UAAsD,QAAQ;AAE7E,MAAI,UAAU,OAAO,gBAAgB;AACnC,eAAI,4BAA4B,GACzB;AAAA,MACL,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAIF,MAAI,kBAAkB,YAAY,EAAE;AACpC,QAAM,cAA+B;AAAA,IACnC,KAAK;AAAA,IACL,OAAO,QAAQ,YAAY,CAAC,QAAQ,QAAQ,MAAM;AAAA,EACpD;AAEA,MAAI;AACF,aAAS,cAAc,WAAW;AAAA,EACpC,SAAS,OAAO;AACd,UAAM,MAAM;AACZ,UAAM,IAAI,MAAM,iBAAiB,IAAI,OAAO,GAAG,IAAI,SAAS;AAAA,EAAK,IAAI,MAAM,KAAK,EAAE,EAAE;AAAA,EACtF;AAGA,mBAAU,UAAU;AAAA,IAClB;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC;AAAA,IACA;AAAA,EACF,CAAC,GAEM;AAAA,IACL,UAAU;AAAA,IACV;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAMO,SAAS,gBAAgB,MAAc,OAAqB;AACjE,QAAM,aAAa,QAAQ,IAAI;AAC/B,EAAI,aACF,eAAe,YAAY,GAAG,IAAI,IAAI,KAAK;AAAA,CAAI,IAG/C,QAAQ,KAAK,mBAAmB,IAAI,IAAI,KAAK,EAAE;AAEnD;AAKO,SAAS,kBAA2B;AACzC,SAAO,CAAC,CAAC,QAAQ,IAAI;AACvB;AAKO,SAAS,OAAgB;AAC9B,SAAO,CAAC,CAAC,QAAQ,IAAI;AACvB;",
5
+ "names": []
6
+ }
@@ -0,0 +1,73 @@
1
+ import { execSync } from "node:child_process";
2
+ import { appendFileSync } from "node:fs";
3
+ import { createCacheKey, loadCache, saveCache } from "./cache.mjs";
4
+ import { generateFingerprint } from "./fingerprint.mjs";
5
+ async function runWithCache(options) {
6
+ const {
7
+ platform,
8
+ buildCommand,
9
+ outputPaths,
10
+ projectRoot = process.cwd(),
11
+ cachePrefix = "native-build",
12
+ debug = !1
13
+ } = options,
14
+ log = debug ? console.log : () => {};
15
+ log(`Generating ${platform} fingerprint...`);
16
+ const {
17
+ hash: fingerprint
18
+ } = await generateFingerprint({
19
+ platform,
20
+ projectRoot,
21
+ debug
22
+ });
23
+ log(`Fingerprint: ${fingerprint}`);
24
+ const cacheKey = createCacheKey({
25
+ platform,
26
+ fingerprint,
27
+ prefix: cachePrefix
28
+ });
29
+ log(`Cache key: ${cacheKey}`);
30
+ const cached = loadCache(cacheKey);
31
+ if (cached && cached.fingerprint === fingerprint) return log("Cache hit! Skipping build."), {
32
+ cacheHit: !0,
33
+ fingerprint,
34
+ cacheKey,
35
+ outputPaths
36
+ };
37
+ log(`Running build: ${buildCommand}`);
38
+ const execOptions = {
39
+ cwd: projectRoot,
40
+ stdio: debug ? "inherit" : ["pipe", "pipe", "pipe"]
41
+ };
42
+ try {
43
+ execSync(buildCommand, execOptions);
44
+ } catch (error) {
45
+ const err = error;
46
+ throw new Error(`Build failed: ${err.message}${err.stderr ? `
47
+ ${err.stderr}` : ""}`);
48
+ }
49
+ return saveCache(cacheKey, {
50
+ fingerprint,
51
+ timestamp: (/* @__PURE__ */new Date()).toISOString(),
52
+ platform,
53
+ outputPaths
54
+ }), {
55
+ cacheHit: !1,
56
+ fingerprint,
57
+ cacheKey,
58
+ outputPaths
59
+ };
60
+ }
61
+ function setGitHubOutput(name, value) {
62
+ const outputFile = process.env.GITHUB_OUTPUT;
63
+ outputFile ? appendFileSync(outputFile, `${name}=${value}
64
+ `) : console.info(`[GitHub Output] ${name}=${value}`);
65
+ }
66
+ function isGitHubActions() {
67
+ return !!process.env.GITHUB_ACTIONS;
68
+ }
69
+ function isCI() {
70
+ return !!process.env.CI;
71
+ }
72
+ export { isCI, isGitHubActions, runWithCache, setGitHubOutput };
73
+ //# sourceMappingURL=runner.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["execSync","appendFileSync","createCacheKey","loadCache","saveCache","generateFingerprint","runWithCache","options","platform","buildCommand","outputPaths","projectRoot","process","cwd","cachePrefix","debug","log","console","hash","fingerprint","cacheKey","prefix","cached","cacheHit","execOptions","stdio","error","err","Error","message","stderr","timestamp","Date","toISOString","setGitHubOutput","name","value","outputFile","env","GITHUB_OUTPUT","info","isGitHubActions","GITHUB_ACTIONS","isCI","CI"],"sources":["../src/runner.ts"],"sourcesContent":[null],"mappings":"AAAA,SAASA,QAAA,QAAsC;AAC/C,SAASC,cAAA,QAAsB;AAC/B,SAASC,cAAA,EAAgBC,SAAA,EAAWC,SAAA,QAAiB;AACrD,SAASC,mBAAA,QAA2B;AAwBpC,eAAsBC,aACpBC,OAAA,EAC6B;EAC7B,MAAM;MACJC,QAAA;MACAC,YAAA;MACAC,WAAA;MACAC,WAAA,GAAcC,OAAA,CAAQC,GAAA,CAAI;MAC1BC,WAAA,GAAc;MACdC,KAAA,GAAQ;IACV,IAAIR,OAAA;IAEES,GAAA,GAAMD,KAAA,GAAQE,OAAA,CAAQD,GAAA,GAAM,MAAM,CAAC;EAGzCA,GAAA,CAAI,cAAcR,QAAQ,iBAAiB;EAC3C,MAAM;IAAEU,IAAA,EAAMC;EAAY,IAAI,MAAMd,mBAAA,CAAoB;IACtDG,QAAA;IACAG,WAAA;IACAI;EACF,CAAC;EACDC,GAAA,CAAI,gBAAgBG,WAAW,EAAE;EAGjC,MAAMC,QAAA,GAAWlB,cAAA,CAAe;IAAEM,QAAA;IAAUW,WAAA;IAAaE,MAAA,EAAQP;EAAY,CAAC;EAC9EE,GAAA,CAAI,cAAcI,QAAQ,EAAE;EAE5B,MAAME,MAAA,GAASnB,SAAA,CAAsDiB,QAAQ;EAE7E,IAAIE,MAAA,IAAUA,MAAA,CAAOH,WAAA,KAAgBA,WAAA,EACnC,OAAAH,GAAA,CAAI,4BAA4B,GACzB;IACLO,QAAA,EAAU;IACVJ,WAAA;IACAC,QAAA;IACAV;EACF;EAIFM,GAAA,CAAI,kBAAkBP,YAAY,EAAE;EACpC,MAAMe,WAAA,GAA+B;IACnCX,GAAA,EAAKF,WAAA;IACLc,KAAA,EAAOV,KAAA,GAAQ,YAAY,CAAC,QAAQ,QAAQ,MAAM;EACpD;EAEA,IAAI;IACFf,QAAA,CAASS,YAAA,EAAce,WAAW;EACpC,SAASE,KAAA,EAAO;IACd,MAAMC,GAAA,GAAMD,KAAA;IACZ,MAAM,IAAIE,KAAA,CAAM,iBAAiBD,GAAA,CAAIE,OAAO,GAAGF,GAAA,CAAIG,MAAA,GAAS;AAAA,EAAKH,GAAA,CAAIG,MAAM,KAAK,EAAE,EAAE;EACtF;EAGA,OAAA1B,SAAA,CAAUgB,QAAA,EAAU;IAClBD,WAAA;IACAY,SAAA,GAAW,mBAAIC,IAAA,CAAK,GAAEC,WAAA,CAAY;IAClCzB,QAAA;IACAE;EACF,CAAC,GAEM;IACLa,QAAA,EAAU;IACVJ,WAAA;IACAC,QAAA;IACAV;EACF;AACF;AAMO,SAASwB,gBAAgBC,IAAA,EAAcC,KAAA,EAAqB;EACjE,MAAMC,UAAA,GAAazB,OAAA,CAAQ0B,GAAA,CAAIC,aAAA;EAC3BF,UAAA,GACFpC,cAAA,CAAeoC,UAAA,EAAY,GAAGF,IAAI,IAAIC,KAAK;AAAA,CAAI,IAG/CnB,OAAA,CAAQuB,IAAA,CAAK,mBAAmBL,IAAI,IAAIC,KAAK,EAAE;AAEnD;AAKO,SAASK,gBAAA,EAA2B;EACzC,OAAO,CAAC,CAAC7B,OAAA,CAAQ0B,GAAA,CAAII,cAAA;AACvB;AAKO,SAASC,KAAA,EAAgB;EAC9B,OAAO,CAAC,CAAC/B,OAAA,CAAQ0B,GAAA,CAAIM,EAAA;AACvB","ignoreList":[]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@tamagui/native-ci",
3
+ "version": "1.139.0",
4
+ "description": "Native CI/CD helpers for React Native apps with Expo - fingerprinting, caching, and build optimization",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "source": "src/index.ts",
8
+ "types": "./types/index.d.ts",
9
+ "module": "dist",
10
+ "bin": "./dist/cli.mjs",
11
+ "engines": {
12
+ "node": ">=18"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "files": [
18
+ "src",
19
+ "types",
20
+ "dist",
21
+ "actions",
22
+ "action.yml"
23
+ ],
24
+ "scripts": {
25
+ "build": "tamagui-build --skip-native",
26
+ "watch": "yarn build --watch",
27
+ "clean": "tamagui-build clean",
28
+ "clean:build": "tamagui-build clean:build"
29
+ },
30
+ "exports": {
31
+ "./package.json": "./package.json",
32
+ ".": {
33
+ "types": "./types/index.d.ts",
34
+ "import": "./dist/index.mjs"
35
+ }
36
+ },
37
+ "dependencies": {
38
+ "@expo/fingerprint": "^0.15.3"
39
+ },
40
+ "devDependencies": {
41
+ "@tamagui/build": "1.139.0",
42
+ "@types/bun": "^1.1.0",
43
+ "@types/node": "^22.1.0"
44
+ },
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "https://github.com/tamagui/tamagui.git",
48
+ "directory": "code/packages/native-ci"
49
+ }
50
+ }
package/src/android.ts ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Android-specific utilities for Detox test runners
3
+ */
4
+
5
+ import { existsSync } from 'node:fs'
6
+ import { join } from 'node:path'
7
+ import { $ } from 'bun'
8
+ import { METRO_PORT, DETOX_SERVER_PORT } from './constants'
9
+
10
+ /**
11
+ * Wait for Android device/emulator to be ready.
12
+ * Times out after 30 seconds if no device is available.
13
+ */
14
+ export async function waitForDevice(): Promise<void> {
15
+ console.info('\n--- Waiting for device ---')
16
+
17
+ // Check if any device is connected first (with a quick timeout)
18
+ try {
19
+ const result = await $`adb devices`.quiet()
20
+ const lines = result.stdout.toString().split('\n').filter(line => line.includes('\tdevice'))
21
+ if (lines.length === 0) {
22
+ throw new Error('No Android device/emulator connected. Please start an emulator first.')
23
+ }
24
+ } catch (error) {
25
+ const err = error as Error
26
+ throw new Error(`No Android device available: ${err.message}`)
27
+ }
28
+
29
+ try {
30
+ // Wait for device to be fully booted (with timeout)
31
+ await $`timeout 30 adb wait-for-device`.quiet()
32
+ await $`timeout 60 adb shell 'while [ -z "$(getprop sys.boot_completed)" ]; do sleep 1; done'`.quiet()
33
+ console.info('Device is ready!')
34
+ } catch (error) {
35
+ const err = error as Error
36
+ throw new Error(`Failed to wait for Android device: ${err.message}`)
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Setup ADB reverse port forwarding for Metro and Detox server.
42
+ * This allows the emulator to connect to services on the host machine.
43
+ */
44
+ export async function setupAdbReverse(): Promise<void> {
45
+ console.info('\n--- Setting up ADB reverse ---')
46
+
47
+ try {
48
+ // Metro bundler port
49
+ await $`adb reverse tcp:${METRO_PORT} tcp:${METRO_PORT}`
50
+ console.info(`Reversed port ${METRO_PORT} (Metro)`)
51
+
52
+ // Detox server port
53
+ await $`adb reverse tcp:${DETOX_SERVER_PORT} tcp:${DETOX_SERVER_PORT}`
54
+ console.info(`Reversed port ${DETOX_SERVER_PORT} (Detox)`)
55
+ } catch (error) {
56
+ const err = error as Error
57
+ throw new Error(
58
+ `Failed to setup ADB reverse ports: ${err.message}\n` +
59
+ 'Make sure the Android emulator is running and adb is available.'
60
+ )
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Full Android device setup - wait for device and setup port forwarding.
66
+ */
67
+ export async function setupAndroidDevice(): Promise<void> {
68
+ await waitForDevice()
69
+ await setupAdbReverse()
70
+ }
71
+
72
+ /**
73
+ * Ensure the android/ folder has full prebuild structure for Metro.
74
+ * In CI, the build job caches the entire android/ folder (including APKs and test files).
75
+ * The test job restores this cache and should NOT regenerate it.
76
+ *
77
+ * Why we DON'T always regenerate (unlike a previous approach):
78
+ * - The cached android/ folder includes DetoxTest.java and other manually-added test files
79
+ * - Running `expo prebuild --clean` would DELETE these critical test infrastructure files
80
+ * - The cached folder's fingerprint already ensures it's in sync with node_modules
81
+ *
82
+ * We check for android/build.gradle as the indicator of a complete prebuild.
83
+ * Only regenerate if the folder is missing or incomplete.
84
+ */
85
+ export async function ensureAndroidFolder(): Promise<void> {
86
+ const buildGradlePath = join(process.cwd(), 'android', 'build.gradle')
87
+
88
+ if (existsSync(buildGradlePath)) {
89
+ console.info('Android folder already exists (build.gradle found)')
90
+ return
91
+ }
92
+
93
+ console.info('\n--- Generating android/ folder (for Metro) ---')
94
+ console.info('Note: android/build.gradle not found, running expo prebuild')
95
+
96
+ try {
97
+ await $`npx expo prebuild --platform android`
98
+ console.info('Android folder generated!')
99
+ } catch (error) {
100
+ const err = error as Error
101
+ throw new Error(`Failed to generate android folder: ${err.message}`)
102
+ }
103
+ }