diff --git a/.changeset/fix-ios-bootstrap-hang.md b/.changeset/fix-ios-bootstrap-hang.md new file mode 100644 index 0000000..0a0607a --- /dev/null +++ b/.changeset/fix-ios-bootstrap-hang.md @@ -0,0 +1,23 @@ +--- +"vitest-mobile": patch +--- + +### fix(harness): prevent bootstrap hang from npx install prompts + +Add `--yes` to every `npx` call in the harness builder so npx +auto-confirms package downloads instead of prompting. Under the +spinner, stdin is `'ignore'` — any prompt would hang indefinitely: + +- `npx --yes @react-native-community/cli init` (scaffold step, runs + before the project's `node_modules` exist, so npx always needs to + resolve the package over the network) +- `npx --yes react-native build-ios` and `npx --yes react-native + build-android` (build steps; defensive — these usually resolve + locally after `npm install`) + +While here, also switch `buildAndroid` from a raw `gradlew +assembleDebug` to `npx react-native build-android` for symmetry with +iOS and to let the RN CLI handle codegen + Gradle wrapper setup. +Pass `--tasks assembleDebug` explicitly: the CLI defaults its task +prefix to `bundle` (producing an `.aab`), not `assemble` (producing +the `.apk` we need). The `--help` text is misleading on this point. diff --git a/packages/vitest-mobile/src/node/harness-builder/android.ts b/packages/vitest-mobile/src/node/harness-builder/android.ts index a3c5bf3..f55cbb9 100644 --- a/packages/vitest-mobile/src/node/harness-builder/android.ts +++ b/packages/vitest-mobile/src/node/harness-builder/android.ts @@ -7,7 +7,7 @@ import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } fr import { resolve } from 'node:path'; import { log } from '../logger'; import { applyTemplateTree, fillFilePlaceholders } from '../templates'; -import { HARNESS_APP_NAME, HARNESS_BUNDLE_ID, run, runLive } from './_shared'; +import { HARNESS_APP_NAME, HARNESS_BUNDLE_ID, runLive } from './_shared'; export function getAndroidBinaryPath(buildDir: string): string { return resolve(buildDir, 'build', `${HARNESS_APP_NAME}.apk`); @@ -46,12 +46,14 @@ export async function buildAndroid(projectDir: string, buildDir: string): Promis log.info('Building Android debug APK (this may take a few minutes)...'); const gradleStart = Date.now(); - const gradlew = resolve(androidDir, 'gradlew'); - if (!existsSync(gradlew)) { - throw new Error('gradlew not found in Android project'); - } - run(`chmod +x "${gradlew}"`, { cwd: androidDir }); - await runLive(`"${gradlew}" assembleDebug -x lint --no-daemon`, { cwd: androidDir }); + // Pass --tasks assembleDebug explicitly: `react-native build-android` + // defaults its task prefix to 'bundle' (producing an AAB), not 'assemble' + // (producing an APK). The CLI's --help is misleading on this point. + // Note: the CLI auto-prepends `app:` to each task name, so we pass the + // bare task name (passing `app:assembleDebug` would yield `app:app:...`). + await runLive('npx --yes react-native build-android --tasks assembleDebug --extra-params "-x lint --no-daemon"', { + cwd: projectDir, + }); log.info(` Android build complete (${((Date.now() - gradleStart) / 1000).toFixed(1)}s)`); const apkPath = resolve(androidDir, 'app', 'build', 'outputs', 'apk', 'debug', 'app-debug.apk'); diff --git a/packages/vitest-mobile/src/node/harness-builder/index.ts b/packages/vitest-mobile/src/node/harness-builder/index.ts index 74a79c3..33e1398 100644 --- a/packages/vitest-mobile/src/node/harness-builder/index.ts +++ b/packages/vitest-mobile/src/node/harness-builder/index.ts @@ -396,7 +396,7 @@ async function scaffoldProject(buildDir: string, options: HarnessBuildOptions): // runLive (async when a log sink is active) keeps the spinner animating // during this ~30s operation; run() would block the event loop sync. await runLive( - `npx @react-native-community/cli init ${HARNESS_APP_NAME} --version ${options.reactNativeVersion} --skip-install --skip-git-init`, + `npx --yes @react-native-community/cli init ${HARNESS_APP_NAME} --version ${options.reactNativeVersion} --skip-install --skip-git-init`, { cwd: buildDir }, ); diff --git a/packages/vitest-mobile/src/node/harness-builder/ios.ts b/packages/vitest-mobile/src/node/harness-builder/ios.ts index 65f280f..5cb4c4b 100644 --- a/packages/vitest-mobile/src/node/harness-builder/ios.ts +++ b/packages/vitest-mobile/src/node/harness-builder/ios.ts @@ -72,10 +72,16 @@ export async function buildIOS(projectDir: string): Promise { log.info('Building for iOS simulator (this may take a few minutes)...'); const stepStart = Date.now(); + // --force-pods is required: without it, the CLI only runs `pod install` + // when `react-native.config.js` sets `automaticPodsInstallation: true` + // (the bare RN init template does not). Skipping pod install causes + // xcodebuild to fail with "Unable to load contents of file list" against + // the Pods xcfilelists. const buildCmd = [ - 'npx react-native build-ios', + 'npx --yes react-native build-ios', `--scheme ${HARNESS_APP_NAME}`, '--mode Debug', + '--force-pods', `--buildFolder "${resolve(iosDir, 'DerivedData')}"`, ].join(' ');