@tamagui/native-ci 1.144.1 → 1.144.3
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/dist/android.js +93 -0
- package/dist/cli.mjs +2 -2
- package/dist/index.mjs +2 -2
- package/dist/ios.js +70 -0
- package/dist/run-detox-android.js +40 -0
- package/dist/run-detox-ios.js +35 -0
- package/package.json +2 -2
package/dist/android.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Android-specific utilities for Detox test runners
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { $ } from 'bun';
|
|
7
|
+
import { METRO_PORT, DETOX_SERVER_PORT } from './constants';
|
|
8
|
+
/**
|
|
9
|
+
* Wait for Android device/emulator to be ready.
|
|
10
|
+
* Times out after 30 seconds if no device is available.
|
|
11
|
+
*/
|
|
12
|
+
export async function waitForDevice() {
|
|
13
|
+
console.info('\n--- Waiting for device ---');
|
|
14
|
+
// Check if any device is connected first (with a quick timeout)
|
|
15
|
+
try {
|
|
16
|
+
const result = await $ `adb devices`.quiet();
|
|
17
|
+
const lines = result.stdout.toString().split('\n').filter(line => line.includes('\tdevice'));
|
|
18
|
+
if (lines.length === 0) {
|
|
19
|
+
throw new Error('No Android device/emulator connected. Please start an emulator first.');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
const err = error;
|
|
24
|
+
throw new Error(`No Android device available: ${err.message}`);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
// Wait for device to be fully booted (with timeout)
|
|
28
|
+
await $ `timeout 30 adb wait-for-device`.quiet();
|
|
29
|
+
await $ `timeout 60 adb shell 'while [ -z "$(getprop sys.boot_completed)" ]; do sleep 1; done'`.quiet();
|
|
30
|
+
console.info('Device is ready!');
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
const err = error;
|
|
34
|
+
throw new Error(`Failed to wait for Android device: ${err.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Setup ADB reverse port forwarding for Metro and Detox server.
|
|
39
|
+
* This allows the emulator to connect to services on the host machine.
|
|
40
|
+
*/
|
|
41
|
+
export async function setupAdbReverse() {
|
|
42
|
+
console.info('\n--- Setting up ADB reverse ---');
|
|
43
|
+
try {
|
|
44
|
+
// Metro bundler port
|
|
45
|
+
await $ `adb reverse tcp:${METRO_PORT} tcp:${METRO_PORT}`;
|
|
46
|
+
console.info(`Reversed port ${METRO_PORT} (Metro)`);
|
|
47
|
+
// Detox server port
|
|
48
|
+
await $ `adb reverse tcp:${DETOX_SERVER_PORT} tcp:${DETOX_SERVER_PORT}`;
|
|
49
|
+
console.info(`Reversed port ${DETOX_SERVER_PORT} (Detox)`);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const err = error;
|
|
53
|
+
throw new Error(`Failed to setup ADB reverse ports: ${err.message}\n` +
|
|
54
|
+
'Make sure the Android emulator is running and adb is available.');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Full Android device setup - wait for device and setup port forwarding.
|
|
59
|
+
*/
|
|
60
|
+
export async function setupAndroidDevice() {
|
|
61
|
+
await waitForDevice();
|
|
62
|
+
await setupAdbReverse();
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Ensure the android/ folder has full prebuild structure for Metro.
|
|
66
|
+
* In CI, the build job caches the entire android/ folder (including APKs and test files).
|
|
67
|
+
* The test job restores this cache and should NOT regenerate it.
|
|
68
|
+
*
|
|
69
|
+
* Why we DON'T always regenerate (unlike a previous approach):
|
|
70
|
+
* - The cached android/ folder includes DetoxTest.java and other manually-added test files
|
|
71
|
+
* - Running `expo prebuild --clean` would DELETE these critical test infrastructure files
|
|
72
|
+
* - The cached folder's fingerprint already ensures it's in sync with node_modules
|
|
73
|
+
*
|
|
74
|
+
* We check for android/build.gradle as the indicator of a complete prebuild.
|
|
75
|
+
* Only regenerate if the folder is missing or incomplete.
|
|
76
|
+
*/
|
|
77
|
+
export async function ensureAndroidFolder() {
|
|
78
|
+
const buildGradlePath = join(process.cwd(), 'android', 'build.gradle');
|
|
79
|
+
if (existsSync(buildGradlePath)) {
|
|
80
|
+
console.info('Android folder already exists (build.gradle found)');
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
console.info('\n--- Generating android/ folder (for Metro) ---');
|
|
84
|
+
console.info('Note: android/build.gradle not found, running expo prebuild');
|
|
85
|
+
try {
|
|
86
|
+
await $ `npx expo prebuild --platform android`;
|
|
87
|
+
console.info('Android folder generated!');
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
const err = error;
|
|
91
|
+
throw new Error(`Failed to generate android folder: ${err.message}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
package/dist/cli.mjs
CHANGED
|
@@ -5,8 +5,8 @@ import { setGitHubOutput, isGitHubActions, isCI } from "./runner.mjs";
|
|
|
5
5
|
import { ensureIosDeps, ensureAndroidDeps, ensureMaestro, printDepsStatus } from "./deps.mjs";
|
|
6
6
|
import { withMetro } from "./metro.mjs";
|
|
7
7
|
import { runDetoxTests } from "./detox.mjs";
|
|
8
|
-
import { ensureIOSFolder, ensureIOSApp } from "./ios";
|
|
9
|
-
import { setupAndroidDevice, ensureAndroidFolder } from "./android";
|
|
8
|
+
import { ensureIOSFolder, ensureIOSApp } from "./ios.mjs";
|
|
9
|
+
import { setupAndroidDevice, ensureAndroidFolder } from "./android.mjs";
|
|
10
10
|
const HELP = `
|
|
11
11
|
native-ci - Native CI/CD helpers for Expo apps
|
|
12
12
|
|
package/dist/index.mjs
CHANGED
|
@@ -4,8 +4,8 @@ import { createCacheKey, saveFingerprintToKV, getFingerprintFromKV, extendKVTTL,
|
|
|
4
4
|
import { runWithCache, setGitHubOutput, isGitHubActions, isCI } from "./runner.mjs";
|
|
5
5
|
import { waitForMetro, prewarmBundle, startMetro, setupSignalHandlers, withMetro } from "./metro.mjs";
|
|
6
6
|
import { parseDetoxArgs, buildDetoxArgs, runDetoxTests } from "./detox.mjs";
|
|
7
|
-
import { waitForDevice, setupAdbReverse, setupAndroidDevice, ensureAndroidFolder } from "./android";
|
|
8
|
-
import { ensureIOSFolder } from "./ios";
|
|
7
|
+
import { waitForDevice, setupAdbReverse, setupAndroidDevice, ensureAndroidFolder } from "./android.mjs";
|
|
8
|
+
import { ensureIOSFolder } from "./ios.mjs";
|
|
9
9
|
import { checkDeps, ensureIosDeps, ensureAndroidDeps, ensureMaestro, printDepsStatus } from "./deps.mjs";
|
|
10
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
11
|
//# sourceMappingURL=index.mjs.map
|
package/dist/ios.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* iOS-specific utilities for Detox test runners
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync } from 'node:fs';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { $ } from 'bun';
|
|
7
|
+
import { isCI } from './runner';
|
|
8
|
+
/**
|
|
9
|
+
* Ensure the ios/ folder has full prebuild structure for Metro.
|
|
10
|
+
* In CI, the build job may only cache the .app file, so the test job needs
|
|
11
|
+
* to regenerate the ios folder structure for Metro to work correctly.
|
|
12
|
+
*
|
|
13
|
+
* Metro needs the native project files (Podfile, *.xcodeproj) to properly
|
|
14
|
+
* configure the JS bundle with native module information (global.expo.modules).
|
|
15
|
+
*
|
|
16
|
+
* We check for ios/Podfile as the indicator of a complete prebuild,
|
|
17
|
+
* not just the existence of the ios/ folder.
|
|
18
|
+
*
|
|
19
|
+
* Uses --no-install to skip pod install (pods are not needed for Metro).
|
|
20
|
+
*/
|
|
21
|
+
export async function ensureIOSFolder() {
|
|
22
|
+
const podfilePath = join(process.cwd(), 'ios', 'Podfile');
|
|
23
|
+
if (!existsSync(podfilePath)) {
|
|
24
|
+
console.info('\n--- Generating ios/ folder (for Metro) ---');
|
|
25
|
+
console.info('Note: ios/Podfile not found, running expo prebuild');
|
|
26
|
+
try {
|
|
27
|
+
await $ `npx expo prebuild --platform ios --no-install`;
|
|
28
|
+
console.info('iOS folder generated!');
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
const err = error;
|
|
32
|
+
throw new Error(`Failed to generate ios folder: ${err.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
console.info('iOS folder already exists (Podfile found)');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Ensure the iOS app binary exists, building it if necessary.
|
|
41
|
+
* On CI, this is a no-op since CI builds the app in a separate job.
|
|
42
|
+
* Locally, this will build the app if the binary is missing.
|
|
43
|
+
*/
|
|
44
|
+
export async function ensureIOSApp(config = 'ios.sim.debug') {
|
|
45
|
+
// On CI, the app is built separately - don't build here
|
|
46
|
+
if (isCI()) {
|
|
47
|
+
console.info('CI detected - skipping local build check');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Check if app binary exists (use the path from detoxrc)
|
|
51
|
+
const appPath = process.env.DETOX_IOS_APP_PATH ||
|
|
52
|
+
'ios/build/Build/Products/Debug-iphonesimulator/tamaguikitchensink.app';
|
|
53
|
+
const fullAppPath = join(process.cwd(), appPath);
|
|
54
|
+
if (existsSync(fullAppPath)) {
|
|
55
|
+
console.info(`iOS app found at ${appPath}`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
console.info(`\n--- iOS app not found at ${appPath}, building... ---`);
|
|
59
|
+
// Ensure pods are installed first
|
|
60
|
+
const podsPath = join(process.cwd(), 'ios', 'Pods');
|
|
61
|
+
if (!existsSync(podsPath)) {
|
|
62
|
+
console.info('Installing CocoaPods dependencies...');
|
|
63
|
+
await $ `pod install --project-directory=ios`;
|
|
64
|
+
}
|
|
65
|
+
// Build the app using detox build
|
|
66
|
+
console.info(`Building iOS app (config: ${config})...`);
|
|
67
|
+
console.info('This may take a few minutes on first run.');
|
|
68
|
+
await $ `npx detox build -c ${config}`;
|
|
69
|
+
console.info('iOS app built successfully!');
|
|
70
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Run Android Detox tests with Metro bundler
|
|
4
|
+
*
|
|
5
|
+
* Usage: bun run-detox-android.ts [options]
|
|
6
|
+
*
|
|
7
|
+
* Options:
|
|
8
|
+
* --config <name> Detox configuration name (default: android.emu.ci.debug)
|
|
9
|
+
* --project-root <path> Project root directory (default: cwd)
|
|
10
|
+
* --headless Run in headless mode
|
|
11
|
+
* --record-logs <mode> Record logs: none, failing, all (default: all)
|
|
12
|
+
* --retries <n> Number of retries for flaky tests (default: 0)
|
|
13
|
+
*/
|
|
14
|
+
import { withMetro } from './metro';
|
|
15
|
+
import { parseDetoxArgs, runDetoxTests } from './detox';
|
|
16
|
+
import { waitForDevice, setupAdbReverse, ensureAndroidFolder } from './android';
|
|
17
|
+
const options = parseDetoxArgs('android');
|
|
18
|
+
console.info('=== Android Detox Test Runner ===');
|
|
19
|
+
console.info(`Config: ${options.config}`);
|
|
20
|
+
console.info(`Project root: ${options.projectRoot}`);
|
|
21
|
+
console.info(`Headless: ${options.headless}`);
|
|
22
|
+
// Change to project root
|
|
23
|
+
process.chdir(options.projectRoot);
|
|
24
|
+
// Ensure android folder exists (CI only caches APKs, not the full project)
|
|
25
|
+
await ensureAndroidFolder();
|
|
26
|
+
// Wait for Android device to be ready
|
|
27
|
+
await waitForDevice();
|
|
28
|
+
// Setup ADB reverse for Metro (Detox handles its own port via reversePorts config, but we need Metro early)
|
|
29
|
+
await setupAdbReverse();
|
|
30
|
+
// Run tests with Metro
|
|
31
|
+
const exitCode = await withMetro('android', async () => {
|
|
32
|
+
return runDetoxTests({
|
|
33
|
+
config: options.config,
|
|
34
|
+
projectRoot: options.projectRoot,
|
|
35
|
+
recordLogs: options.recordLogs,
|
|
36
|
+
retries: options.retries,
|
|
37
|
+
headless: options.headless,
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
process.exit(exitCode);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Run iOS Detox tests with Metro bundler
|
|
4
|
+
*
|
|
5
|
+
* Usage: bun run-detox-ios.ts [options]
|
|
6
|
+
*
|
|
7
|
+
* Options:
|
|
8
|
+
* --config <name> Detox configuration name (default: ios.sim.debug)
|
|
9
|
+
* --project-root <path> Project root directory (default: cwd)
|
|
10
|
+
* --record-logs <mode> Record logs: none, failing, all (default: all)
|
|
11
|
+
* --retries <n> Number of retries for flaky tests (default: 0)
|
|
12
|
+
*/
|
|
13
|
+
import { withMetro } from './metro';
|
|
14
|
+
import { parseDetoxArgs, runDetoxTests } from './detox';
|
|
15
|
+
import { ensureIOSFolder, ensureIOSApp } from './ios';
|
|
16
|
+
const options = parseDetoxArgs('ios');
|
|
17
|
+
console.info('=== iOS Detox Test Runner ===');
|
|
18
|
+
console.info(`Config: ${options.config}`);
|
|
19
|
+
console.info(`Project root: ${options.projectRoot}`);
|
|
20
|
+
// Change to project root
|
|
21
|
+
process.chdir(options.projectRoot);
|
|
22
|
+
// Ensure ios folder exists (in case build artifacts were separated)
|
|
23
|
+
await ensureIOSFolder();
|
|
24
|
+
// Ensure iOS app is built (skipped on CI where app is pre-built)
|
|
25
|
+
await ensureIOSApp(options.config);
|
|
26
|
+
// Run tests with Metro
|
|
27
|
+
const exitCode = await withMetro('ios', async () => {
|
|
28
|
+
return runDetoxTests({
|
|
29
|
+
config: options.config,
|
|
30
|
+
projectRoot: options.projectRoot,
|
|
31
|
+
recordLogs: options.recordLogs,
|
|
32
|
+
retries: options.retries,
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
process.exit(exitCode);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tamagui/native-ci",
|
|
3
|
-
"version": "1.144.
|
|
3
|
+
"version": "1.144.3",
|
|
4
4
|
"description": "Native CI/CD helpers for React Native apps with Expo - fingerprinting, caching, and build optimization",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"@expo/fingerprint": "^0.15.3"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@tamagui/build": "1.144.
|
|
42
|
+
"@tamagui/build": "1.144.3",
|
|
43
43
|
"@types/bun": "^1.1.0",
|
|
44
44
|
"@types/node": "^22.1.0"
|
|
45
45
|
},
|