From 840cd461dbbcc0c1a08b49d9db6aaf7c69fa6521 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 4 Sep 2026 15:34:16 +0000 Subject: [PATCH 1/6] fix(android): load native module when New Architecture is off Hardcoding isTurboModule=true hid RNZipArchive from the legacy NativeModule registry on old-arch apps, so NativeModules.RNZipArchive was null. Follow BuildConfig.IS_NEW_ARCHITECTURE_ENABLED instead. Add playground-rn old-arch Maestro CI (RNZA-5) and document the compatibility matrix. Stay on v7 only for RN < 0.70. Co-authored-by: plrthink --- .github/workflows/old-arch.yml | 241 ++++++++++++++++++ CHANGELOG.md | 6 + MIGRATION.md | 6 +- README.md | 27 +- __tests__/package-metadata.test.js | 10 +- .../com/rnziparchive/RNZipArchivePackage.java | 6 +- playground-rn/README.md | 14 + 7 files changed, 302 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/old-arch.yml diff --git a/.github/workflows/old-arch.yml b/.github/workflows/old-arch.yml new file mode 100644 index 00000000..4d650c80 --- /dev/null +++ b/.github/workflows/old-arch.yml @@ -0,0 +1,241 @@ +name: Old Architecture +# RNZA-5: prove v9 zip/unzip when New Architecture is off (playground-rn, RN 0.83). + +on: + pull_request: + branches: [master] + push: + branches: [master] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + e2e-ios-old-arch: + name: E2E iOS old-arch (rn) + runs-on: macos-15 + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 20 + + - name: Install Maestro + run: | + set -euo pipefail + for attempt in 1 2 3; do + echo "Installing Maestro (attempt $attempt)..." + if curl -fsSL "https://get.maestro.mobile.dev" | bash \ + && test -x "$HOME/.maestro/bin/maestro"; then + break + fi + echo "Maestro install failed on attempt $attempt" + rm -rf "$HOME/.maestro" + if [ "$attempt" -eq 3 ]; then + echo "Maestro install failed after 3 attempts" + exit 1 + fi + sleep $((attempt * 5)) + done + echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" + "$HOME/.maestro/bin/maestro" --version + + - name: Cache node_modules + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: playground-rn/node_modules + key: ${{ runner.os }}-rn-oldarch-node-${{ hashFiles('playground-rn/package.json') }} + + - name: Install dependencies + working-directory: playground-rn + run: | + if [ -f "pnpm-lock.yaml" ]; then + npm install -g pnpm@9 + pnpm install + else + npm install + fi + + - name: Cache CocoaPods + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: playground-rn/ios/Pods + key: ${{ runner.os }}-rn-oldarch-pods-${{ hashFiles('playground-rn/ios/Podfile.lock') }} + + - name: Install CocoaPods (RCT_NEW_ARCH_ENABLED=0) + working-directory: playground-rn/ios + run: | + gem install cocoapods -v '>= 1.13' + RCT_NEW_ARCH_ENABLED=0 pod install + + - name: Build iOS app + working-directory: playground-rn/ios + env: + RCT_NEW_ARCH_ENABLED: '0' + run: | + xcodebuild \ + -workspace PlaygroundRN.xcworkspace \ + -scheme PlaygroundRN \ + -configuration Release \ + -destination 'generic/platform=iOS Simulator' \ + -derivedDataPath build \ + CODE_SIGNING_ALLOWED=NO \ + COMPILER_INDEX_STORE_ENABLE=NO \ + RCT_NEW_ARCH_ENABLED=0 + + - name: Boot iOS Simulator + run: | + DEVICE_ID=$(xcrun simctl list devices | grep -E 'iPhone [0-9]+ \(' | grep -oE '[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}' | head -1) + echo "Booting simulator: $DEVICE_ID" + xcrun simctl boot "$DEVICE_ID" + echo "MAESTRO_IOS_DEVICE=$DEVICE_ID" >> $GITHUB_ENV + xcrun simctl bootstatus "$DEVICE_ID" + + - name: Run E2E tests + env: + MAESTRO_DRIVER_STARTUP_TIMEOUT: 300000 + run: | + APP_PATH="playground-rn/ios/build/Build/Products/Release-iphonesimulator/PlaygroundRN.app" + xcrun simctl install "$MAESTRO_IOS_DEVICE" "$APP_PATH" + export APP_ID="com.rnziparchive.playground.rn" + maestro test --device="$MAESTRO_IOS_DEVICE" --env APP_ID="$APP_ID" .maestro/flows/ci-master.yaml + + - name: Upload Maestro debug artifacts + if: failure() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: maestro-debug-ios-rn-old-arch + path: ~/.maestro/tests/ + + - name: Upload iOS crash logs + if: failure() + run: | + mkdir -p crash-logs + cp ~/Library/Logs/DiagnosticReports/*.crash crash-logs/ 2>/dev/null || true + ls crash-logs/ || echo "No crash logs found" + shell: bash + + - name: Upload crash logs artifact + if: failure() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: ios-crash-logs-rn-old-arch + path: crash-logs/ + + e2e-android-old-arch: + name: E2E Android old-arch (rn) + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: 20 + + - name: Setup Java + uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4.9.1 + with: + distribution: 'zulu' + java-version: '17' + + - name: Install Maestro + run: | + set -euo pipefail + for attempt in 1 2 3; do + echo "Installing Maestro (attempt $attempt)..." + if curl -fsSL "https://get.maestro.mobile.dev" | bash \ + && test -x "$HOME/.maestro/bin/maestro"; then + break + fi + echo "Maestro install failed on attempt $attempt" + rm -rf "$HOME/.maestro" + if [ "$attempt" -eq 3 ]; then + echo "Maestro install failed after 3 attempts" + exit 1 + fi + sleep $((attempt * 5)) + done + echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" + "$HOME/.maestro/bin/maestro" --version + + - name: Cache node_modules + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: playground-rn/node_modules + key: ${{ runner.os }}-rn-oldarch-node-${{ hashFiles('playground-rn/package.json') }} + + - name: Install dependencies + working-directory: playground-rn + run: | + if [ -f "pnpm-lock.yaml" ]; then + npm install -g pnpm@9 + pnpm install + else + npm install + fi + + - name: Cache Gradle + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-rn-oldarch-gradle-${{ hashFiles('playground-rn/android/gradle/wrapper/gradle-wrapper.properties', 'playground-rn/android/build.gradle') }} + restore-keys: | + ${{ runner.os }}-rn-oldarch-gradle- + + - name: Build Android app (newArchEnabled=false) + working-directory: playground-rn/android + env: + ORG_GRADLE_PROJECT_newArchEnabled: 'false' + run: ./gradlew :app:assembleRelease -PnewArchEnabled=false + + - name: Assert New Architecture is off in BuildConfig + working-directory: playground-rn/android + run: | + set -euo pipefail + app_bc=$(find app/build/generated -name BuildConfig.java | head -1) + test -n "$app_bc" + echo "App BuildConfig: $app_bc" + grep -n 'IS_NEW_ARCHITECTURE_ENABLED' "$app_bc" + grep -E 'IS_NEW_ARCHITECTURE_ENABLED = (false|Boolean\.parseBoolean\("false"\))' "$app_bc" + echo "Library BuildConfig candidates:" + find ../.. -name BuildConfig.java 2>/dev/null | grep -E 'rnziparchive|zip-archive' || true + lib_hit=$(find ../.. -name BuildConfig.java 2>/dev/null | xargs grep -l 'IS_NEW_ARCHITECTURE_ENABLED' 2>/dev/null | grep -E 'com/rnziparchive/BuildConfig.java' || true) + if [ -n "$lib_hit" ]; then + echo "$lib_hit" + echo "$lib_hit" | xargs grep -n 'IS_NEW_ARCHITECTURE_ENABLED' + echo "$lib_hit" | xargs grep -E 'IS_NEW_ARCHITECTURE_ENABLED = (false|Boolean\.parseBoolean\("false"\))' + fi + + - name: Run E2E tests on Android emulator + uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0 + with: + api-level: 29 + arch: x86_64 + cores: 4 + ram-size: 4096M + heap-size: 1024M + disk-size: 4096M + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: | + adb shell settings put global hide_error_dialogs 1 + adb install playground-rn/android/app/build/outputs/apk/release/app-release.apk + maestro test --env APP_ID="com.rnziparchive.playground.rn" .maestro/flows/ci-master.yaml + + - name: Upload Maestro debug artifacts + if: failure() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: maestro-debug-android-rn-old-arch + path: ~/.maestro/tests/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 91ab2de6..287d6442 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## [Unreleased] +### Fixed +- Android: register as a legacy Native Module when New Architecture is off (`isTurboModule` follows `BuildConfig.IS_NEW_ARCHITECTURE_ENABLED`) so `NativeModules.RNZipArchive` is non-null on old-arch apps (RNZA-5) + +### Added +- CI: playground-rn old-architecture Android + iOS Maestro e2e (zip/unzip) in `.github/workflows/old-arch.yml` (RNZA-5) + ## [9.5.0] - 2026-09-04 ### Added diff --git a/MIGRATION.md b/MIGRATION.md index 10166514..435333a1 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -19,7 +19,7 @@ The JavaScript API is unchanged from v7 through v9 — no call-site changes. You Working examples: [playground-expo](./playground-expo/) and [playground-rn](./playground-rn/). -Stay on v7 only for React Native **< 0.70**. On 0.70+, New Architecture is recommended. If the native module fails to load on an old-architecture 0.70+ app, fall back to v7 until Interop is confirmed. +Stay on v7 only for React Native **< 0.70**. On 0.70+, New Architecture is recommended. Old-architecture apps can stay on v9: Android registers as a legacy native module when `newArchEnabled=false`, iOS uses `RCT_EXPORT_MODULE`, and JS falls back to `NativeModules`. Gated by [playground-rn](./playground-rn/) (RN 0.83) in `.github/workflows/old-arch.yml`. ## v9.5 @@ -133,7 +133,7 @@ v8.0 migrates `react-native-zip-archive` from Legacy Native Modules to **TurboMo **JavaScript API is unchanged.** No JavaScript call-site changes are required. -Use v8+/v9 on React Native >= 0.70. Stay on v7 only if you are on React Native **< 0.70**. New Architecture is recommended; old-architecture Interop on 0.70+ is not confirmed — if the native module fails to load, fall back to v7 or enable New Architecture (see Troubleshooting). +Use v8+/v9 on React Native >= 0.70. Stay on v7 only if you are on React Native **< 0.70**. New Architecture is recommended. Old-architecture 0.70+ apps should load v9 via `NativeModules` without a separate Interop package — see the matrix in the README. ### Migration Steps @@ -200,7 +200,7 @@ npm install react-native-zip-archive@^7.0.0 | Issue | Solution | |-------|----------| -| "Native module not found" | Enable New Architecture first. On an old-architecture 0.70+ app, fall back to `^7.0.0` until Interop is confirmed. | +| "Native module not found" | Rebuild the native app after install. On RN 0.70+ with New Architecture off, v9 should load via `NativeModules` (see README old-architecture matrix). Stay on `^7.0.0` only for RN < 0.70. | | Build fails on iOS | Delete `ios/Pods` and `ios/Podfile.lock`, then `pod install` | | Build fails on Android | Run `./gradlew clean` and clear Metro cache | | Works on Android but not iOS | Ensure you ran `RCT_NEW_ARCH_ENABLED=1 pod install` | diff --git a/README.md b/README.md index d4b482c5..b45f7fcf 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Zip archive utility for React Native. > ```bash > npm install react-native-zip-archive@^7.0.0 > ``` -> If the native module fails to load on an old-architecture 0.70+ app, fall back to v7 until Interop Layer support is confirmed. +> Old-architecture apps on RN **0.70+** can stay on v9: JS falls back to `NativeModules`, Android registers as a legacy native module when `newArchEnabled=false`, and iOS uses `RCT_EXPORT_MODULE`. That path is gated by [playground-rn](./playground-rn/) CI in `.github/workflows/old-arch.yml` (zip/unzip Maestro on RN 0.83). New Architecture is still recommended. > > **iOS:** Version 7.0.0+ requires a deployment target of iOS 15.5+ to comply with App Store privacy policy. @@ -21,6 +21,31 @@ Zip archive utility for React Native. | iOS | >= 15.5 | | Android | >= API 23 (Android 6.0) | +## Old architecture (RN 0.70+) + +v9 loads when New Architecture is off. Stay on v7 only for RN **< 0.70**. + +| Surface | Old-arch path | +|---------|----------------| +| JS | `TurboModuleRegistry.get('RNZipArchive')`, then `NativeModules.RNZipArchive` | +| Android | `isTurboModule` follows `BuildConfig.IS_NEW_ARCHITECTURE_ENABLED`; paper specs compile when new arch is off | +| iOS | `RCT_EXPORT_MODULE` always; `getTurboModule` is `#ifdef RCT_NEW_ARCH_ENABLED` | + +| App | RN | Android `newArchEnabled=false` | iOS `RCT_NEW_ARCH_ENABLED=0` | New Arch on (control) | +|-----|----|--------------------------------|------------------------------|------------------------| +| [playground-rn](./playground-rn/) | 0.83.9 | CI e2e (`.github/workflows/old-arch.yml`) | CI e2e | existing `e2e.yml` | +| Production apps | 0.73–0.76 | Same native paths; not e2e-tested in this repo | Same native paths; not e2e-tested in this repo | — | + +Reproduce locally from `playground-rn`: + +```bash +# Android +cd android && ./gradlew :app:assembleRelease -PnewArchEnabled=false + +# iOS +cd ios && RCT_NEW_ARCH_ENABLED=0 pod install +``` + ## Comparison | | This library | JSZip in React Native | Nitro (`react-native-nitro-unzip` / `react-native-nitro-archive`) | diff --git a/__tests__/package-metadata.test.js b/__tests__/package-metadata.test.js index 50c09e32..969d7d87 100644 --- a/__tests__/package-metadata.test.js +++ b/__tests__/package-metadata.test.js @@ -86,10 +86,14 @@ describe('docs claims vs native source (RNZA-7/15/17/19)', () => { expect(ios).not.toMatch(/SSZipArchive unzipFileAtPath/); }); - test('README does not claim old-arch Interop is proven', () => { + test('README documents old-arch v9 load path and playground-rn CI', () => { const readme = read('README.md'); - expect(readme).toMatch(/old-architecture 0\.70\+ app/); - expect(readme).not.toMatch(/old architecture is (fully )?supported/i); + const pkgJava = read('android/src/main/java/com/rnziparchive/RNZipArchivePackage.java'); + expect(readme).toMatch(/old-arch\.yml/); + expect(readme).toMatch(/newArchEnabled=false/); + expect(readme).toMatch(/RCT_NEW_ARCH_ENABLED=0/); + expect(pkgJava).toMatch(/boolean isTurboModule = BuildConfig\.IS_NEW_ARCHITECTURE_ENABLED/); + expect(pkgJava).not.toMatch(/true\s*\/\/ isTurboModule = true/); }); test('README records AbortSignal and ZipError usage', () => { diff --git a/android/src/main/java/com/rnziparchive/RNZipArchivePackage.java b/android/src/main/java/com/rnziparchive/RNZipArchivePackage.java index 27b785b8..dbb08011 100644 --- a/android/src/main/java/com/rnziparchive/RNZipArchivePackage.java +++ b/android/src/main/java/com/rnziparchive/RNZipArchivePackage.java @@ -22,6 +22,10 @@ public NativeModule getModule(String name, ReactApplicationContext reactContext) public ReactModuleInfoProvider getReactModuleInfoProvider() { return () -> { Map map = new HashMap<>(); + // Follow the host app flag. Hardcoding isTurboModule=true hides this + // module from the legacy NativeModule registry when newArchEnabled=false, + // so NativeModules.RNZipArchive is null and JS cannot fall back. + boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; map.put(RNZipArchiveModule.NAME, new ReactModuleInfo( RNZipArchiveModule.NAME, // name RNZipArchiveModule.NAME, // className @@ -29,7 +33,7 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() { false, // needsEagerInit true, // hasConstants — required for RN 0.70.x compat false, // isCXXModule - true // isTurboModule = true + isTurboModule )); return map; }; diff --git a/playground-rn/README.md b/playground-rn/README.md index 3e2c3f85..21d0e4f2 100644 --- a/playground-rn/README.md +++ b/playground-rn/README.md @@ -4,6 +4,20 @@ This is a new [**React Native**](https://reactnative.dev) project, bootstrapped > **Note**: Make sure you have completed the [Set Up Your Environment](https://reactnative.dev/docs/set-up-your-environment) guide before proceeding. +## Old architecture (RNZA-5) + +This app defaults to New Architecture (`newArchEnabled=true`). To reproduce v9 on the old architecture: + +```sh +# Android — overrides android/gradle.properties +cd android && ./gradlew :app:assembleRelease -PnewArchEnabled=false + +# iOS — reinstall pods with New Architecture off, then build +cd ios && RCT_NEW_ARCH_ENABLED=0 pod install +``` + +CI runs the Maestro zip/unzip suite against those builds in `.github/workflows/old-arch.yml` at the repo root. + ## Step 1: Start Metro First, you will need to run **Metro**, the JavaScript build tool for React Native. From 476f6ca06fba649c4f06c87216631b8a31bdb561 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 4 Sep 2026 16:06:53 +0000 Subject: [PATCH 2/6] fix(ci): disable Android New Architecture via gradle.properties RN's gradle plugin reads newArchEnabled from the file, so -PnewArchEnabled=false still produced IS_NEW_ARCHITECTURE_ENABLED=true and the old-arch Android e2e never actually ran old arch. iOS old-arch Maestro already passed. Co-authored-by: plrthink --- .github/workflows/old-arch.yml | 14 +++++++++++--- README.md | 4 ++-- playground-rn/README.md | 6 ++++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.github/workflows/old-arch.yml b/.github/workflows/old-arch.yml index 4d650c80..65e127eb 100644 --- a/.github/workflows/old-arch.yml +++ b/.github/workflows/old-arch.yml @@ -193,11 +193,19 @@ jobs: restore-keys: | ${{ runner.os }}-rn-oldarch-gradle- + - name: Disable New Architecture in gradle.properties + working-directory: playground-rn/android + run: | + set -euo pipefail + # RN's gradle plugin reads gradle.properties directly; -PnewArchEnabled=false + # does not flip IS_NEW_ARCHITECTURE_ENABLED on the app BuildConfig. + grep -q '^newArchEnabled=' gradle.properties + sed -i 's/^newArchEnabled=.*/newArchEnabled=false/' gradle.properties + grep '^newArchEnabled=false$' gradle.properties + - name: Build Android app (newArchEnabled=false) working-directory: playground-rn/android - env: - ORG_GRADLE_PROJECT_newArchEnabled: 'false' - run: ./gradlew :app:assembleRelease -PnewArchEnabled=false + run: ./gradlew :app:assembleRelease - name: Assert New Architecture is off in BuildConfig working-directory: playground-rn/android diff --git a/README.md b/README.md index b45f7fcf..55b99fe3 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,8 @@ v9 loads when New Architecture is off. Stay on v7 only for RN **< 0.70**. Reproduce locally from `playground-rn`: ```bash -# Android -cd android && ./gradlew :app:assembleRelease -PnewArchEnabled=false +# Android — set newArchEnabled=false in android/gradle.properties, then: +cd android && ./gradlew :app:assembleRelease # iOS cd ios && RCT_NEW_ARCH_ENABLED=0 pod install diff --git a/playground-rn/README.md b/playground-rn/README.md index 21d0e4f2..6a1cdffa 100644 --- a/playground-rn/README.md +++ b/playground-rn/README.md @@ -9,8 +9,10 @@ This is a new [**React Native**](https://reactnative.dev) project, bootstrapped This app defaults to New Architecture (`newArchEnabled=true`). To reproduce v9 on the old architecture: ```sh -# Android — overrides android/gradle.properties -cd android && ./gradlew :app:assembleRelease -PnewArchEnabled=false +# Android — RN reads gradle.properties directly; -P is not enough +cd android +# set newArchEnabled=false in gradle.properties, then: +./gradlew :app:assembleRelease # iOS — reinstall pods with New Architecture off, then build cd ios && RCT_NEW_ARCH_ENABLED=0 pod install From ebf498d2d096f4beb78d834927493d22b1607cf4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 4 Sep 2026 16:37:53 +0000 Subject: [PATCH 3/6] ci: prove old-arch compile on RN 0.81, not 0.83 RN 0.82+ ignores newArchEnabled=false and RCT_NEW_ARCH_ENABLED=0, so playground-rn (0.83) cannot opt out. Move old-arch CI to a throwaway 0.81.6 app (last opt-out line) and document the matrix. Co-authored-by: plrthink --- .github/workflows/old-arch.yml | 297 ++++++++++++--------------------- CHANGELOG.md | 2 +- MIGRATION.md | 4 +- README.md | 26 +-- playground-rn/README.md | 14 +- 5 files changed, 121 insertions(+), 222 deletions(-) diff --git a/.github/workflows/old-arch.yml b/.github/workflows/old-arch.yml index 65e127eb..1e2c894d 100644 --- a/.github/workflows/old-arch.yml +++ b/.github/workflows/old-arch.yml @@ -1,5 +1,7 @@ name: Old Architecture -# RNZA-5: prove v9 zip/unzip when New Architecture is off (playground-rn, RN 0.83). +# RNZA-5: prove v9 still builds when New Architecture is off. +# playground-rn is RN 0.83 — since 0.82, newArchEnabled=false / RCT_NEW_ARCH_ENABLED=0 +# are ignored. Last opt-out line is RN 0.81. on: pull_request: @@ -12,126 +14,15 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -jobs: - e2e-ios-old-arch: - name: E2E iOS old-arch (rn) - runs-on: macos-15 - timeout-minutes: 60 - steps: - - name: Checkout - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 - - - name: Setup Node.js - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 - with: - node-version: 20 - - - name: Install Maestro - run: | - set -euo pipefail - for attempt in 1 2 3; do - echo "Installing Maestro (attempt $attempt)..." - if curl -fsSL "https://get.maestro.mobile.dev" | bash \ - && test -x "$HOME/.maestro/bin/maestro"; then - break - fi - echo "Maestro install failed on attempt $attempt" - rm -rf "$HOME/.maestro" - if [ "$attempt" -eq 3 ]; then - echo "Maestro install failed after 3 attempts" - exit 1 - fi - sleep $((attempt * 5)) - done - echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" - "$HOME/.maestro/bin/maestro" --version - - - name: Cache node_modules - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - path: playground-rn/node_modules - key: ${{ runner.os }}-rn-oldarch-node-${{ hashFiles('playground-rn/package.json') }} - - - name: Install dependencies - working-directory: playground-rn - run: | - if [ -f "pnpm-lock.yaml" ]; then - npm install -g pnpm@9 - pnpm install - else - npm install - fi - - - name: Cache CocoaPods - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - path: playground-rn/ios/Pods - key: ${{ runner.os }}-rn-oldarch-pods-${{ hashFiles('playground-rn/ios/Podfile.lock') }} - - - name: Install CocoaPods (RCT_NEW_ARCH_ENABLED=0) - working-directory: playground-rn/ios - run: | - gem install cocoapods -v '>= 1.13' - RCT_NEW_ARCH_ENABLED=0 pod install - - - name: Build iOS app - working-directory: playground-rn/ios - env: - RCT_NEW_ARCH_ENABLED: '0' - run: | - xcodebuild \ - -workspace PlaygroundRN.xcworkspace \ - -scheme PlaygroundRN \ - -configuration Release \ - -destination 'generic/platform=iOS Simulator' \ - -derivedDataPath build \ - CODE_SIGNING_ALLOWED=NO \ - COMPILER_INDEX_STORE_ENABLE=NO \ - RCT_NEW_ARCH_ENABLED=0 - - - name: Boot iOS Simulator - run: | - DEVICE_ID=$(xcrun simctl list devices | grep -E 'iPhone [0-9]+ \(' | grep -oE '[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}' | head -1) - echo "Booting simulator: $DEVICE_ID" - xcrun simctl boot "$DEVICE_ID" - echo "MAESTRO_IOS_DEVICE=$DEVICE_ID" >> $GITHUB_ENV - xcrun simctl bootstatus "$DEVICE_ID" - - - name: Run E2E tests - env: - MAESTRO_DRIVER_STARTUP_TIMEOUT: 300000 - run: | - APP_PATH="playground-rn/ios/build/Build/Products/Release-iphonesimulator/PlaygroundRN.app" - xcrun simctl install "$MAESTRO_IOS_DEVICE" "$APP_PATH" - export APP_ID="com.rnziparchive.playground.rn" - maestro test --device="$MAESTRO_IOS_DEVICE" --env APP_ID="$APP_ID" .maestro/flows/ci-master.yaml - - - name: Upload Maestro debug artifacts - if: failure() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: maestro-debug-ios-rn-old-arch - path: ~/.maestro/tests/ - - - name: Upload iOS crash logs - if: failure() - run: | - mkdir -p crash-logs - cp ~/Library/Logs/DiagnosticReports/*.crash crash-logs/ 2>/dev/null || true - ls crash-logs/ || echo "No crash logs found" - shell: bash - - - name: Upload crash logs artifact - if: failure() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - with: - name: ios-crash-logs-rn-old-arch - path: crash-logs/ +env: + RN_OLD_ARCH_VERSION: '0.81.6' + RN_OLD_ARCH_CLI: '15.1.3' - e2e-android-old-arch: - name: E2E Android old-arch (rn) +jobs: + compile-android-0-81: + name: Compile Android old-arch (RN 0.81) runs-on: ubuntu-latest - timeout-minutes: 60 + timeout-minutes: 45 steps: - name: Checkout uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 @@ -147,68 +38,46 @@ jobs: distribution: 'zulu' java-version: '17' - - name: Install Maestro + - name: Pack library run: | set -euo pipefail - for attempt in 1 2 3; do - echo "Installing Maestro (attempt $attempt)..." - if curl -fsSL "https://get.maestro.mobile.dev" | bash \ - && test -x "$HOME/.maestro/bin/maestro"; then - break - fi - echo "Maestro install failed on attempt $attempt" - rm -rf "$HOME/.maestro" - if [ "$attempt" -eq 3 ]; then - echo "Maestro install failed after 3 attempts" - exit 1 - fi - sleep $((attempt * 5)) - done - echo "$HOME/.maestro/bin" >> "$GITHUB_PATH" - "$HOME/.maestro/bin/maestro" --version - - - name: Cache node_modules - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - path: playground-rn/node_modules - key: ${{ runner.os }}-rn-oldarch-node-${{ hashFiles('playground-rn/package.json') }} + TARBALL=$(npm pack --silent) + echo "TARBALL=$TARBALL" >> "$GITHUB_ENV" + echo "Packed $TARBALL" - - name: Install dependencies - working-directory: playground-rn + - name: Init RN ${{ env.RN_OLD_ARCH_VERSION }} app + working-directory: ${{ runner.temp }} + env: + CI: '1' run: | - if [ -f "pnpm-lock.yaml" ]; then - npm install -g pnpm@9 - pnpm install - else - npm install - fi + set -euo pipefail + npx --yes "@react-native-community/cli@${RN_OLD_ARCH_CLI}" init RnzaOldArch \ + --version "$RN_OLD_ARCH_VERSION" \ + --pm npm \ + --skip-git-init \ + --install-pods=false + test -d RnzaOldArch + + - name: Install packed library + working-directory: ${{ runner.temp }}/RnzaOldArch + run: | + set -euo pipefail + npm install "${GITHUB_WORKSPACE}/${TARBALL}" - - name: Cache Gradle - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-rn-oldarch-gradle-${{ hashFiles('playground-rn/android/gradle/wrapper/gradle-wrapper.properties', 'playground-rn/android/build.gradle') }} - restore-keys: | - ${{ runner.os }}-rn-oldarch-gradle- - - - name: Disable New Architecture in gradle.properties - working-directory: playground-rn/android + - name: Disable New Architecture + working-directory: ${{ runner.temp }}/RnzaOldArch/android run: | set -euo pipefail - # RN's gradle plugin reads gradle.properties directly; -PnewArchEnabled=false - # does not flip IS_NEW_ARCHITECTURE_ENABLED on the app BuildConfig. grep -q '^newArchEnabled=' gradle.properties sed -i 's/^newArchEnabled=.*/newArchEnabled=false/' gradle.properties grep '^newArchEnabled=false$' gradle.properties - - name: Build Android app (newArchEnabled=false) - working-directory: playground-rn/android - run: ./gradlew :app:assembleRelease + - name: Assemble release + working-directory: ${{ runner.temp }}/RnzaOldArch/android + run: ./gradlew :app:assembleRelease --no-configuration-cache - - name: Assert New Architecture is off in BuildConfig - working-directory: playground-rn/android + - name: Assert New Architecture is off + working-directory: ${{ runner.temp }}/RnzaOldArch/android run: | set -euo pipefail app_bc=$(find app/build/generated -name BuildConfig.java | head -1) @@ -216,34 +85,74 @@ jobs: echo "App BuildConfig: $app_bc" grep -n 'IS_NEW_ARCHITECTURE_ENABLED' "$app_bc" grep -E 'IS_NEW_ARCHITECTURE_ENABLED = (false|Boolean\.parseBoolean\("false"\))' "$app_bc" - echo "Library BuildConfig candidates:" - find ../.. -name BuildConfig.java 2>/dev/null | grep -E 'rnziparchive|zip-archive' || true - lib_hit=$(find ../.. -name BuildConfig.java 2>/dev/null | xargs grep -l 'IS_NEW_ARCHITECTURE_ENABLED' 2>/dev/null | grep -E 'com/rnziparchive/BuildConfig.java' || true) + echo "Library BuildConfig:" + lib_hit=$(find . "${GITHUB_WORKSPACE}/android" -name BuildConfig.java 2>/dev/null \ + | xargs grep -l 'package com.rnziparchive;' 2>/dev/null || true) + echo "$lib_hit" if [ -n "$lib_hit" ]; then - echo "$lib_hit" echo "$lib_hit" | xargs grep -n 'IS_NEW_ARCHITECTURE_ENABLED' echo "$lib_hit" | xargs grep -E 'IS_NEW_ARCHITECTURE_ENABLED = (false|Boolean\.parseBoolean\("false"\))' fi - - name: Run E2E tests on Android emulator - uses: reactivecircus/android-emulator-runner@a421e43855164a8197daf9d8d40fe71c6996bb0d # v2.38.0 - with: - api-level: 29 - arch: x86_64 - cores: 4 - ram-size: 4096M - heap-size: 1024M - disk-size: 4096M - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: | - adb shell settings put global hide_error_dialogs 1 - adb install playground-rn/android/app/build/outputs/apk/release/app-release.apk - maestro test --env APP_ID="com.rnziparchive.playground.rn" .maestro/flows/ci-master.yaml - - - name: Upload Maestro debug artifacts - if: failure() - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + compile-ios-0-81: + name: Compile iOS old-arch (RN 0.81) + runs-on: macos-15 + timeout-minutes: 45 + steps: + - name: Checkout + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 + + - name: Setup Node.js + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 with: - name: maestro-debug-android-rn-old-arch - path: ~/.maestro/tests/ + node-version: 20 + + - name: Pack library + run: | + set -euo pipefail + TARBALL=$(npm pack --silent) + echo "TARBALL=$TARBALL" >> "$GITHUB_ENV" + echo "Packed $TARBALL" + + - name: Init RN ${{ env.RN_OLD_ARCH_VERSION }} app + working-directory: ${{ runner.temp }} + env: + CI: '1' + run: | + set -euo pipefail + npx --yes "@react-native-community/cli@${RN_OLD_ARCH_CLI}" init RnzaOldArch \ + --version "$RN_OLD_ARCH_VERSION" \ + --pm npm \ + --skip-git-init \ + --install-pods=false + test -d RnzaOldArch + + - name: Install packed library + working-directory: ${{ runner.temp }}/RnzaOldArch + run: | + set -euo pipefail + npm install "${GITHUB_WORKSPACE}/${TARBALL}" + + - name: Install CocoaPods (RCT_NEW_ARCH_ENABLED=0) + working-directory: ${{ runner.temp }}/RnzaOldArch/ios + run: | + set -euo pipefail + gem install cocoapods -v '>= 1.13' + RCT_NEW_ARCH_ENABLED=0 pod install + + - name: Build iOS app + working-directory: ${{ runner.temp }}/RnzaOldArch/ios + env: + RCT_NEW_ARCH_ENABLED: '0' + run: | + set -euo pipefail + WORKSPACE=$(ls -1 *.xcworkspace | head -1) + SCHEME="${WORKSPACE%.xcworkspace}" + xcodebuild \ + -workspace "$WORKSPACE" \ + -scheme "$SCHEME" \ + -configuration Release \ + -destination 'generic/platform=iOS Simulator' \ + -derivedDataPath build \ + CODE_SIGNING_ALLOWED=NO \ + COMPILER_INDEX_STORE_ENABLE=NO diff --git a/CHANGELOG.md b/CHANGELOG.md index 287d6442..4df2ae02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - Android: register as a legacy Native Module when New Architecture is off (`isTurboModule` follows `BuildConfig.IS_NEW_ARCHITECTURE_ENABLED`) so `NativeModules.RNZipArchive` is non-null on old-arch apps (RNZA-5) ### Added -- CI: playground-rn old-architecture Android + iOS Maestro e2e (zip/unzip) in `.github/workflows/old-arch.yml` (RNZA-5) +- CI: RN 0.81.6 old-architecture Android + iOS compile in `.github/workflows/old-arch.yml` (RNZA-5). RN 0.82+ cannot opt out of New Architecture. ## [9.5.0] - 2026-09-04 diff --git a/MIGRATION.md b/MIGRATION.md index 435333a1..6fb0af06 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -19,7 +19,7 @@ The JavaScript API is unchanged from v7 through v9 — no call-site changes. You Working examples: [playground-expo](./playground-expo/) and [playground-rn](./playground-rn/). -Stay on v7 only for React Native **< 0.70**. On 0.70+, New Architecture is recommended. Old-architecture apps can stay on v9: Android registers as a legacy native module when `newArchEnabled=false`, iOS uses `RCT_EXPORT_MODULE`, and JS falls back to `NativeModules`. Gated by [playground-rn](./playground-rn/) (RN 0.83) in `.github/workflows/old-arch.yml`. +Stay on v7 only for React Native **< 0.70**. On 0.70–0.81, New Architecture is recommended but old-architecture apps can stay on v9: Android registers as a legacy native module when `newArchEnabled=false`, iOS uses `RCT_EXPORT_MODULE`, and JS falls back to `NativeModules`. RN **0.82+** cannot opt out of New Architecture. Old-arch compile is gated by `.github/workflows/old-arch.yml` on RN 0.81.6. ## v9.5 @@ -133,7 +133,7 @@ v8.0 migrates `react-native-zip-archive` from Legacy Native Modules to **TurboMo **JavaScript API is unchanged.** No JavaScript call-site changes are required. -Use v8+/v9 on React Native >= 0.70. Stay on v7 only if you are on React Native **< 0.70**. New Architecture is recommended. Old-architecture 0.70+ apps should load v9 via `NativeModules` without a separate Interop package — see the matrix in the README. +Use v8+/v9 on React Native >= 0.70. Stay on v7 only if you are on React Native **< 0.70**. New Architecture is recommended. On RN 0.70–0.81, old-architecture apps should load v9 via `NativeModules` without a separate Interop package. RN 0.82+ is New Architecture only — see the matrix in the README. ### Migration Steps diff --git a/README.md b/README.md index 55b99fe3..a716612d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Zip archive utility for React Native. > ```bash > npm install react-native-zip-archive@^7.0.0 > ``` -> Old-architecture apps on RN **0.70+** can stay on v9: JS falls back to `NativeModules`, Android registers as a legacy native module when `newArchEnabled=false`, and iOS uses `RCT_EXPORT_MODULE`. That path is gated by [playground-rn](./playground-rn/) CI in `.github/workflows/old-arch.yml` (zip/unzip Maestro on RN 0.83). New Architecture is still recommended. +> Old-architecture apps on RN **0.70–0.81** can stay on v9: JS falls back to `NativeModules`, Android registers as a legacy native module when `newArchEnabled=false`, and iOS uses `RCT_EXPORT_MODULE`. That path is compiled in CI on RN **0.81.6** (`.github/workflows/old-arch.yml`). RN **0.82+** cannot opt out of New Architecture ([RN 0.82](https://reactnative.dev/blog/2025/10/08/react-native-0.82)) — [playground-rn](./playground-rn/) (0.83) is New Arch only. New Architecture is recommended on every supported RN. > > **iOS:** Version 7.0.0+ requires a deployment target of iOS 15.5+ to comply with App Store privacy policy. @@ -21,9 +21,9 @@ Zip archive utility for React Native. | iOS | >= 15.5 | | Android | >= API 23 (Android 6.0) | -## Old architecture (RN 0.70+) +## Old architecture (RN 0.70–0.81) -v9 loads when New Architecture is off. Stay on v7 only for RN **< 0.70**. +v9 loads when New Architecture is off. Stay on v7 only for RN **< 0.70**. RN **0.82+** ignores `newArchEnabled=false` / `RCT_NEW_ARCH_ENABLED=0` and always runs New Architecture. | Surface | Old-arch path | |---------|----------------| @@ -31,19 +31,19 @@ v9 loads when New Architecture is off. Stay on v7 only for RN **< 0.70**. | Android | `isTurboModule` follows `BuildConfig.IS_NEW_ARCHITECTURE_ENABLED`; paper specs compile when new arch is off | | iOS | `RCT_EXPORT_MODULE` always; `getTurboModule` is `#ifdef RCT_NEW_ARCH_ENABLED` | -| App | RN | Android `newArchEnabled=false` | iOS `RCT_NEW_ARCH_ENABLED=0` | New Arch on (control) | -|-----|----|--------------------------------|------------------------------|------------------------| -| [playground-rn](./playground-rn/) | 0.83.9 | CI e2e (`.github/workflows/old-arch.yml`) | CI e2e | existing `e2e.yml` | -| Production apps | 0.73–0.76 | Same native paths; not e2e-tested in this repo | Same native paths; not e2e-tested in this repo | — | +| RN | Android `newArchEnabled=false` | iOS `RCT_NEW_ARCH_ENABLED=0` | New Arch on | +|----|--------------------------------|------------------------------|-------------| +| 0.82+ ([playground-rn](./playground-rn/) 0.83.9) | N/A — flag ignored | N/A — flag ignored | existing `e2e.yml` (zip/unzip Maestro) | +| 0.81.6 (last opt-out) | CI compile (`.github/workflows/old-arch.yml`) | CI compile | — | +| 0.73–0.80 | same native paths as 0.81; not separately built | same | — | -Reproduce locally from `playground-rn`: +Reproduce the 0.81 compile locally (same as CI): ```bash -# Android — set newArchEnabled=false in android/gradle.properties, then: -cd android && ./gradlew :app:assembleRelease - -# iOS -cd ios && RCT_NEW_ARCH_ENABLED=0 pod install +npx @react-native-community/cli@15.1.3 init RnzaOldArch --version 0.81.6 --pm npm --skip-git-init +cd RnzaOldArch && npm install /path/to/react-native-zip-archive +# Android: set newArchEnabled=false in android/gradle.properties, then assembleRelease +# iOS: RCT_NEW_ARCH_ENABLED=0 pod install ``` ## Comparison diff --git a/playground-rn/README.md b/playground-rn/README.md index 6a1cdffa..a6d520a2 100644 --- a/playground-rn/README.md +++ b/playground-rn/README.md @@ -6,19 +6,9 @@ This is a new [**React Native**](https://reactnative.dev) project, bootstrapped ## Old architecture (RNZA-5) -This app defaults to New Architecture (`newArchEnabled=true`). To reproduce v9 on the old architecture: +This app is **RN 0.83.9**. Since React Native 0.82, `newArchEnabled=false` and `RCT_NEW_ARCH_ENABLED=0` are ignored — New Architecture is the only option. -```sh -# Android — RN reads gradle.properties directly; -P is not enough -cd android -# set newArchEnabled=false in gradle.properties, then: -./gradlew :app:assembleRelease - -# iOS — reinstall pods with New Architecture off, then build -cd ios && RCT_NEW_ARCH_ENABLED=0 pod install -``` - -CI runs the Maestro zip/unzip suite against those builds in `.github/workflows/old-arch.yml` at the repo root. +Old-architecture compile proof lives in `.github/workflows/old-arch.yml` against **RN 0.81.6** (last version that can opt out). ## Step 1: Start Metro From 290f23ca64b8065bddd56b64733562e8d865bed7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 4 Sep 2026 17:09:03 +0000 Subject: [PATCH 4/6] ci: raise RN 0.81 iOS target to 15.5 for old-arch compile Android 0.81 old-arch assemble already passed with IS_NEW_ARCHITECTURE_ENABLED=false. iOS pod install failed because RN 0.81 defaults to iOS 15.1 and RNZipArchive requires 15.5. Co-authored-by: plrthink --- .github/workflows/old-arch.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/old-arch.yml b/.github/workflows/old-arch.yml index 1e2c894d..1a3cc70a 100644 --- a/.github/workflows/old-arch.yml +++ b/.github/workflows/old-arch.yml @@ -137,6 +137,9 @@ jobs: working-directory: ${{ runner.temp }}/RnzaOldArch/ios run: | set -euo pipefail + # RN 0.81 defaults to iOS 15.1; this library requires 15.5 (App Store privacy). + sed -i '' "s/platform :ios, min_ios_version_supported/platform :ios, '15.5'/" Podfile + grep "platform :ios" Podfile gem install cocoapods -v '>= 1.13' RCT_NEW_ARCH_ENABLED=0 pod install From 602061130785f896f951464b9b7022f61b7b76c2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 4 Sep 2026 17:40:01 +0000 Subject: [PATCH 5/6] ci: pick the iOS .xcworkspace bundle, not its contents ls *.xcworkspace listed contents.xcworkspacedata inside the bundle. Use ls -d so xcodebuild gets RnzaOldArch.xcworkspace. Co-authored-by: plrthink --- .github/workflows/old-arch.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/old-arch.yml b/.github/workflows/old-arch.yml index 1a3cc70a..d81b14df 100644 --- a/.github/workflows/old-arch.yml +++ b/.github/workflows/old-arch.yml @@ -149,8 +149,9 @@ jobs: RCT_NEW_ARCH_ENABLED: '0' run: | set -euo pipefail - WORKSPACE=$(ls -1 *.xcworkspace | head -1) + WORKSPACE=$(ls -d *.xcworkspace) SCHEME="${WORKSPACE%.xcworkspace}" + echo "Using workspace=$WORKSPACE scheme=$SCHEME" xcodebuild \ -workspace "$WORKSPACE" \ -scheme "$SCHEME" \ From 0ba2b8cd353e02b76e76893c7319ae5a98eccba1 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 5 Sep 2026 05:58:56 +0000 Subject: [PATCH 6/6] docs: install table by RN version from old-arch evidence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v7 only for RN < 0.70. Latest v9 on 0.70–0.81 including old architecture (0.81 compile CI). New Architecture only on 0.82+. Stop treating New Architecture as required on 0.70–0.81. Co-authored-by: plrthink --- CHANGELOG.md | 3 +++ MIGRATION.md | 24 ++++++++++++++-------- README.md | 32 +++++++++++++++--------------- __tests__/package-metadata.test.js | 6 ++++++ 4 files changed, 41 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4df2ae02..101cb5b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ ### Added - CI: RN 0.81.6 old-architecture Android + iOS compile in `.github/workflows/old-arch.yml` (RNZA-5). RN 0.82+ cannot opt out of New Architecture. +### Changed +- README + MIGRATION: install table by RN version — v7 only for RN < 0.70; v9 on 0.70–0.81 including old architecture; New Architecture only on 0.82+ + ## [9.5.0] - 2026-09-04 ### Added diff --git a/MIGRATION.md b/MIGRATION.md index 6fb0af06..f8370cb8 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -19,7 +19,13 @@ The JavaScript API is unchanged from v7 through v9 — no call-site changes. You Working examples: [playground-expo](./playground-expo/) and [playground-rn](./playground-rn/). -Stay on v7 only for React Native **< 0.70**. On 0.70–0.81, New Architecture is recommended but old-architecture apps can stay on v9: Android registers as a legacy native module when `newArchEnabled=false`, iOS uses `RCT_EXPORT_MODULE`, and JS falls back to `NativeModules`. RN **0.82+** cannot opt out of New Architecture. Old-arch compile is gated by `.github/workflows/old-arch.yml` on RN 0.81.6. +| Your React Native | What to install | +|-------------------|-----------------| +| **< 0.70** | Stay on `^7.0.0` (or upgrade RN first) | +| **0.70–0.81** | Latest v9. New Architecture recommended; old architecture works. Rebuild native. | +| **0.82+** | Latest v9. New Architecture only — RN ignores `newArchEnabled=false` / `RCT_NEW_ARCH_ENABLED=0`. | + +Old-arch proof is compile + link on RN **0.81.6** (`.github/workflows/old-arch.yml`), not device Maestro. See the [README matrix](./README.md#old-architecture-rn-070081). ## v9.5 @@ -133,7 +139,7 @@ v8.0 migrates `react-native-zip-archive` from Legacy Native Modules to **TurboMo **JavaScript API is unchanged.** No JavaScript call-site changes are required. -Use v8+/v9 on React Native >= 0.70. Stay on v7 only if you are on React Native **< 0.70**. New Architecture is recommended. On RN 0.70–0.81, old-architecture apps should load v9 via `NativeModules` without a separate Interop package. RN 0.82+ is New Architecture only — see the matrix in the README. +Use v8+/v9 on React Native >= 0.70. Stay on v7 only if you are on React Native **< 0.70**. New Architecture is recommended, not required on 0.70–0.81. Do not add a separate Interop package. RN 0.82+ is New Architecture only — see the [README matrix](./README.md#old-architecture-rn-070081). ### Migration Steps @@ -145,16 +151,18 @@ npx react-native --version If you're on React Native < 0.70, stay on v7.x of this library (or upgrade React Native to 0.70+ first). -#### Step 2: New Architecture (recommended) +#### Step 2: New Architecture (recommended, not required on 0.70–0.81) + +This library does **not** require New Architecture on RN 0.70–0.81. On RN 0.82+ you cannot turn it off ([RN 0.82](https://reactnative.dev/blog/2025/10/08/react-native-0.82)). -Follow the [official React Native guide](https://reactnative.dev/docs/new-architecture-intro). +If you want New Architecture on 0.70–0.81, follow the [official React Native guide](https://reactnative.dev/docs/new-architecture-intro): **Android**: In `android/gradle.properties`: ```properties newArchEnabled=true ``` -**iOS**: Reinstall pods with New Architecture enabled: +**iOS**: ```bash cd ios RCT_NEW_ARCH_ENABLED=1 pod install @@ -200,10 +208,10 @@ npm install react-native-zip-archive@^7.0.0 | Issue | Solution | |-------|----------| -| "Native module not found" | Rebuild the native app after install. On RN 0.70+ with New Architecture off, v9 should load via `NativeModules` (see README old-architecture matrix). Stay on `^7.0.0` only for RN < 0.70. | -| Build fails on iOS | Delete `ios/Pods` and `ios/Podfile.lock`, then `pod install` | +| "Native module not found" | Rebuild the native app (`pod install` + Android rebuild). Stay on `^7.0.0` only for RN **< 0.70**. On 0.70–0.81 with New Architecture off, v9 loads via `NativeModules` after a native rebuild (see [README](./README.md#old-architecture-rn-070081)). If it is still null after rebuild, open an issue with RN version and `newArchEnabled`. | +| Build fails on iOS | Delete `ios/Pods` and `ios/Podfile.lock`, then `pod install`. This library requires iOS **15.5+**. | | Build fails on Android | Run `./gradlew clean` and clear Metro cache | -| Works on Android but not iOS | Ensure you ran `RCT_NEW_ARCH_ENABLED=1 pod install` | +| Works on Android but not iOS | Confirm a native rebuild. On RN 0.82+ New Architecture cannot be disabled. On 0.70–0.81, `RCT_NEW_ARCH_ENABLED=1` is optional for this library. | | Expo Go shows "Native module not found" | Use Expo Development Build instead | ### Need Help? diff --git a/README.md b/README.md index a716612d..46a0601a 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ Zip archive utility for React Native. -> Latest **v8+ / v9** targets React Native ≥ 0.70 with TurboModules. **New Architecture is recommended.** +> **v9** is for React Native ≥ 0.70. New Architecture is recommended. Stay on v7 only for RN **< 0.70**. > -> Use `^7.0.0` only if you are on React Native **< 0.70**: -> ```bash -> npm install react-native-zip-archive@^7.0.0 -> ``` -> Old-architecture apps on RN **0.70–0.81** can stay on v9: JS falls back to `NativeModules`, Android registers as a legacy native module when `newArchEnabled=false`, and iOS uses `RCT_EXPORT_MODULE`. That path is compiled in CI on RN **0.81.6** (`.github/workflows/old-arch.yml`). RN **0.82+** cannot opt out of New Architecture ([RN 0.82](https://reactnative.dev/blog/2025/10/08/react-native-0.82)) — [playground-rn](./playground-rn/) (0.83) is New Arch only. New Architecture is recommended on every supported RN. +> | Your React Native | Install | +> |-------------------|---------| +> | **< 0.70** | `npm install react-native-zip-archive@^7.0.0` | +> | **0.70–0.81** | latest v9 (old architecture works; native rebuild required) | +> | **0.82+** | latest v9 (New Architecture only — RN ignores the opt-out flags) | > > **iOS:** Version 7.0.0+ requires a deployment target of iOS 15.5+ to comply with App Store privacy policy. @@ -23,27 +23,27 @@ Zip archive utility for React Native. ## Old architecture (RN 0.70–0.81) -v9 loads when New Architecture is off. Stay on v7 only for RN **< 0.70**. RN **0.82+** ignores `newArchEnabled=false` / `RCT_NEW_ARCH_ENABLED=0` and always runs New Architecture. +Do not stay on v7 for old architecture on RN 0.70+. Install latest v9 and rebuild native. -| Surface | Old-arch path | -|---------|----------------| +| Surface | How v9 loads when New Architecture is off | +|---------|-------------------------------------------| | JS | `TurboModuleRegistry.get('RNZipArchive')`, then `NativeModules.RNZipArchive` | | Android | `isTurboModule` follows `BuildConfig.IS_NEW_ARCHITECTURE_ENABLED`; paper specs compile when new arch is off | | iOS | `RCT_EXPORT_MODULE` always; `getTurboModule` is `#ifdef RCT_NEW_ARCH_ENABLED` | -| RN | Android `newArchEnabled=false` | iOS `RCT_NEW_ARCH_ENABLED=0` | New Arch on | -|----|--------------------------------|------------------------------|-------------| -| 0.82+ ([playground-rn](./playground-rn/) 0.83.9) | N/A — flag ignored | N/A — flag ignored | existing `e2e.yml` (zip/unzip Maestro) | -| 0.81.6 (last opt-out) | CI compile (`.github/workflows/old-arch.yml`) | CI compile | — | -| 0.73–0.80 | same native paths as 0.81; not separately built | same | — | +| RN | Android `newArchEnabled=false` | iOS `RCT_NEW_ARCH_ENABLED=0` | Evidence | +|----|--------------------------------|------------------------------|----------| +| **0.82+** ([playground-rn](./playground-rn/) 0.83.9) | N/A — flag ignored | N/A — flag ignored | [RN 0.82](https://reactnative.dev/blog/2025/10/08/react-native-0.82); zip/unzip Maestro on New Arch (`e2e.yml`) | +| **0.81.6** (last opt-out) | compile + `IS_NEW_ARCHITECTURE_ENABLED=false` | compile + Legacy Architecture | `.github/workflows/old-arch.yml` (not device Maestro) | +| **0.73–0.80** | same native paths as 0.81 | same | inferred; not separately built | -Reproduce the 0.81 compile locally (same as CI): +Reproduce the 0.81 compile (same as CI): ```bash npx @react-native-community/cli@15.1.3 init RnzaOldArch --version 0.81.6 --pm npm --skip-git-init cd RnzaOldArch && npm install /path/to/react-native-zip-archive # Android: set newArchEnabled=false in android/gradle.properties, then assembleRelease -# iOS: RCT_NEW_ARCH_ENABLED=0 pod install +# iOS: set platform :ios, '15.5' in the Podfile, then RCT_NEW_ARCH_ENABLED=0 pod install ``` ## Comparison diff --git a/__tests__/package-metadata.test.js b/__tests__/package-metadata.test.js index 969d7d87..a9d658ec 100644 --- a/__tests__/package-metadata.test.js +++ b/__tests__/package-metadata.test.js @@ -88,10 +88,16 @@ describe('docs claims vs native source (RNZA-7/15/17/19)', () => { test('README documents old-arch v9 load path and playground-rn CI', () => { const readme = read('README.md'); + const migration = read('MIGRATION.md'); const pkgJava = read('android/src/main/java/com/rnziparchive/RNZipArchivePackage.java'); expect(readme).toMatch(/old-arch\.yml/); expect(readme).toMatch(/newArchEnabled=false/); expect(readme).toMatch(/RCT_NEW_ARCH_ENABLED=0/); + expect(readme).toMatch(/Stay on v7 only for RN/); + expect(readme).toMatch(/0\.70–0\.81/); + expect(readme).toMatch(/0\.82\+/); + expect(migration).toMatch(/0\.70–0\.81/); + expect(migration).toMatch(/recommended, not required on 0\.70–0\.81/); expect(pkgJava).toMatch(/boolean isTurboModule = BuildConfig\.IS_NEW_ARCHITECTURE_ENABLED/); expect(pkgJava).not.toMatch(/true\s*\/\/ isTurboModule = true/); });