feat: export embedded bundle from native builds - #157
Draft
floyd-soomgo wants to merge 3 commits into
Draft
Conversation
A binary patch is computed against the JS bundle inside the app binary, so releasing one means holding on to exactly the bytes that build embedded. Producing that file was left to whoever ran the build; these hooks produce it. An `apply from:` Gradle script gives every variant that bundles JS a task that copies the bundle out right after it is compiled, and an Xcode build phase script does the same for iOS. Both read the bundle from the build rather than a fixed path, and write a `binary-patch-base.json` record beside it - the same record the `bundle` command writes, plus the binary version, build number and commit that only a native build knows. Builds that embed no bundle export nothing.
The base bundle is the one input a release cannot verify on its own: hand it the bundle of a different build and the release still succeeds, only for the patch to be unappliable on every device. When a record exported by a build hook sits next to the base bundle, `release` now reads it: a base file that no longer hashes to what the record describes, or one exported from a binary version other than `--binary-version`, stops the release before anything is built or uploaded. A base bundle with no record beside it releases as before, and a record that cannot be read only warns.
Applies both hooks to the example app, which is what proves they copy the bundle the binary actually ships: the exported file hashes the same as the one inside the APK and the built .app.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Seventh PR of the binary differential OTA series (stacked on
test(e2e): cover platform binary patch updates— merge that first).The whole feature rests on one invariant: the patch base handed to
release --binary-bundle-pathmust be the exact bundle bytes shipped in the store binary. Until now, producing that file was every team's CI problem. This PR ships the solution in the library: build hooks for both platforms that copy the freshly compiled bundle — before any packaging step could touch it — into an export directory, together with a manifest describing it. A release can then cross-check the base it was given against that manifest and fail loudly on a mixup, instead of shipping a patch that every client would silently fall back from.Both hooks are opt-in — nothing changes for apps that don't apply them.
Usage
Android — one line in the app module's
build.gradle:iOS — a Run Script phase after "Bundle React Native code and images":
"${PODS_ROOT}/../../node_modules/@bravemobile/react-native-code-push/scripts/export-embedded-bundle.sh"Each bundle-producing build then leaves
<bundle>+binary-patch-base.jsonunder the platform's build output tree (-PcodePushExportDir/CODEPUSH_EXPORT_DIRto relocate). How the exported artifacts are archived and fetched at release time stays the pipeline's choice.Changes
android/codepush-export.gradle— registers a per-variant export task fed fromcreateBundle<Variant>JsAndAssets's declared outputs (jsBundleDir+bundleAssetName), so no path is hardcoded and the resolved file is the final Hermes-compiled bundle; custombundleAssetNames survive. Variants that don't bundle get no task at all — the check rides on the RN plugin's own gate rather than re-implementingdebuggableVariantsparsing. The manifest hashes the exported copy, so the record describes exactly the file a release will be handed.scripts/export-embedded-bundle.sh— copies the RN bundle phase's product (${BUNDLE_NAME:-main}.jsbundle, matching RN's own naming convention); builds that produce no bundle (simulator Debug,SKIP_BUNDLING) exit silently. Versions come from the built product'sInfo.plist.binary-patch-base.jsonbeside the exported bundle:baseBundleHash,binaryVersion,buildNumber, andgitShawhen available (its absence never fails a build). This is a superset of the record thebundlecommand already writes, same filename rule, so the two metafiles stay one schema.releasecross-validation — when a manifest sits beside--binary-bundle-path: a SHA-256 mismatch between the file and its record fails the release (wrong-file-in-pipeline protection), abinaryVersionmismatch with--binary-versionfails the release, an unparseable manifest warns and proceeds. No manifest → behavior unchanged. All branches unit-tested; the existingbundle-record warning path is untouched.scripts/contributes only this one file); README documents integration, overrides, and a pipeline example. An Expo config plugin is noted as a follow-up.Known limitations
org.gradle.configuration-cache=true); the RN template doesn't enable it, but CC users should hold off applying the hook until a follow-up lands.ext.codePushExportDirmust be set before theapply from:line;-PcodePushExportDirhas no such ordering constraint.Test plan