From 46b20335bb88c125fda406e3a70df69b8f40028c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliv=C3=A9r=20Falvai?= Date: Thu, 9 Jul 2026 13:08:10 +0200 Subject: [PATCH] Clean up GHA config --- .azurepipelines/build-rn-code-push-1es.yml | 104 --------------------- .azurepipelines/test-rn-code-push.yml | 94 ------------------- .github/workflows/ci-test.yml | 65 +++++++------ .gitignore | 2 + CLAUDE.md | 33 ++----- ios/CodePush/CodePush.m | 13 +-- mise.toml | 4 + test/template/ios/Podfile | 35 ++++++- test/test.ts | 2 +- 9 files changed, 92 insertions(+), 260 deletions(-) delete mode 100644 .azurepipelines/build-rn-code-push-1es.yml delete mode 100644 .azurepipelines/test-rn-code-push.yml create mode 100644 mise.toml diff --git a/.azurepipelines/build-rn-code-push-1es.yml b/.azurepipelines/build-rn-code-push-1es.yml deleted file mode 100644 index b9423bd..0000000 --- a/.azurepipelines/build-rn-code-push-1es.yml +++ /dev/null @@ -1,104 +0,0 @@ -trigger: -- master - -pr: -- master - -resources: - repositories: - - repository: 1ESPipelineTemplates - type: git - name: 1ESPipelineTemplates/1ESPipelineTemplates - ref: refs/tags/release -name: $(Build.SourceBranchName)_$(date:yyyyMMdd)$(rev:.r) - -extends: - ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/master') }}: - template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates - ${{ else }}: - template: v1/1ES.Unofficial.PipelineTemplate.yml@1ESPipelineTemplates - parameters: - pool: - name: 1ES-PT-CBL-Mariner-2.0-Gen2 - os: linux - customBuildTags: - - ES365AIMigrationTooling-BulkMigrated - sdl: - sourceAnalysisPool: 1ES-PT-Windows-2022 - credscan: - suppressionsFile: $(Build.SourcesDirectory)/.config/CredScanSuppressions.json - stages: - - stage: Stage - jobs: - - job: HostJob - templateContext: - outputs: - - output: pipelineArtifact - displayName: "Publish Artifact: artifacts" - path: '$(Build.ArtifactStagingDirectory)/npm' - artifactName: npm - - steps: - - task: NodeTool@0 - inputs: - versionSpec: '14.x' - displayName: 'Install Node.js' - - - script: | - npm pack - npm install -g react-native-code-push*.tgz - displayName: 'Package react-native-code-push' - workingDirectory: $(Build.SourcesDirectory) - - - task: DeleteFiles@1 - inputs: - contents: node_modules - displayName: 'Delete node_modules' - - - task: ArchiveFiles@2 - inputs: - rootFolderOrFile: '$(Build.SourcesDirectory)' - includeRootFolder: false - archiveType: 'tar' - archiveFile: '$(Build.ArtifactStagingDirectory)/npm/$(Build.BuildId).tgz' - replaceExistingArchive: true - verbose: true - displayName: 'Prepare npm artifact' - - - stage: APIScan - dependsOn: Stage - pool: - name: 1ES-PT-Windows-2022 - os: windows - variables: - "agent.source.skip": true - jobs: - - job: APIScan - steps: - - task: DownloadPipelineArtifact@2 - displayName: Download Build Artifacts for APIScan - inputs: - artifactName: npm - targetPath: '$(Agent.BuildDirectory)/npm' - - task: ExtractFiles@1 - inputs: - archiveFilePatterns: '$(Agent.BuildDirectory)/npm/*.tgz' - destinationFolder: '$(Agent.BuildDirectory)/npm_extracted' - - task: AzureKeyVault@2 - inputs: - azureSubscription: 'AC - Dev Infra & Build Pool' - KeyVaultName: 'mobile-center-sdk' - SecretsFilter: 'appcenter-sdk-managed-identity-clientid' - RunAsPreJob: false - - task: APIScan@2 - displayName: 'Run APIScan' - inputs: - softwareFolder: '$(Agent.BuildDirectory)\npm_extracted' - softwareName: 'react-native-code-push' - softwareVersionNum: '$(Build.BuildId)' - isLargeApp: false - toolVersion: 'Latest' - verbosityLevel: verbose - condition: and(succeeded(), ne(variables['DisableAPIScan'], 'true')) - env: - AzureServicesAuthConnectionString: 'runAs=App;AppId=$(appcenter-sdk-managed-identity-clientid)' \ No newline at end of file diff --git a/.azurepipelines/test-rn-code-push.yml b/.azurepipelines/test-rn-code-push.yml deleted file mode 100644 index baf19ed..0000000 --- a/.azurepipelines/test-rn-code-push.yml +++ /dev/null @@ -1,94 +0,0 @@ -trigger: -- master - -pr: -- master - -variables: -- name: api-level - value: '27' - -pool: - vmImage: 'macOS-12' - -stages: -- stage: RunTests - displayName: 'Run Android & IOS tests' - jobs: - - job: TestAndroid - timeoutInMinutes: 120 - displayName: 'Test android' - steps: - - - script: | - adb devices - displayName: 'Start adb server' - - - script: | - $ANDROID_HOME/tools/bin/sdkmanager "system-images;android-$(api-level);google_apis;x86" - displayName: 'Download system image' - - - script: | - $ANDROID_HOME/tools/bin/avdmanager create avd --force --name TestEmulator --abi google_apis/x86 --package 'system-images;android-$(api-level);google_apis;x86' --device "Nexus 6P" - displayName: 'Creating Android emulator' - - - script: | - $ANDROID_HOME/emulator/emulator -avd TestEmulator -noaudio -no-window -no-snapshot-save -no-boot-anim -memory 6144 & - displayName: 'Start Android emulator' - - - script: | - $ANDROID_HOME/platform-tools/adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed | tr -d '\r') ]]; do sleep 1; done' - displayName: 'Wait for emulator to boot' - - - script: | - adb shell settings put global window_animation_scale 0.0 - displayName: 'Disable animations and transitions' - - - script: | - adb shell settings put global transition_animation_scale 0.0 - displayName: 'Disable animations and transitions' - - - script: | - adb shell settings put global animator_duration_scale 0.0 - displayName: 'Disable animations and transitions' - - - - task: JavaToolInstaller@0 - inputs: - versionSpec: '11' - jdkArchitectureOption: 'x64' - jdkSourceOption: 'PreInstalled' - displayName: 'Change Java version' - - - script: | - npm install - displayName: 'Package Installation' - - - script: | - npm run build:tests && npm run test:setup:android - displayName: 'Setup Android tests' - - - script: | - npm run test:fast:android - displayName: 'Run Android test' - - - job: TestIOS - timeoutInMinutes: 120 - displayName: 'Test IOS' - steps: - - - script: | - npm install - displayName: 'Install dependencies' - - - script: | - npm run build:tests && npm run test:setup:ios - displayName: 'Setup iOS tests' - - - script: | - npm run test:fast:ios - displayName: 'Run tests' - - - - diff --git a/.github/workflows/ci-test.yml b/.github/workflows/ci-test.yml index e49aac3..d7631bd 100644 --- a/.github/workflows/ci-test.yml +++ b/.github/workflows/ci-test.yml @@ -2,47 +2,60 @@ name: CI Tests on: pull_request: - types: [opened, edited] workflow_dispatch: jobs: - android-new-arch-test: - runs-on: self-hosted + lint: + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - - name: Run Android Tests - run: | - npm install - npm run test:android + - uses: jdx/mise-action@v4 + + - name: Install dependencies + run: npm install + - name: Lint and type-check + run: npm run build:tests - ios-new-arch-test: - runs-on: self-hosted + android-test: + needs: lint + runs-on: bitrise-react-native-code-push-linux-runner steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 - - name: Run iOS Tests - run: | - npm install - npm run test:ios + - uses: jdx/mise-action@v4 - android-old-arch-test: - runs-on: self-hosted - steps: - - uses: actions/checkout@v3 + - name: Install dependencies + run: npm install - - name: Run Android Tests + - name: Enable KVM run: | - npm install - npm run test:oldArch:android + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm - ios-old-arch-test: - runs-on: self-hosted + - name: Run Android Tests + uses: reactivecircus/android-emulator-runner@v2 + env: + # Tests need `emulator` to be in $PATH. + PATH: /usr/local/lib/android/sdk/emulator:/usr/local/lib/android/sdk/platform-tools:${{ env.PATH }} + with: + api-level: 27 + target: google_apis + arch: x86 + disable-animations: true + script: npm run test:android + + ios-test: + needs: lint + runs-on: bitrise-react-native-code-push-macos-runner steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v7 + + - uses: jdx/mise-action@v4 - name: Run iOS Tests run: | npm install - npm run test:oldArch:ios \ No newline at end of file + npm run test:ios diff --git a/.gitignore b/.gitignore index 2510af0..ddc58ec 100644 --- a/.gitignore +++ b/.gitignore @@ -195,3 +195,5 @@ Examples/testapp_rn #CodePushExpoDemoApp *.rm + +.claude/settings.local.json diff --git a/CLAUDE.md b/CLAUDE.md index 6cd30c7..ef08deb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,7 +1,5 @@ # CLAUDE.md -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - ## Project Overview React Native CodePush is a native module that enables over-the-air updates for React Native apps. It consists of native implementations for iOS (Objective-C), Android (Java), and Windows (C++), unified through a JavaScript bridge layer. @@ -46,31 +44,20 @@ React Native CodePush is a native module that enables over-the-air updates for R ### Testing Framework - **Custom Test Runner**: TypeScript-based test framework in `test/` -- **Emulator Management**: Automated setup and teardown of test environments - **Real App Testing**: Creates actual React Native apps for integration testing - **Scenario Testing**: Update, rollback, and error scenarios +- **Templates**: `test/template/` holds native files (Podfile, AppDelegate, Android app files) and JS scenarios copied over top of a freshly generated RN/Expo app during test setup, overwriting its defaults — edit files here, not the generated project, for changes to persist +- **`test:ios` vs `test:setup:ios` vs `test:fast:ios`**: `test:ios` is just `test:setup:ios` followed by `test:fast:ios` — the two are meant to be split apart for local iteration. + - `test:setup:ios` (mocha `--ios --setup`) boots the simulator and provisions the test app once: copies templates, runs `pod install`, patches Info.plist/AppDelegate. It never builds or runs any test scenario. + - `test:fast:ios` (mocha `--ios`) skips provisioning and goes straight to the actual test scenarios: its `before()` hook calls `RNIOS.buildApp` (`xcodebuild` against the already-provisioned `.xcworkspace`) and installs the binary, then runs the update/rollback/error scenarios. + - For the fast local loop: run `test:setup:ios` once per template/dependency change, then re-run `test:fast:ios` repeatedly while iterating on test/scenario code — this skips `pod install` and re-provisioning on every iteration. + - There's still no "just build, no tests" npm script — for a raw build only, lift the `xcodebuild` invocation out of `RNIOS.buildApp` in `test/test.ts` and run it by hand against the provisioned `TestCodePush.xcworkspace`. +- When debugging a CI failure, don't trust the first plausible-looking theory from log noise — reproduce the exact failing command locally on matching hardware/toolchain before writing up a root cause. This is faster than iterating against multi-hour CI runs and catches wrong hypotheses early. +- Before running `npm run test:setup:ios`, shut down all booted simulators (`xcrun simctl shutdown all`) — the test framework's simulator picker hangs silently (no error) if simulators are already booted outside it. +- `npm run test:setup:ios` provisions a full test app outside the repo (under a system temp/`test-run` dir), not inside `test/` — expect to search for it rather than finding it checked into the repo tree. +- The provisioned test app's `node_modules/@code-push-next/react-native-code-push` is a real copy, not a symlink — editing `ios/` (or `android/`) native source in the repo has zero effect on `test:fast:ios` runs until you re-copy those files into that `node_modules` path (or rerun `test:setup:ios`). ### Build Integration - **Android Gradle Plugin**: Automatically generates bundle hashes and processes assets - **iOS CocoaPods**: Manages native dependencies and build configuration - **Bundle Processing**: Automated zip creation and hash calculation for OTA updates - -## Development Workflow - -1. **Making Changes**: Edit native code or JavaScript bridge -2. **Testing**: Run platform-specific tests with real emulators -3. **Integration**: Test with actual React Native apps via test framework -4. **Validation**: Ensure compatibility with both RN architectures - -## Key Files -- `CodePush.js` - Main JavaScript API -- `test/TestRunner.ts` - Test framework entry point -- `android/build.gradle` - Android build configuration -- `ios/CodePush.podspec` - iOS CocoaPods specification -- `plugin.xml` - Cordova plugin configuration - -## Special Considerations -- Native module requires platform-specific knowledge (iOS/Android/Windows) -- Testing requires emulator setup and can be time-intensive -- Updates must be backward compatible with existing app installations -- Bundle hash calculation is critical for update integrity \ No newline at end of file diff --git a/ios/CodePush/CodePush.m b/ios/CodePush/CodePush.m index bd92a79..d9d40b6 100644 --- a/ios/CodePush/CodePush.m +++ b/ios/CodePush/CodePush.m @@ -538,19 +538,10 @@ - (void)loadBundle // file (since Chrome wouldn't support it). Otherwise, update // the current bundle URL to point at the latest update if ([CodePush isUsingTestConfiguration] || ![super.bridge.bundleURL.scheme hasPrefix:@"http"]) { - RCTReloadCommandSetBundleURL([CodePush bundleURL]); - // [super.bridge setValue:[CodePush bundleURL] forKey:@"bundleURL"]; + [super.bridge setValue:[CodePush bundleURL] forKey:@"bundleURL"]; } - // Add a small delay to ensure Fabric surface initialization is complete - // before triggering reload. This fixes a race condition in React Native 0.82+ - // with New Architecture where RCTInstance invalidate and RCTFabricSurface start - // can run concurrently, causing a crash when MountingCoordinator's mutex is accessed - // after being destroyed. - // - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - RCTTriggerReloadCommandListeners(@"react-native-code-push: Restart"); - }); + RCTTriggerReloadCommandListeners(@"react-native-code-push: Restart"); }); } diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..ee9b7ed --- /dev/null +++ b/mise.toml @@ -0,0 +1,4 @@ +[tools] +node = "22" +java = "17" +ruby = "4.0" diff --git a/test/template/ios/Podfile b/test/template/ios/Podfile index bb0376a..dff03d4 100644 --- a/test/template/ios/Podfile +++ b/test/template/ios/Podfile @@ -31,5 +31,38 @@ target 'TestCodePush' do :mac_catalyst_enabled => false, # :ccache_enabled => true ) + + # Workaround: fmt 11.0.2 (pinned transitively via RCT-Folly) fails to compile on + # Xcode 26.4+ due to a compiler compatibility bug. Fixed upstream in fmt 12.1.0. + # See https://github.com/facebook/react-native/issues/55601 + # + # REMOVE THIS once the React Native version used here bundles fmt >= 12.1.0 + # (i.e. after upgrading React Native past 0.82.x). + installer.pods_project.targets.each do |target| + if target.name == 'fmt' + target.build_configurations.each do |config| + config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++17' + end + end + end + + # Skip the x86_64 simulator slice. Test runs only ever target an arm64 + # simulator (Apple Silicon), so building x86_64 as well just doubles + # compile/link time for a slice that never runs, and adds unnecessary + # failure surface (e.g. codegen sources that don't compile cleanly for it). + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'x86_64' + end + end + + installer.aggregate_targets.each do |aggregate_target| + aggregate_target.user_project.native_targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'x86_64' + end + end + aggregate_target.user_project.save + end end -end \ No newline at end of file +end diff --git a/test/test.ts b/test/test.ts index 7e59e78..00de751 100644 --- a/test/test.ts +++ b/test/test.ts @@ -285,7 +285,7 @@ class RNIOS extends Platform.IOS implements RNPlatform { return this.getEmulatorManager().getTargetEmulator() .then((targetEmulator: string) => { return TestUtil.getProcessOutput("xcodebuild -workspace " + path.join(iOSProject, TestConfig.TestAppName) + ".xcworkspace -scheme " + TestConfig.TestAppName + - " -configuration Release -destination \"platform=iOS Simulator,id=" + targetEmulator + "\" -derivedDataPath build EXCLUDED_ARCHS=arm64", { cwd: iOSProject, timeout: 30 * 60 * 1000, maxBuffer: 1024 * 1024 * 5000, noLogStdOut: true }); + " -configuration Release -destination \"platform=iOS Simulator,id=" + targetEmulator + "\" -derivedDataPath build", { cwd: iOSProject, timeout: 10 * 60 * 1000, maxBuffer: 1024 * 1024 * 5000, noLogStdOut: true }); }) .then( () => { return null; },