diff --git a/.changeset/fix-reanimated-pod-install.md b/.changeset/fix-reanimated-pod-install.md new file mode 100644 index 0000000..ed25ad4 --- /dev/null +++ b/.changeset/fix-reanimated-pod-install.md @@ -0,0 +1,5 @@ +--- +"vitest-mobile": patch +--- + +Fix `bootstrap` failing on iOS for projects that depend on `react-native-reanimated@4`. The React Native community CLI's `--force-pods` path calls `pod install` with `RCT_NEW_ARCH_ENABLED='0'` even when the template defaults the New Architecture to on, which trips Reanimated's podspec assertion. The harness builder now prepends `ENV['RCT_NEW_ARCH_ENABLED'] = '1'` to the scaffolded Podfile so the env is corrected inside the same pod-install process, before any podspecs are evaluated. diff --git a/packages/vitest-mobile/src/node/harness-builder/ios.ts b/packages/vitest-mobile/src/node/harness-builder/ios.ts index 1ca72c8..96266d3 100644 --- a/packages/vitest-mobile/src/node/harness-builder/ios.ts +++ b/packages/vitest-mobile/src/node/harness-builder/ios.ts @@ -4,7 +4,7 @@ */ import { execSync } from 'node:child_process'; -import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { log } from '../logger'; import { applyTemplateTree, fillFilePlaceholders } from '../templates'; @@ -46,14 +46,13 @@ export function customizeIOS(projectDir: string, cacheKey: string): void { if (existsSync(podfilePath)) { let podfile = readFileSync(podfilePath, 'utf8'); podfile = podfile.replace(/platform\s+:ios,\s*.+/, "platform :ios, '16.0'"); + podfile = injectNewArchEnvOverride(podfile); podfile = injectFmtCxx17Fix(podfile); writeFileSync(podfilePath, podfile); } updateIOSBundleId(projectDir); - seedNewArchSnifferBait(iosDir); - // PlistBuddy is macOS-only; skip on Linux (Android-only CI runners still // customize both platforms for the shared project, but only build one). if (process.platform === 'darwin') { @@ -80,9 +79,8 @@ export async function buildIOS(projectDir: string): Promise { // xcodebuild to fail with "Unable to load contents of file list" against // the Pods xcfilelists. // - // For why `customizeIOS` pre-seeds a sniffer-bait pbxproj instead of us - // running pod install ourselves with the right env, see - // `seedNewArchSnifferBait` below. + // For why `customizeIOS` injects an `RCT_NEW_ARCH_ENABLED` override at + // the top of the Podfile, see `injectNewArchEnvOverride`. const buildCmd = [ 'npx --yes react-native build-ios', `--scheme ${HARNESS_APP_NAME}`, @@ -135,55 +133,33 @@ export function trimIOSBuildArtifacts(projectDir: string): void { // ── Internals ─────────────────────────────────────────────────────── /** - * Pre-seed `ios/Pods/Pods.xcodeproj/project.pbxproj` with a one-line file - * containing the literal string `-DRCT_NEW_ARCH_ENABLED=1`. This defeats the - * React Native community CLI's New-Architecture detection sniffer so that - * `react-native build-ios --force-pods` runs `pod install` with new arch on - * (matching the `RCTNewArchEnabled=true` the template writes to Info.plist). - * - * Why we need this: the CLI's `installPods` step hard-overrides - * `RCT_NEW_ARCH_ENABLED` to `'0'` or `'1'` in the pod-install child env - * based on the result of `getArchitecture(iosSourceDir)`, which is literally - * - * const project = await readFile('/Pods/Pods.xcodeproj/project.pbxproj'); - * return project.includes('-DRCT_NEW_ARCH_ENABLED=1'); + * Prepend `ENV['RCT_NEW_ARCH_ENABLED'] = '1'` to the Podfile so the New + * Architecture is on regardless of what the React Native community CLI + * tells `pod install` via the child-process env. * - * — see `@react-native-community/cli-platform-apple/tools/getArchitecture.ts`. - * On a virgin scaffold that file doesn't exist (CocoaPods only creates it - * during `pod install`), the sniffer returns `false`, the CLI passes - * `RCT_NEW_ARCH_ENABLED='0'`, and Reanimated 4.x's podspec assertion - * aborts the install: - * - * [!] Invalid `RNReanimated.podspec` file: [Reanimated] Reanimated requires - * the New Architecture to be enabled. If you have `RCT_NEW_ARCH_ENABLED=0` - * set in your environment you should remove it. - * - * Pre-seeding a dummy file that contains the marker substring is enough to - * make the sniffer return `true` on the first build. After `pod install` - * runs successfully with new arch on, CocoaPods regenerates this file with - * a real Xcode project that also contains `-DRCT_NEW_ARCH_ENABLED=1` (in the - * pod target's `OTHER_CFLAGS`), so subsequent rebuilds keep returning `true` - * — the workaround is self-sustaining. + * Why we need this: in the `--force-pods` / `--only-pods` path the CLI's + * `resolvePods` calls `install()`, which calls `installPods()` *without* + * passing `newArchEnabled` — so the env always gets `RCT_NEW_ARCH_ENABLED + * ='0'`, regardless of what the architecture sniffer found. Reanimated 4 + * .x's podspec asserts on the env var (`ENV['RCT_NEW_ARCH_ENABLED'] != + * '0'`) and aborts the install. Setting the env in Ruby at the top of the + * Podfile happens *inside* the same pod-install process the CLI just + * spawned with `'0'`, but *before* `use_native_modules!` triggers + * `RNReanimated.podspec`, so the override sticks for the rest of the + * install — including the `NewArchitectureHelper.new_arch_enabled` read + * later that drives `use_react_native!`'s codegen and Info.plist write- + * back. * * Upstream fix that would remove the env injection entirely: - * https://github.com/react-native-community/cli/pull/2773 — approved by the - * maintainer but unmerged as of CLI 20.1.3. Once it ships and we adopt it - * the bait file becomes a harmless one-line scratch file that pod install - * overwrites unconditionally. + * https://github.com/react-native-community/cli/pull/2773 — approved by + * the maintainer but unmerged as of CLI 20.1.3. Once it ships and we + * adopt it, this override becomes a redundant reassignment of an env var + * that's already `'1'` (or unset, which Reanimated treats as on). */ -function seedNewArchSnifferBait(iosDir: string): void { - const podsXcodeprojDir = resolve(iosDir, 'Pods', 'Pods.xcodeproj'); - mkdirSync(podsXcodeprojDir, { recursive: true }); - const pbxprojPath = resolve(podsXcodeprojDir, 'project.pbxproj'); - if (existsSync(pbxprojPath)) return; - writeFileSync( - pbxprojPath, - '// vitest-mobile new-arch sniffer bait: ensures react-native-community/cli ' + - 'passes RCT_NEW_ARCH_ENABLED=1 to pod install on the first run. CocoaPods ' + - 'overwrites this file during pod install, and the regenerated copy carries ' + - 'the same marker since new arch ends up on.\n' + - '// -DRCT_NEW_ARCH_ENABLED=1\n', - ); +function injectNewArchEnvOverride(podfile: string): string { + const line = "ENV['RCT_NEW_ARCH_ENABLED'] = '1'\n"; + if (podfile.includes(line)) return podfile; + return line + podfile; } /**