Skip to content

Conversation

@kraenhansen
Copy link
Collaborator

@kraenhansen kraenhansen commented Feb 10, 2026

Fixes #262 and #213 by modifying the Xcode project after it has been generated by CocoaPods.

Merging this PR will:

  • Add a patch-xcode-project command to the host CLI: When called, it will modify the main Xcode app target to include a build phase which calls into the CLI with link --apple args.
  • Add an exit hook to the host package's podspec to conveniently run the patch-xcode-project command. Since users can run this commend themselves or we could drive it from a SPM project, I don't consider this a dependency on CocoaPods as such.
  • Modify the Apple link command to expect invocation from inside a build phase, to copy, renaming and signing frameworks (not xcframeworks) to their final destination in the app
    bundle, based on environment variables provided by Xcode.

Because the frameworks are no longer explicitly referenced by targets we also don't have the issue of the dynamic libraries being loaded at startup time. Which means faster boot times and less concern about the order of operations as they'll be loaded when required by the app.

@kraenhansen kraenhansen self-assigned this Feb 10, 2026
@kraenhansen kraenhansen added Apple 🍎 Anything related to the Apple platform (iOS, macOS, Cocoapods, Xcode, XCFrameworks, etc.) Android 🤖 Anything related to the Android platform (Gradle, NDK, Android SDK) Host 🏡 Our `react-native-node-api-modules` package Linking 🔗 Discovering and copying prebuilds from packages into the host MacOS 💻 Anything related to the Apple MacOS platform or React Native MacOS support labels Feb 10, 2026
@kraenhansen kraenhansen force-pushed the kh/apple-link-without-cocoapods branch from 06b03e4 to 7a4eacc Compare February 10, 2026 10:21
@kraenhansen kraenhansen linked an issue Feb 10, 2026 that may be closed by this pull request
@kraenhansen kraenhansen force-pushed the kh/apple-link-without-cocoapods branch from fc32743 to 6dc2cfb Compare February 10, 2026 12:47
@kraenhansen
Copy link
Collaborator Author

Tests on MacOS passing on this PR is currently blocked by #353.

@kraenhansen
Copy link
Collaborator Author

That's weird 🙄 Running the app through the CLI instead of XCode doesn't seem to fail validating the app 🤷‍♂️

@kraenhansen
Copy link
Collaborator Author

@cursor can you help review this?

@cursor
Copy link

cursor bot commented Feb 11, 2026

Now I have a complete picture. Let me provide a comprehensive code review.


Code Review: Apple link without cocoapods

Overview

This PR implements a significant change to how Node-API frameworks are linked for Apple platforms. Instead of relying on CocoaPods to manage framework dependencies directly, it modifies the Xcode project to include a custom build phase that handles copying, renaming, and code signing of frameworks during the build process.

Strengths

1. Good Architecture Design

  • Clean separation of concerns with xcode-helpers.ts for Xcode workspace/project utilities and Apple-specific linking logic in apple.ts
  • Factory pattern with createAppleLinker() is a nice abstraction that returns a configured linker function
  • The approach of patching the Xcode project post-CocoaPods means this can also work with SPM in the future

2. Comprehensive Platform Support
The determineFrameworkSlice() function in xcode-helpers.ts (lines 103-152) correctly maps all Apple platform names:

  • iOS device/simulator
  • macOS (including Mac Catalyst)
  • tvOS device/simulator
  • visionOS (xros) device/simulator

3. Robust Framework Handling

  • Handles both flat and versioned (bundle-style) frameworks correctly
  • Properly restores symlinks that NPM may have broken during packaging
  • Correctly updates install_name and regenerates debug symbols

4. Good Error Handling

  • Uses assertions with meaningful error messages
  • Uses Zod for schema validation of plist files
  • Graceful handling of missing architectures in Android linking

5. Test Coverage
The test in bin.test.ts was updated to pass the required Xcode environment variables when testing the Apple linker.

