@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
package/README.md ADDED
@@ -0,0 +1,320 @@
1
+ # @tamagui/native-ci
2
+
3
+ Native CI/CD helpers for React Native apps with Expo. Provides fingerprint-based build caching and Detox test runners for GitHub Actions.
4
+
5
+ ## Features
6
+
7
+ - **Fingerprint-based caching**: Uses `@expo/fingerprint` to detect when native rebuilds are needed
8
+ - **2-level caching**: Pre-fingerprint hash (fast) + full fingerprint (accurate)
9
+ - **KV store integration**: Optional Redis/Upstash KV for persistent fingerprint cache
10
+ - **Detox test runners**: Clean TypeScript scripts for running Detox E2E tests
11
+ - **Reusable GitHub Actions**: Drop-in composite actions for iOS and Android
12
+ - **Signal handling**: Proper cleanup on CI cancellation (SIGINT/SIGTERM)
13
+
14
+ ## Requirements
15
+
16
+ - **Bun**: The Detox runner scripts require [Bun](https://bun.sh) runtime
17
+ - **Node.js**: >= 18 for the CLI and library functions
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ npm install @tamagui/native-ci
23
+ # or
24
+ yarn add @tamagui/native-ci
25
+ # or
26
+ bun add @tamagui/native-ci
27
+ ```
28
+
29
+ ## CLI Usage
30
+
31
+ ```bash
32
+ # Generate fingerprint for a platform
33
+ npx @tamagui/native-ci fingerprint ios
34
+ npx @tamagui/native-ci fingerprint android
35
+
36
+ # Generate pre-fingerprint hash (fast)
37
+ npx @tamagui/native-ci pre-hash yarn.lock app.json
38
+
39
+ # Generate cache key
40
+ npx @tamagui/native-ci cache-key ios <fingerprint>
41
+
42
+ # KV operations (requires env vars)
43
+ npx @tamagui/native-ci kv-get <key>
44
+ npx @tamagui/native-ci kv-set <key> <value>
45
+ ```
46
+
47
+ ### Options
48
+
49
+ - `--project-root <path>` - Project root directory (default: cwd)
50
+ - `--prefix <prefix>` - Cache key prefix (default: native-build)
51
+ - `--github-output` - Output results for GitHub Actions
52
+ - `--json` - Output as JSON
53
+
54
+ ### Environment Variables
55
+
56
+ - `KV_STORE_REDIS_REST_URL` - Redis REST API URL for fingerprint caching
57
+ - `KV_STORE_REDIS_REST_TOKEN` - Redis REST API token
58
+
59
+ ## Detox Test Runners
60
+
61
+ Run Detox tests with Metro bundler and proper cleanup:
62
+
63
+ ```bash
64
+ # iOS
65
+ bun run node_modules/@tamagui/native-ci/src/run-detox-ios.ts \
66
+ --project-root ./my-app \
67
+ --config ios.sim.debug
68
+
69
+ # Android
70
+ bun run node_modules/@tamagui/native-ci/src/run-detox-android.ts \
71
+ --project-root ./my-app \
72
+ --config android.emu.ci.debug \
73
+ --headless
74
+ ```
75
+
76
+ ### Runner Options
77
+
78
+ | Option | Description | Default |
79
+ |--------|-------------|---------|
80
+ | `--config` | Detox configuration name | `ios.sim.debug` / `android.emu.ci.debug` |
81
+ | `--project-root` | Project root directory | Current directory |
82
+ | `--record-logs` | Log recording: none, failing, all | `all` |
83
+ | `--retries` | Number of test retries | `0` |
84
+ | `--headless` | Run in headless mode (Android only) | `false` |
85
+
86
+ ## GitHub Actions
87
+
88
+ ### Fingerprint Action
89
+
90
+ ```yaml
91
+ - name: Generate Fingerprint
92
+ uses: tamagui/tamagui/code/packages/native-ci/actions/fingerprint@main
93
+ id: fingerprint
94
+ with:
95
+ platform: ios # or android
96
+ project-root: ./my-app
97
+ kv-url: ${{ secrets.KV_STORE_REDIS_REST_URL }}
98
+ kv-token: ${{ secrets.KV_STORE_REDIS_REST_TOKEN }}
99
+
100
+ - name: Use fingerprint
101
+ run: echo "Fingerprint: ${{ steps.fingerprint.outputs.fingerprint }}"
102
+ ```
103
+
104
+ #### Inputs
105
+
106
+ | Input | Description | Default |
107
+ |-------|-------------|---------|
108
+ | `platform` | Platform (ios or android) | Required |
109
+ | `project-root` | Path to Expo project | `.` |
110
+ | `cache-prefix` | Prefix for cache keys | `native-build` |
111
+ | `kv-url` | Redis KV REST URL (optional) | - |
112
+ | `kv-token` | Redis KV REST token (optional) | - |
113
+ | `pre-hash-files` | Files for pre-fingerprint hash | `yarn.lock,package-lock.json,app.json` |
114
+
115
+ #### Outputs
116
+
117
+ | Output | Description |
118
+ |--------|-------------|
119
+ | `fingerprint` | Generated fingerprint hash |
120
+ | `cache-key` | Cache key for this build |
121
+ | `pre-fingerprint-hash` | Quick pre-fingerprint hash |
122
+ | `cache-hit` | Whether fingerprint was cached |
123
+
124
+ ### iOS Detox Tests Action
125
+
126
+ ```yaml
127
+ - name: Run iOS Detox Tests
128
+ uses: tamagui/tamagui/code/packages/native-ci/actions/test-detox-ios@main
129
+ with:
130
+ working-directory: ./my-app
131
+ config: ios.sim.debug
132
+ app-path: ${{ env.IOS_APP_PATH }}
133
+ ```
134
+
135
+ #### Inputs
136
+
137
+ | Input | Description | Default |
138
+ |-------|-------------|---------|
139
+ | `project-root` | Path to project root | `.` |
140
+ | `working-directory` | Working directory for tests | `.` |
141
+ | `config` | Detox configuration name | `ios.sim.debug` |
142
+ | `record-logs` | Log recording: none, failing, all | `all` |
143
+ | `retries` | Number of test retries | `0` |
144
+ | `simulator` | iOS simulator device type | `iPhone 15` |
145
+ | `app-path` | Path to built app (optional) | - |
146
+
147
+ ### Android Detox Tests Action
148
+
149
+ ```yaml
150
+ - name: Run Android Detox Tests
151
+ uses: tamagui/tamagui/code/packages/native-ci/actions/test-detox-android@main
152
+ with:
153
+ working-directory: ./my-app
154
+ config: android.emu.ci.debug
155
+ ```
156
+
157
+ #### Inputs
158
+
159
+ | Input | Description | Default |
160
+ |-------|-------------|---------|
161
+ | `project-root` | Path to project root | `.` |
162
+ | `working-directory` | Working directory for tests | `.` |
163
+ | `config` | Detox configuration name | `android.emu.ci.debug` |
164
+ | `record-logs` | Log recording: none, failing, all | `all` |
165
+ | `retries` | Number of test retries | `0` |
166
+ | `api-level` | Android API level | `30` |
167
+ | `emulator-options` | Emulator options | See defaults |
168
+
169
+ ## Programmatic API
170
+
171
+ ```typescript
172
+ import {
173
+ // Fingerprinting
174
+ generateFingerprint,
175
+ generatePreFingerprintHash,
176
+
177
+ // Caching
178
+ createCacheKey,
179
+ saveFingerprintToKV,
180
+ getFingerprintFromKV,
181
+
182
+ // Build runner
183
+ runWithCache,
184
+
185
+ // Metro utilities
186
+ withMetro,
187
+ waitForMetro,
188
+
189
+ // Detox utilities
190
+ runDetoxTests,
191
+ parseDetoxArgs,
192
+
193
+ // Android utilities
194
+ setupAndroidDevice,
195
+
196
+ // Constants
197
+ METRO_PORT,
198
+ DETOX_SERVER_PORT,
199
+ } from '@tamagui/native-ci'
200
+
201
+ // Generate fingerprint
202
+ const { hash } = await generateFingerprint({
203
+ platform: 'ios',
204
+ projectRoot: './my-app',
205
+ })
206
+
207
+ // Run build with caching
208
+ const result = await runWithCache({
209
+ platform: 'ios',
210
+ buildCommand: 'xcodebuild ...',
211
+ outputPaths: ['./ios/build'],
212
+ })
213
+
214
+ // Run tests with Metro
215
+ const exitCode = await withMetro('ios', async () => {
216
+ return runDetoxTests({
217
+ config: 'ios.sim.debug',
218
+ projectRoot: './my-app',
219
+ recordLogs: 'failing',
220
+ retries: 0,
221
+ })
222
+ })
223
+ ```
224
+
225
+ ## Example Workflow
226
+
227
+ ```yaml
228
+ name: Native Tests
229
+
230
+ on:
231
+ push:
232
+ branches: [main]
233
+ pull_request:
234
+
235
+ jobs:
236
+ build-ios:
237
+ runs-on: macos-14
238
+ outputs:
239
+ cache-key: ${{ steps.fingerprint.outputs.cache-key }}
240
+ steps:
241
+ - uses: actions/checkout@v4
242
+
243
+ - name: Generate Fingerprint
244
+ uses: tamagui/tamagui/code/packages/native-ci/actions/fingerprint@main
245
+ id: fingerprint
246
+ with:
247
+ platform: ios
248
+ project-root: ./my-app
249
+
250
+ - name: Check Build Cache
251
+ uses: actions/cache/restore@v4
252
+ id: cache
253
+ with:
254
+ path: ./my-app/ios/build
255
+ key: ${{ steps.fingerprint.outputs.cache-key }}
256
+ lookup-only: true
257
+
258
+ - name: Build iOS App
259
+ if: steps.cache.outputs.cache-hit != 'true'
260
+ run: |
261
+ cd my-app
262
+ npx expo prebuild --platform ios
263
+ xcodebuild -workspace ios/*.xcworkspace ...
264
+
265
+ - name: Save Build Cache
266
+ if: steps.cache.outputs.cache-hit != 'true'
267
+ uses: actions/cache/save@v4
268
+ with:
269
+ path: ./my-app/ios/build
270
+ key: ${{ steps.fingerprint.outputs.cache-key }}
271
+
272
+ test-ios:
273
+ needs: build-ios
274
+ runs-on: macos-14
275
+ steps:
276
+ - uses: actions/checkout@v4
277
+
278
+ - name: Restore Build
279
+ uses: actions/cache/restore@v4
280
+ with:
281
+ path: ./my-app/ios/build
282
+ key: ${{ needs.build-ios.outputs.cache-key }}
283
+
284
+ - name: Run Tests
285
+ uses: tamagui/tamagui/code/packages/native-ci/actions/test-detox-ios@main
286
+ with:
287
+ working-directory: ./my-app
288
+ ```
289
+
290
+ ## How Fingerprinting Works
291
+
292
+ 1. **Pre-fingerprint hash**: Quick hash of `yarn.lock`, `app.json`, etc.
293
+ 2. **KV cache lookup**: Check if we've seen this pre-hash before
294
+ 3. **Full fingerprint**: If not cached, run `@expo/fingerprint` for accurate native dependency detection
295
+ 4. **Cache build artifacts**: Use fingerprint as cache key
296
+
297
+ This 2-level approach means:
298
+ - Cache hits are instant (no fingerprint generation needed)
299
+ - Rebuilds only happen when native dependencies actually change
300
+ - Works across CI runs with KV persistence
301
+
302
+ ## Architecture
303
+
304
+ ```
305
+ src/
306
+ ├── constants.ts # Shared constants and types
307
+ ├── fingerprint.ts # Fingerprint generation
308
+ ├── cache.ts # KV store and local cache
309
+ ├── runner.ts # Build runner with caching
310
+ ├── metro.ts # Metro bundler utilities
311
+ ├── detox.ts # Detox test utilities
312
+ ├── android.ts # Android-specific utilities
313
+ ├── cli.ts # CLI entry point
314
+ ├── run-detox-ios.ts # iOS test runner script
315
+ └── run-detox-android.ts # Android test runner script
316
+ ```
317
+
318
+ ## License
319
+
320
+ MIT
package/action.yml ADDED
@@ -0,0 +1,98 @@
1
+ name: 'Native CI - Fingerprint Cache'
2
+ description: 'Generate Expo fingerprints and manage native build caching'
3
+ author: 'Tamagui'
4
+
5
+ inputs:
6
+ platform:
7
+ description: 'Platform to generate fingerprint for (ios or android)'
8
+ required: true
9
+ project-root:
10
+ description: 'Path to the Expo project root'
11
+ required: false
12
+ default: '.'
13
+ cache-prefix:
14
+ description: 'Prefix for cache keys'
15
+ required: false
16
+ default: 'native-build'
17
+ kv-url:
18
+ description: 'Redis KV REST API URL for fingerprint caching'
19
+ required: false
20
+ kv-token:
21
+ description: 'Redis KV REST API token'
22
+ required: false
23
+ pre-hash-files:
24
+ description: 'Comma-separated list of files for pre-fingerprint hash'
25
+ required: false
26
+ default: 'yarn.lock,app.json'
27
+
28
+ outputs:
29
+ fingerprint:
30
+ description: 'The generated fingerprint hash'
31
+ value: ${{ steps.fingerprint.outputs.fingerprint }}
32
+ cache-key:
33
+ description: 'The cache key to use for this build'
34
+ value: ${{ steps.fingerprint.outputs.cache-key }}
35
+ pre-fingerprint-hash:
36
+ description: 'Quick pre-fingerprint hash'
37
+ value: ${{ steps.pre-hash.outputs.pre-fingerprint-hash }}
38
+ cache-hit:
39
+ description: 'Whether the fingerprint was found in cache'
40
+ value: ${{ steps.check-cache.outputs.cache-hit }}
41
+
42
+ runs:
43
+ using: 'composite'
44
+ steps:
45
+ - name: Calculate Pre-Fingerprint Hash
46
+ id: pre-hash
47
+ shell: bash
48
+ working-directory: ${{ inputs.project-root }}
49
+ run: |
50
+ FILES="${{ inputs.pre-hash-files }}"
51
+ HASH=$(echo "$FILES" | tr ',' '\n' | xargs -I{} cat {} 2>/dev/null | sha256sum | cut -c1-16)
52
+ echo "pre-fingerprint-hash=$HASH" >> $GITHUB_OUTPUT
53
+
54
+ - name: Check KV Cache for Fingerprint
55
+ id: kv-lookup
56
+ if: inputs.kv-url != '' && inputs.kv-token != ''
57
+ shell: bash
58
+ env:
59
+ KV_URL: ${{ inputs.kv-url }}
60
+ KV_TOKEN: ${{ inputs.kv-token }}
61
+ run: |
62
+ KEY="${{ inputs.platform }}-${{ inputs.cache-prefix }}-pre-${{ steps.pre-hash.outputs.pre-fingerprint-hash }}"
63
+ RESULT=$(curl -s "$KV_URL/get/$KEY" -H "Authorization: Bearer $KV_TOKEN" | jq -r '.result')
64
+ if [ "$RESULT" != "null" ]; then
65
+ echo "fingerprint=$RESULT" >> $GITHUB_OUTPUT
66
+ echo "found=true" >> $GITHUB_OUTPUT
67
+ # Extend TTL
68
+ curl -s -X POST "$KV_URL/EXPIRE/$KEY/2592000" -H "Authorization: Bearer $KV_TOKEN" > /dev/null
69
+ else
70
+ echo "found=false" >> $GITHUB_OUTPUT
71
+ fi
72
+
73
+ - name: Generate Fingerprint
74
+ id: fingerprint
75
+ if: steps.kv-lookup.outputs.found != 'true'
76
+ shell: bash
77
+ working-directory: ${{ inputs.project-root }}
78
+ run: |
79
+ FINGERPRINT=$(npx @expo/fingerprint fingerprint:generate --platform ${{ inputs.platform }} | jq -r '.hash')
80
+ echo "fingerprint=$FINGERPRINT" >> $GITHUB_OUTPUT
81
+ echo "cache-key=${{ inputs.cache-prefix }}-${{ inputs.platform }}-$FINGERPRINT" >> $GITHUB_OUTPUT
82
+
83
+ - name: Use Cached Fingerprint
84
+ if: steps.kv-lookup.outputs.found == 'true'
85
+ shell: bash
86
+ run: |
87
+ echo "fingerprint=${{ steps.kv-lookup.outputs.fingerprint }}" >> $GITHUB_OUTPUT
88
+ echo "cache-key=${{ inputs.cache-prefix }}-${{ inputs.platform }}-${{ steps.kv-lookup.outputs.fingerprint }}" >> $GITHUB_OUTPUT
89
+
90
+ - name: Save Fingerprint to KV
91
+ if: inputs.kv-url != '' && inputs.kv-token != '' && steps.kv-lookup.outputs.found != 'true'
92
+ shell: bash
93
+ env:
94
+ KV_URL: ${{ inputs.kv-url }}
95
+ KV_TOKEN: ${{ inputs.kv-token }}
96
+ run: |
97
+ KEY="${{ inputs.platform }}-${{ inputs.cache-prefix }}-pre-${{ steps.pre-hash.outputs.pre-fingerprint-hash }}"
98
+ curl -s -X POST "$KV_URL/SETEX/$KEY/2592000/${{ steps.fingerprint.outputs.fingerprint }}" -H "Authorization: Bearer $KV_TOKEN" > /dev/null
@@ -0,0 +1,110 @@
1
+ name: 'Native CI - Fingerprint'
2
+ description: 'Generate Expo fingerprint with 2-level caching (pre-hash + full fingerprint)'
3
+ author: 'Tamagui'
4
+
5
+ inputs:
6
+ platform:
7
+ description: 'Platform (ios or android)'
8
+ required: true
9
+ project-root:
10
+ description: 'Path to the Expo project root'
11
+ default: '.'
12
+ cache-prefix:
13
+ description: 'Prefix for cache keys'
14
+ default: 'native-build'
15
+ kv-url:
16
+ description: 'Redis KV REST API URL for fingerprint caching (optional)'
17
+ required: false
18
+ kv-token:
19
+ description: 'Redis KV REST API token (optional)'
20
+ required: false
21
+ pre-hash-files:
22
+ description: 'Comma-separated list of files for pre-fingerprint hash'
23
+ default: 'yarn.lock,package-lock.json,app.json'
24
+
25
+ outputs:
26
+ fingerprint:
27
+ description: 'The generated fingerprint hash'
28
+ value: ${{ steps.result.outputs.fingerprint }}
29
+ cache-key:
30
+ description: 'The cache key to use for this build'
31
+ value: ${{ steps.result.outputs.cache-key }}
32
+ pre-fingerprint-hash:
33
+ description: 'Quick pre-fingerprint hash'
34
+ value: ${{ steps.pre-hash.outputs.hash }}
35
+ cache-hit:
36
+ description: 'Whether fingerprint was found in KV cache'
37
+ value: ${{ steps.kv-lookup.outputs.found || 'false' }}
38
+
39
+ runs:
40
+ using: 'composite'
41
+ steps:
42
+ - name: Calculate Pre-Fingerprint Hash
43
+ id: pre-hash
44
+ shell: bash
45
+ working-directory: ${{ inputs.project-root }}
46
+ run: |
47
+ FILES="${{ inputs.pre-hash-files }}"
48
+ HASH=$(echo "$FILES" | tr ',' '\n' | while read f; do
49
+ [ -f "$f" ] && cat "$f"
50
+ done | sha256sum | cut -c1-16)
51
+ echo "hash=$HASH" >> $GITHUB_OUTPUT
52
+ echo "Pre-fingerprint hash: $HASH"
53
+
54
+ - name: Check KV Cache for Fingerprint
55
+ id: kv-lookup
56
+ if: inputs.kv-url != '' && inputs.kv-token != ''
57
+ shell: bash
58
+ env:
59
+ KV_URL: ${{ inputs.kv-url }}
60
+ KV_TOKEN: ${{ inputs.kv-token }}
61
+ run: |
62
+ KEY="${{ inputs.platform }}-${{ inputs.cache-prefix }}-pre-${{ steps.pre-hash.outputs.hash }}"
63
+ echo "Looking up KV key: $KEY"
64
+ RESULT=$(curl -s "$KV_URL/get/$KEY" -H "Authorization: Bearer $KV_TOKEN" | jq -r '.result // empty')
65
+ if [ -n "$RESULT" ] && [ "$RESULT" != "null" ]; then
66
+ echo "Found cached fingerprint: $RESULT"
67
+ echo "fingerprint=$RESULT" >> $GITHUB_OUTPUT
68
+ echo "found=true" >> $GITHUB_OUTPUT
69
+ # Extend TTL to 30 days
70
+ curl -s -X POST "$KV_URL/EXPIRE/$KEY/2592000" -H "Authorization: Bearer $KV_TOKEN" > /dev/null
71
+ else
72
+ echo "No cached fingerprint found"
73
+ echo "found=false" >> $GITHUB_OUTPUT
74
+ fi
75
+
76
+ - name: Generate Fingerprint
77
+ id: generate
78
+ if: steps.kv-lookup.outputs.found != 'true'
79
+ shell: bash
80
+ working-directory: ${{ inputs.project-root }}
81
+ run: |
82
+ echo "Generating ${{ inputs.platform }} fingerprint..."
83
+ FINGERPRINT=$(npx @expo/fingerprint fingerprint:generate --platform ${{ inputs.platform }} | jq -r '.hash')
84
+ echo "Generated fingerprint: $FINGERPRINT"
85
+ echo "fingerprint=$FINGERPRINT" >> $GITHUB_OUTPUT
86
+
87
+ - name: Save Fingerprint to KV
88
+ if: inputs.kv-url != '' && inputs.kv-token != '' && steps.kv-lookup.outputs.found != 'true' && steps.generate.outputs.fingerprint != ''
89
+ shell: bash
90
+ env:
91
+ KV_URL: ${{ inputs.kv-url }}
92
+ KV_TOKEN: ${{ inputs.kv-token }}
93
+ run: |
94
+ KEY="${{ inputs.platform }}-${{ inputs.cache-prefix }}-pre-${{ steps.pre-hash.outputs.hash }}"
95
+ echo "Saving fingerprint to KV: $KEY"
96
+ curl -s -X POST "$KV_URL/SETEX/$KEY/2592000/${{ steps.generate.outputs.fingerprint }}" -H "Authorization: Bearer $KV_TOKEN" > /dev/null
97
+
98
+ - name: Set Result
99
+ id: result
100
+ shell: bash
101
+ run: |
102
+ if [ "${{ steps.kv-lookup.outputs.found }}" = "true" ]; then
103
+ FP="${{ steps.kv-lookup.outputs.fingerprint }}"
104
+ else
105
+ FP="${{ steps.generate.outputs.fingerprint }}"
106
+ fi
107
+ echo "fingerprint=$FP" >> $GITHUB_OUTPUT
108
+ echo "cache-key=${{ inputs.cache-prefix }}-${{ inputs.platform }}-$FP" >> $GITHUB_OUTPUT
109
+ echo "Final fingerprint: $FP"
110
+ echo "Cache key: ${{ inputs.cache-prefix }}-${{ inputs.platform }}-$FP"
@@ -0,0 +1,70 @@
1
+ name: 'Native CI - Android Detox Tests'
2
+ description: 'Run Detox E2E tests on Android emulator'
3
+ author: 'Tamagui'
4
+
5
+ inputs:
6
+ project-root:
7
+ description: 'Path to the project root'
8
+ default: '.'
9
+ working-directory:
10
+ description: 'Working directory for running tests'
11
+ default: '.'
12
+ config:
13
+ description: 'Detox configuration name'
14
+ default: 'android.emu.ci.debug'
15
+ record-logs:
16
+ description: 'Record logs mode: none, failing, all'
17
+ default: 'all'
18
+ retries:
19
+ description: 'Number of test retries (0 = fail fast)'
20
+ default: '0'
21
+ api-level:
22
+ description: 'Android API level'
23
+ default: '30'
24
+ emulator-options:
25
+ description: 'Emulator options'
26
+ default: '-no-snapshot -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -camera-front none'
27
+
28
+ runs:
29
+ using: 'composite'
30
+ steps:
31
+ - name: Setup Bun
32
+ uses: oven-sh/setup-bun@v2
33
+ with:
34
+ bun-version: latest
35
+
36
+ - name: Setup Java
37
+ uses: actions/setup-java@v4
38
+ with:
39
+ distribution: 'temurin'
40
+ java-version: '17'
41
+
42
+ - name: Install Detox CLI
43
+ shell: bash
44
+ run: npm install -g detox-cli
45
+
46
+ - name: Start Android Emulator and Run Tests
47
+ uses: reactivecircus/android-emulator-runner@v2
48
+ with:
49
+ api-level: ${{ inputs.api-level }}
50
+ target: google_apis
51
+ arch: x86_64
52
+ profile: pixel_6
53
+ avd-name: test
54
+ ram-size: 4096M
55
+ heap-size: 1024M
56
+ force-avd-creation: false
57
+ emulator-boot-timeout: 900
58
+ disable-animations: true
59
+ emulator-options: ${{ inputs.emulator-options }}
60
+ working-directory: ${{ inputs.working-directory }}
61
+ pre-emulator-launch-script: |
62
+ adb start-server
63
+ script: |
64
+ SCRIPT_PATH=$(node -e "console.log(require.resolve('@tamagui/native-ci/src/run-detox-android.ts'))")
65
+ bun run "$SCRIPT_PATH" \
66
+ --headless \
67
+ --project-root "$PWD" \
68
+ --config "${{ inputs.config }}" \
69
+ --record-logs "${{ inputs.record-logs }}" \
70
+ --retries "${{ inputs.retries }}"
@@ -0,0 +1,66 @@
1
+ name: 'Native CI - iOS Detox Tests'
2
+ description: 'Run Detox E2E tests on iOS simulator'
3
+ author: 'Tamagui'
4
+
5
+ inputs:
6
+ project-root:
7
+ description: 'Path to the project root'
8
+ default: '.'
9
+ working-directory:
10
+ description: 'Working directory for running tests'
11
+ default: '.'
12
+ config:
13
+ description: 'Detox configuration name'
14
+ default: 'ios.sim.debug'
15
+ record-logs:
16
+ description: 'Record logs mode: none, failing, all'
17
+ default: 'all'
18
+ retries:
19
+ description: 'Number of test retries (0 = fail fast)'
20
+ default: '0'
21
+ simulator:
22
+ description: 'iOS simulator device type'
23
+ default: 'iPhone 15'
24
+ app-path:
25
+ description: 'Path to the built app (optional, uses DETOX_IOS_APP_PATH env var)'
26
+ required: false
27
+
28
+ runs:
29
+ using: 'composite'
30
+ steps:
31
+ - name: Setup Bun
32
+ uses: oven-sh/setup-bun@v2
33
+ with:
34
+ bun-version: latest
35
+
36
+ - name: Install Detox CLI
37
+ shell: bash
38
+ run: npm install -g detox-cli
39
+
40
+ - name: Install applesimutils
41
+ shell: bash
42
+ run: |
43
+ brew tap wix/brew
44
+ brew install applesimutils
45
+
46
+ - name: Boot iOS Simulator
47
+ shell: bash
48
+ run: |
49
+ DEVICE_ID=$(xcrun simctl list devices available -j | jq -r '.devices | to_entries | .[] | select(.key | contains("iOS")) | .value[] | select(.name == "${{ inputs.simulator }}") | .udid' | head -1)
50
+ if [ -n "$DEVICE_ID" ]; then
51
+ echo "Booting simulator: ${{ inputs.simulator }} ($DEVICE_ID)"
52
+ xcrun simctl boot "$DEVICE_ID" || true
53
+ fi
54
+
55
+ - name: Run Detox Tests
56
+ shell: bash
57
+ working-directory: ${{ inputs.working-directory }}
58
+ env:
59
+ DETOX_IOS_APP_PATH: ${{ inputs.app-path }}
60
+ run: |
61
+ SCRIPT_PATH=$(node -e "console.log(require.resolve('@tamagui/native-ci/src/run-detox-ios.ts'))")
62
+ bun run "$SCRIPT_PATH" \
63
+ --project-root "$PWD" \
64
+ --config "${{ inputs.config }}" \
65
+ --record-logs "${{ inputs.record-logs }}" \
66
+ --retries "${{ inputs.retries }}"