Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion .github/workflows/ci-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,34 @@ jobs:
- name: Check public API compatibility
run: npm run check:api-compat

android-test:
android-unit-test:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7

- uses: jdx/mise-action@v4

- name: Cache npm packages
uses: actions/cache@v6
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('package-lock.json', 'test/test.ts') }}
restore-keys: |
${{ runner.os }}-npm-

- name: Install dependencies
run: npm install

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6

- name: Run Android unit tests
working-directory: android
run: ./gradlew :app:test

android-test:
needs: [lint, android-unit-test]
runs-on: bitrise-react-native-code-push-linux-runner
strategy:
matrix:
Expand All @@ -49,6 +75,16 @@ jobs:
- name: Install dependencies
run: npm install

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6
with:
# This job's Gradle build lives in a test app generated at runtime by the test harness,
# not a project checked into this repo, so there's nothing meaningful for it to
# contribute back to the cache. Read-only avoids each matrix variant (bare/expo) writing
# its own redundant entry; it still reuses whatever android-unit-test wrote, since
# setup-gradle shares its cache across jobs in the same workflow run.
cache-read-only: true

- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
Expand Down
12 changes: 9 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ React Native CodePush is a native module that enables over-the-air updates for R
## Development Commands

### Testing
- `npm test` - Run all tests with TypeScript compilation

#### Unit tests

- `cd android && ./gradlew :app:test`
- iOS: no unit tests yet.

#### E2E Tests
- `npm run test:android` - Run Android-specific tests
- `npm run test:ios` - Run iOS-specific tests
- `npm run test:setup-android` - Set up Android emulator for testing
Expand All @@ -34,7 +40,7 @@ React Native CodePush is a native module that enables over-the-air updates for R

### Platform Structure
- **iOS**: `ios/` - Objective-C implementation with CocoaPods integration
- **Android**: `android/` - Java implementation with Gradle plugin
- **Android**: `android/` - Java/Kotlin implementation with Gradle plugin
- **Windows**: `windows/` - C++ implementation for Windows React Native
- **JavaScript**: Root level - TypeScript definitions and bridge code

Expand All @@ -48,7 +54,7 @@ React Native CodePush is a native module that enables over-the-air updates for R
- **Custom Test Runner**: TypeScript-based test framework in `test/`
- **Real App Testing**: Creates actual React Native apps for integration testing
- **Scenario Testing**: Update, rollback, and error scenarios
- **No unit test infra yet**: this repo only has the mocha-based integration suite above. `src/acquisition-sdk/__tests__/` contains tests ported from upstream `microsoft/code-push`, kept for future reference - they are deliberately not wired into `npm test` or any runner. Don't assume they're dead/forgotten code, and don't wire them in without setting up real unit test infra first.
- **No unit test infra for JS/iOS yet**: JS/iOS only have the mocha-based integration suite above. `src/acquisition-sdk/__tests__/` contains tests ported from upstream `microsoft/code-push`, kept for future reference - they are deliberately not wired into `npm test` or any runner. Don't assume they're dead/forgotten code, and don't wire them in without setting up real unit test infra first.
- **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.
Expand Down
6 changes: 6 additions & 0 deletions Examples/CodePushDemo/android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,9 @@ hermesEnabled=true
# This allows your app to draw behind system bars for an immersive UI.
# Note: Only works with ReactActivity and should not be used with custom Activity.
edgeToEdgeEnabled=false

# Opt out of AGP 9's built-in Kotlin support and new DSL, matching what RN's own 0.87 app template
# does. See the AGP v9 adoption RFC:
# https://github.com/react-native-community/discussions-and-proposals/pull/1006).
android.builtInKotlin=false
android.newDsl=false
48 changes: 42 additions & 6 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,21 @@
apply plugin: "com.android.library"
// No versions specified: both plugins are expected to already be resolved on the root project's
// buildscript classpath, which every RN app template declares (AGP for the app itself, Kotlin
// because RN ships Kotlin internally).
// Matches how other RN libraries (e.g. reanimated) apply these plugins.
plugins {
id "com.android.library"
id "org.jetbrains.kotlin.android" apply false
}

// AGP 9 provides Kotlin support built in; applying the classic kotlin-android plugin on top of it
// leads to a configuration-time failure. Below AGP 9, and on AGP 9+ when `android.builtInKotlin=false`
// opts back out of built-in Kotlin, the classic plugin is still required.
// See React Native RFC about ecosystem migration details: https://github.com/react-native-community/discussions-and-proposals/pull/1006
def agpMajor = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.')[0].toInteger()
def builtInKotlinEnabled = agpMajor >= 9 && (!project.hasProperty("android.builtInKotlin") || Boolean.parseBoolean(project.property("android.builtInKotlin").toString()))
if (!builtInKotlinEnabled) {
apply plugin: "org.jetbrains.kotlin.android"
}