Potential Issues & Suggestions

1. Hardcoded Paths in Build Phase Script

    target.createBuildPhase(xcode.PBXShellScriptBuildPhase, {
      name: BUILD_PHASE_NAME,
      shellScript: [
        "set -e",
        `'${process.execPath}' '${CLI_PATH}' link --apple '${fromPath}'`,
      ].join("\n"),
    });

This embeds absolute paths (process.execPath, CLI_PATH, fromPath) into the Xcode project. If:

  • A team member has Node.js installed in a different location
  • The project moves to a different machine
  • The project path contains special characters

This could cause build failures. Consider using $SRCROOT relative paths or environment variables instead.

2. Missing patch-xcode-project.rb in Files Array

  "files": [
    "logo.svg",
    "bin",
    "dist",
    "!dist/**/*.test.d.ts",
    "!dist/**/*.test.d.ts.map",
    "cpp",
    "android",
    "!android/.cxx",
    "!android/build",
    "apple",
    "include",
    "babel-plugin.js",
    "scripts/patch-hermes.rb",
    "weak-node-api/**",
    "!weak-node-api/build/",
    "*.js",
    "*.podspec"
  ],

The scripts/patch-xcode-project.rb file is required by the podspec but isn't explicitly listed in the files array. It may be implicitly included, but worth verifying it's published correctly.

3. Potential Race Condition with at_exit Hook

  at_exit do
    system(PATCH_XCODE_PROJECT_COMMAND) or raise "Failed to patch the Xcode project"
  end

The at_exit hook runs after CocoaPods finishes, which is correct. However, if multiple pods require this file, the unless defined?(@exit_hooks_installed) guard should prevent duplicate execution. This looks correct.

4. TODO Comments
There are a few TODOs that should be tracked:

  • Line 60 in apple.ts: "Declare input and output files to prevent unnecessary runs" - This would improve incremental build performance
  • Line 62 in xcode-helpers.ts: "Consider continuing searching in parent directories"
  • Line 50 in android.ts: "Update the DT_NEEDED entry in the .so files"

5. Type Declaration for xmldom
The custom type declaration in types/xmldom.d.ts is well-documented and avoids polluting the global namespace with DOM types. Good practice.

6. Signing Logic

      signingIdentity:
        signingRequired !== "NO" && signingAllowed !== "NO"
          ? signingIdentity
          : undefined,

The logic checks that both CODE_SIGNING_REQUIRED and CODE_SIGNING_ALLOWED are not "NO". The default identity falls back to - (ad-hoc signing). This seems correct but might want explicit documentation about the behavior.

Minor Suggestions

  1. Consider adding a --dry-run option to patch-xcode-project to preview changes without modifying the project file

  2. Error message improvement in linkXcframework:

  assert(
    framework,
    `Failed to find a framework slice matching: ${JSON.stringify(expectedSlice)}`,
  );

Consider listing available slices in the error message to help debugging.

  1. Changeset message could be more detailed about the breaking changes or migration path for existing users.

Summary

This is a well-structured PR that addresses the issues with CocoaPods-based framework linking. The code quality is high, with good type safety, error handling, and separation of concerns. The main concern is the hardcoded absolute paths in the build phase script which could cause portability issues across different development environments.

Recommendation: This PR is ready to merge after addressing the hardcoded paths issue or documenting the limitation. The approach is sound and improves the framework linking workflow significantly.

Open in Cursor Open in Web

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Android 🤖 Anything related to the Android platform (Gradle, NDK, Android SDK) Apple 🍎 Anything related to the Apple platform (iOS, macOS, Cocoapods, Xcode, XCFrameworks, etc.) Host 🏡 Our `react-native-node-api-modules` package Linking 🔗 Discovering and copying prebuilds from packages into the host MacOS 💻 Anything related to the Apple MacOS platform or React Native MacOS support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Script phase to auto-link executes too late

1 participant