#!/usr/bin/env node const { exec, spawn } = require('child_process'); const ROOT_DIR = __dirname + '/..'; function run(cmd) { return new Promise((resolve, reject) => { const p = exec(cmd, { cwd: ROOT_DIR, maxBuffer: 1024 * 1024 * 10 }); p.stdout.pipe(process.stdout); p.stderr.pipe(process.stderr); p.on('close', code => (code === 0 ? resolve() : reject(new Error(cmd + ' exited with ' + code)))); }); } function adbList() { return new Promise((resolve, reject) => { exec('adb devices -l', (err, stdout) => { if (err) return reject(err); resolve(stdout); }); }); } /** * Check if Metro is already listening on port 8081. */ function isMetroRunning() { return new Promise(resolve => { exec('lsof -iTCP:8081 -sTCP:LISTEN -t', (err, stdout) => { resolve(!err && stdout.trim().length > 0); }); }); } /** * Start Metro bundler in the background. Returns a cleanup function. */ function startMetro() { console.log('[run-detox] Starting Metro bundler on port 8081...'); const metro = spawn('npx', ['react-native', 'start', '--port', '8081', '--reset-cache'], { cwd: ROOT_DIR, stdio: ['ignore', 'pipe', 'pipe'], detached: false, }); metro.stdout.on('data', d => process.stdout.write('[metro] ' + d)); metro.stderr.on('data', d => process.stderr.write('[metro] ' + d)); metro.on('error', err => console.error('[run-detox] Metro error:', err.message)); return metro; } /** * Wait until Metro is ready (listening on 8081), with a timeout. */ function waitForMetro(timeoutMs = 60000) { return new Promise((resolve, reject) => { const start = Date.now(); const interval = setInterval(() => { exec('lsof -iTCP:8081 -sTCP:LISTEN -t', (err, stdout) => { if (!err && stdout.trim().length > 0) { clearInterval(interval); resolve(); } else if (Date.now() - start > timeoutMs) { clearInterval(interval); reject(new Error('Timed out waiting for Metro to start on port 8081')); } }); }, 2000); }); } (async () => { let metroProcess = null; try { const out = await adbList(); const lines = out.split('\n').slice(1).map(l => l.trim()).filter(Boolean); // filter out emulator entries (emulator-*) and look for 'device' state const attached = lines .map(l => l.split(/\s+/)[0]) .filter(id => id && !id.startsWith('emulator-')); const useConfig = attached.length > 0 ? 'android.attached+android.debug' : 'android.emu.debug+android.debug'; console.log('\n[run-detox] Detected attached devices:', attached.join(', ') || '(none)'); console.log('[run-detox] Using configuration:', useConfig, '\n'); // Ensure Metro is running (required for debug APK to load the JS bundle) const metroAlready = await isMetroRunning(); if (metroAlready) { console.log('[run-detox] Metro is already running on port 8081.'); } else { metroProcess = startMetro(); console.log('[run-detox] Waiting for Metro to be ready (up to 60s)...'); await waitForMetro(60000); console.log('[run-detox] Metro is ready.\n'); } console.log('Building...'); await run(`npx detox build -c ${useConfig}`); console.log('Running tests...'); await run(`npx detox test -c ${useConfig}`); } catch (err) { console.error('\n[run-detox] Error:', err.message || err); process.exit(1); } finally { if (metroProcess) { console.log('\n[run-detox] Stopping Metro bundler...'); metroProcess.kill('SIGTERM'); } } })();