def isNewArchitectureEnabled() {
// To opt-in for the New Architecture, you can either:
Expand All @@ -10,15 +27,18 @@ def isNewArchitectureEnabled() {

def IS_NEW_ARCHITECTURE_ENABLED = isNewArchitectureEnabled()

def DEFAULT_COMPILE_SDK_VERSION = 26
def DEFAULT_BUILD_TOOLS_VERSION = "26.0.3"
def DEFAULT_TARGET_SDK_VERSION = 26
def DEFAULT_MIN_SDK_VERSION = 16
// These are fallbacks only. Keep them aligned with RN's current app template
// so a consumer relying on the fallback still gets a build that actually works
// with a current RN version.
def DEFAULT_COMPILE_SDK_VERSION = 35
def DEFAULT_BUILD_TOOLS_VERSION = "36.0.0"
def DEFAULT_TARGET_SDK_VERSION = 35
def DEFAULT_MIN_SDK_VERSION = 24

android {
namespace "com.microsoft.codepush.react"

compileSdkVersion rootProject.hasProperty('compileSdkVersion') ? rootProject.compileSdkVersion : DEFAULT_COMPILE_SDK_VERSION
compileSdk rootProject.hasProperty('compileSdkVersion') ? rootProject.compileSdkVersion : DEFAULT_COMPILE_SDK_VERSION
buildToolsVersion rootProject.hasProperty('buildToolsVersion') ? rootProject.buildToolsVersion : DEFAULT_BUILD_TOOLS_VERSION

defaultConfig {
Expand All @@ -40,9 +60,25 @@ android {
buildFeatures {
buildConfig true
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}

// Only set when we applied the classic kotlin-android plugin ourselves above: AGP 9's built-in
// Kotlin integration doesn't expose this extension at all, per Android's own built-in
// Kotlin migration guide: https://developer.android.com/build/migrate-to-built-in-kotlin
if (!builtInKotlinEnabled) {
android.kotlinOptions {
jvmTarget = "17"
}
}

dependencies {
implementation 'com.facebook.react:react-android:0.82.1'
implementation 'com.nimbusds:nimbus-jose-jwt:9.37.3'

testImplementation 'junit:junit:4.13.2'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.microsoft.codepush.react

import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import java.io.File

class FileUtilsTest {

@get:Rule
val tempFolder = TemporaryFolder()

@Test
fun copyDirectoryContents_copiesNestedFilesAndSubdirectories() {
val sourceDir = tempFolder.newFolder("source")
File(sourceDir, "root.txt").writeText("root contents")
val nestedDir = File(sourceDir, "nested").apply { mkdir() }
File(nestedDir, "child.txt").writeText("child contents")

val destinationDir = File(tempFolder.root, "destination")

FileUtils.copyDirectoryContents(sourceDir.absolutePath, destinationDir.absolutePath)

assertEquals("root contents", File(destinationDir, "root.txt").readText())
val copiedNestedFile = File(destinationDir, "nested/child.txt")
assertTrue(copiedNestedFile.exists())
assertEquals("child contents", copiedNestedFile.readText())
}
}
7 changes: 6 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ buildscript {
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:8.12.0")
// Must match (or exceed) the AGP version RN's own @react-native/gradle-plugin declares: that plugin is
// included as a composite build below and its own AGP dependency wins Gradle's classpath
// conflict resolution regardless of what's pinned here, so an out-of-date pin here is
// silently overridden rather than actually enforced. Bump this in lockstep with RN's own
// gradle-plugin version whenever bumping the react-native devDependency.
classpath("com.android.tools.build:gradle:9.2.1")
classpath("com.facebook.react:react-native-gradle-plugin")

// NOTE: Do not place your application dependencies here; they belong
Expand Down
7 changes: 7 additions & 0 deletions android/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,10 @@
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
android.useAndroidX=true

# https://github.com/react-native-community/discussions-and-proposals/pull/1006
# Applies to this repo's own standalone build/test harness only; see android/app/build.gradle's comments
# for why downstream consumers may still land in either mode.
android.builtInKotlin=false
android.newDsl=false
2 changes: 1 addition & 1 deletion android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-all.zip