How to detect that a code change necessitates a new binary?

My team and I are working on automating how we release our Expo app. We use the managed workflow — with development builds since we have config plugins — and expo-updates for OTA updates.

Here’s the goal:

  1. When a PR is merged with no binary changes, update the OTA preview channel:

    eas update --branch preview --message "$(git rev-parse --abbrev-ref HEAD)"
    
  2. When a PR is merged with binary changes, kick off new preview and production channel builds:

    eas build --profile preview
    eas build --profile production
    

The question is: how do I detect whether or not the PR necessitates a new binary?

I’ve come up with a few ways, none of which I love:

  1. Naive approach: if there are any changes to app.config.js or package.json, trigger a new binary build.

    → This is not great, since not every change to package.json means that a new package has been added. For example, you could change something scripts or add/remove/update a package that has no native code, just JS. So, this will lead to a lot of unnecessary builds.

  2. Use expo prebuild: On both the source and target branch of the PR, run prebuild and compute a checksum of the ios and android dirs, then compare the two.

    (see example code)
    STAGING_DIR=$TMDDIR/source_checksum
    
    # Source branch
    git checkout $GITHUB_HEAD_REF # (GitHub Actions env var)
    rm -rf node_modules ios android $STAGING_DIR
    yarn && expo prebuild
    mkdir $STAGING_DIR && mv ios $STAGING_DIR && mv android $STAGING_DIR
    
    find -s $STAGING_DIR -type f -exec md5sum {} \; | md5sum > $TMPDIR/source_branch_checksum
    
    # Target branch
    git checkout $GITHUB_BASE_REF # (GitHub Actions env var)
    rm -rf node_modules ios android $STAGING_DIR
    yarn && expo prebuild
    mkdir $STAGING_DIR && mv ios $STAGING_DIR && mv android $STAGING_DIR
    
    find -s $STAGING_DIR -type f -exec md5sum {} \; | md5sum > $TMPDIR/target_branch_checksum
    
    # If the two checksums are not equal, there are binary changes
    diff $TMPDIR/source_branch_checksum $TMPDIR/target_branch_checksum || echo "::set-output name=binary_changes::true" # (GitHub Actions syntax for step output)
    

    → Unfortunately I think this only catches changes to packages that have config plugins. If a package is added that has native code but no config plugin, it may not cause any change to the files produced by expo prebuild.

  3. Use expo config: Maybe there’s a way to leverage expo config --type introspect or something similar. I think this has the same issue as (2) though, that it only detects changes to packages with config plugins.

  4. Parse dependencies: parse the dependencies in node_modules for both the source and target branch, filtered to just those that contain ios/ or android/ directories. If the versions of any of these packages have changed (or if any packages have been added/removed), trigger a new binary build. (Or, if there are any changes to app.config.js, trigger a new binary build.)

    See code: Gist

    → This feels like the best approach so far, but if a non-native package containing an ios/ or android/ directory somewhere was modified, we’d get a false positive.

Thoughts? I feel like there has to be a better way to do this, since detecting whether or not a new binary is needed must be a common Expo use case.