diff --git a/.github/workflows/old-arch.yml b/.github/workflows/old-arch.yml new file mode 100644 index 0000000..d81b14d --- /dev/null +++ b/.github/workflows/old-arch.yml @@ -0,0 +1,162 @@ +name: Old Architecture +# 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: + branches: [master] + push: + branches: [master] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + RN_OLD_ARCH_VERSION: '0.81.6' + RN_OLD_ARCH_CLI: '15.1.3' + +jobs: + compile-android-0-81: + name: Compile Android old-arch (RN 0.81) + runs-on: ubuntu-latest + 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: + node-version: 20 + + - name: Setup Java + uses: actions/setup-java@cf277c60eb25467037889841efdb72551f06f6c3 # v4.9.1 + with: + distribution: 'zulu' + java-version: '17' + + - 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: Disable New Architecture + working-directory: ${{ runner.temp }}/RnzaOldArch/android + run: | + set -euo pipefail + grep -q '^newArchEnabled=' gradle.properties + sed -i 's/^newArchEnabled=.*/newArchEnabled=false/' gradle.properties + grep '^newArchEnabled=false$' gradle.properties + + - name: Assemble release + working-directory: ${{ runner.temp }}/RnzaOldArch/android + run: ./gradlew :app:assembleRelease --no-configuration-cache + + - 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) + 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:" + 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" | xargs grep -n 'IS_NEW_ARCHITECTURE_ENABLED' + echo "$lib_hit" | xargs grep -E 'IS_NEW_ARCHITECTURE_ENABLED = (false|Boolean\.parseBoolean\("false"\))' + fi + + 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: + 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 + # 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 + + - name: Build iOS app + working-directory: ${{ runner.temp }}/RnzaOldArch/ios + env: + RCT_NEW_ARCH_ENABLED: '0' + run: | + set -euo pipefail + WORKSPACE=$(ls -d *.xcworkspace) + SCHEME="${WORKSPACE%.xcworkspace}" + echo "Using workspace=$WORKSPACE scheme=$SCHEME" + 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 91ab2de..101cb5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## [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: 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 1016651..f8370cb 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+, 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. +| 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; 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, 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" | Enable New Architecture first. On an old-architecture 0.70+ app, fall back to `^7.0.0` until Interop is confirmed. | -| 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 d4b482c..46a0601 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 -> ``` -> If the native module fails to load on an old-architecture 0.70+ app, fall back to v7 until Interop Layer support is confirmed. +> | 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. @@ -21,6 +21,31 @@ Zip archive utility for React Native. | iOS | >= 15.5 | | Android | >= API 23 (Android 6.0) | +## Old architecture (RN 0.70–0.81) + +Do not stay on v7 for old architecture on RN 0.70+. Install latest v9 and rebuild native. + +| 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` | 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 (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: set platform :ios, '15.5' in the Podfile, then 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 50c09e3..a9d658e 100644 --- a/__tests__/package-metadata.test.js +++ b/__tests__/package-metadata.test.js @@ -86,10 +86,20 @@ 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 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/); }); 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 27b785b..dbb0801 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 3e2c3f8..a6d520a 100644 --- a/playground-rn/README.md +++ b/playground-rn/README.md @@ -4,6 +4,12 @@ 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 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. + +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 First, you will need to run **Metro**, the JavaScript build tool for React Native.