From 652b6f8b9401914c6905004dd216d25c7b180625 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Jul 2026 15:06:26 +0000 Subject: [PATCH 1/2] fix(macos-test-app): unblock the macOS test app bundle and native build Two independent failures kept the label-gated `test-macos` job red. Metro bundle: the babel plugin rewrites `require("*.node")` in the workspace packages into `require("react-native-node-api").requireNodeAddon(...)`. Those files live outside the (intentionally non-workspace) macOS app, so Metro resolves the bare `react-native-node-api` specifier by walking up from the package directory. npm's hoisted workspaces happened to place it in the repo-root node_modules; pnpm's isolated node_modules does not, so the rewritten require failed with "Unable to resolve module react-native-node-api". Add the app's own node_modules (where its `file:` deps are installed) to Metro's `nodeModulesPaths` so resolution no longer depends on the root package manager's hoisting layout. Native build: GitHub's macos-latest runner now ships Xcode 26.4 / Apple clang 21, which enforces C++20 `consteval` strictly and rejects fmt 11.0.2's FMT_STRING() usages ("call to consteval function ... is not a constant expression") in fmt, Yoga and React-logger. React Native 0.81 bundles fmt 11.0.2 and the upstream fix (fmt 12.1.0) only reached RN >= 0.83.9, so patch the generated Podfile to define FMT_USE_CONSTEVAL=0 across all pods, falling back to runtime format-string validation. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_011PM3HdJVbivXpzc2MQJV9T --- apps/test-app/metro.config.js | 17 +++++++++++++++++ scripts/init-macos-test-app.ts | 19 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/apps/test-app/metro.config.js b/apps/test-app/metro.config.js index 395431b6..009b8d4f 100644 --- a/apps/test-app/metro.config.js +++ b/apps/test-app/metro.config.js @@ -17,6 +17,23 @@ if (config.projectRoot.endsWith("macos-test-app")) { // duplicate react-native version and pollution of the package lock. const path = require("node:path"); config.watchFolders.push(path.resolve(__dirname, "../..")); + + // The babel plugin rewrites `require("something.node")` inside the workspace + // packages (e.g. packages/ferric-example/ferric_example.js) into + // `require("react-native-node-api").requireNodeAddon(...)`. Those files live + // outside this app, so Metro resolves the bare `react-native-node-api` (and + // sibling workspace packages) by walking up from the package directory. Under + // npm's hoisted workspaces that specifier happens to sit in the repo-root + // node_modules, but under pnpm's isolated node_modules it does not, so the + // rewritten require fails to resolve. This app installs the workspace packages + // into its own node_modules (via `npm install` of `file:` deps), so add that + // directory as a global module-resolution path to make resolution independent + // of the root package manager's hoisting layout. + config.resolver = config.resolver || {}; + config.resolver.nodeModulesPaths = [ + ...(config.resolver.nodeModulesPaths || []), + path.resolve(__dirname, "node_modules"), + ]; } module.exports = config; diff --git a/scripts/init-macos-test-app.ts b/scripts/init-macos-test-app.ts index 84708634..19d189b5 100644 --- a/scripts/init-macos-test-app.ts +++ b/scripts/init-macos-test-app.ts @@ -169,7 +169,24 @@ async function patchPodfile() { ], [ "react_native_post_install(installer)", - "react_native_post_install(installer, '../node_modules/react-native-macos')", + `react_native_post_install(installer, '../node_modules/react-native-macos') + + # Xcode 26.4 ships Apple clang 21, which enforces C++20 'consteval' more + # strictly and rejects fmt 11.0.2's FMT_STRING() usages with "call to + # consteval function ... is not a constant expression" (in fmt itself, Yoga + # and React-logger). React Native 0.81 bundles fmt 11.0.2 and the upstream + # fix (fmt 12.1.0) only reached React Native >= 0.83.9, so disable fmt's + # compile-time (consteval) format-string checking across all pods. fmt guards + # this define with '#ifndef FMT_USE_CONSTEVAL', so pre-defining it wins and it + # falls back to runtime format-string validation, which compiles cleanly. + installer.pods_project.targets.each do |target| + target.build_configurations.each do |build_config| + definitions = build_config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] || ['$(inherited)'] + definitions = [definitions] unless definitions.is_a?(Array) + definitions << 'FMT_USE_CONSTEVAL=0' unless definitions.include?('FMT_USE_CONSTEVAL=0') + build_config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = definitions + end + end`, ], ]; From a4d9bfb4f2276b855054621f9675ff3d93eae8dc Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Jul 2026 15:37:48 +0000 Subject: [PATCH 2/2] fix(macos-test-app): patch fmt header directly, add mocha dep, pipefail build First CI run showed the Metro-bundle fix works (the job reached xcodebuild), but surfaced two more issues: - fmt consteval still failed: the GCC_PREPROCESSOR_DEFINITIONS FMT_USE_CONSTEVAL=0 define did not reach every fmt-consuming translation unit. Patch the vendored fmt headers directly instead (flip `#define FMT_USE_CONSTEVAL 1` to 0), the approach known to work for RN 0.81 on Xcode 26.4. - "Run test app" failed with "Cannot find module 'mocha'": mocha-remote-server needs mocha at runtime. It resolves via hoisting in the workspace apps, but the standalone macOS app must depend on it explicitly, so add mocha to the deps transferred from apps/test-app. Also add `set -o pipefail` to the xcodebuild step so a build failure is not masked by xcbeautify's exit code (which is what let the previous run limp past a failed archive into the run step). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_011PM3HdJVbivXpzc2MQJV9T --- .github/workflows/check.yml | 3 ++- scripts/init-macos-test-app.ts | 28 +++++++++++++++++----------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 07e44d52..50dccd4a 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -222,7 +222,8 @@ jobs: run: npx react-native bundle --entry-file index.js --platform macos --dev false --minify false --bundle-output dist/main.macos.jsbundle --assets-dest dist/res working-directory: apps/macos-test-app - name: Build test app - run: xcodebuild archive -workspace MacOSTestApp.xcworkspace -configuration Release -scheme MacOSTestApp-macOS -destination generic/platform="macOS" -archivePath ./build/macos-test-app.xcarchive | xcbeautify + # pipefail so an xcodebuild failure is not masked by xcbeautify's exit code + run: set -o pipefail && xcodebuild archive -workspace MacOSTestApp.xcworkspace -configuration Release -scheme MacOSTestApp-macOS -destination generic/platform="macOS" -archivePath ./build/macos-test-app.xcarchive | xcbeautify working-directory: apps/macos-test-app/macos - name: Run test app run: npx mocha-remote --exit-on-error -- macos/build/macos-test-app.xcarchive/Products/Applications/MacOSTestApp.app/Contents/MacOS/MacOSTestApp diff --git a/scripts/init-macos-test-app.ts b/scripts/init-macos-test-app.ts index 19d189b5..fab1cbfe 100644 --- a/scripts/init-macos-test-app.ts +++ b/scripts/init-macos-test-app.ts @@ -88,6 +88,11 @@ async function patchPackageJson() { const transferredDependencies = new Set([ "@rnx-kit/metro-config", + // `mocha-remote-server` (pulled in by `mocha-remote-cli`) requires `mocha` at + // runtime. It resolves fine in the workspace apps via hoisting, but this app + // is installed standalone, so `mocha` must be an explicit dependency here or + // the "Run test app" step fails with "Cannot find module 'mocha'". + "mocha", "mocha-remote-cli", "mocha-remote-react-native", ]); @@ -175,17 +180,18 @@ async function patchPodfile() { # strictly and rejects fmt 11.0.2's FMT_STRING() usages with "call to # consteval function ... is not a constant expression" (in fmt itself, Yoga # and React-logger). React Native 0.81 bundles fmt 11.0.2 and the upstream - # fix (fmt 12.1.0) only reached React Native >= 0.83.9, so disable fmt's - # compile-time (consteval) format-string checking across all pods. fmt guards - # this define with '#ifndef FMT_USE_CONSTEVAL', so pre-defining it wins and it - # falls back to runtime format-string validation, which compiles cleanly. - installer.pods_project.targets.each do |target| - target.build_configurations.each do |build_config| - definitions = build_config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] || ['$(inherited)'] - definitions = [definitions] unless definitions.is_a?(Array) - definitions << 'FMT_USE_CONSTEVAL=0' unless definitions.include?('FMT_USE_CONSTEVAL=0') - build_config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = definitions - end + # fix (fmt 12.1.0) only reached React Native >= 0.83.9, so force fmt's + # compile-time (consteval) format-string checking off — it falls back to + # runtime validation, which compiles cleanly. Patch the vendored fmt headers + # directly (setting FMT_USE_CONSTEVAL to 0) rather than via a build-settings + # define, because the define does not reliably reach every fmt-consuming + # translation unit. + fmt_root = File.join(installer.sandbox.root.to_s, 'fmt') + Dir.glob(File.join(fmt_root, '**', '*.h')).each do |header| + contents = File.read(header) + next unless contents.include?('FMT_USE_CONSTEVAL') + patched = contents.gsub(/#\\s*define\\s+FMT_USE_CONSTEVAL\\s+1\\b/, '#define FMT_USE_CONSTEVAL 0') + File.write(header, patched) if patched != contents end`, ], ];