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/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..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", ]); @@ -169,7 +174,25 @@ 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 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`, ], ];