From 4a176e0dd2ef2f24a0480e8a611ea2ec8adb81b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Mon, 1 Jun 2026 15:39:14 +0200 Subject: [PATCH 1/5] feat: expose bgp as node module --- .changeset/brownfield-gradle-plugin-source.md | 5 ++ docs/docs/docs/getting-started/android.mdx | 41 ++++++++++++++++ gradle-plugins/react/README.md | 36 ++++++++++++++ packages/react-native-brownfield/package.json | 6 ++- .../scripts/sync-gradle-plugin-source.js | 48 +++++++++++++++++++ 5 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 .changeset/brownfield-gradle-plugin-source.md create mode 100644 packages/react-native-brownfield/scripts/sync-gradle-plugin-source.js diff --git a/.changeset/brownfield-gradle-plugin-source.md b/.changeset/brownfield-gradle-plugin-source.md new file mode 100644 index 00000000..9e97cc69 --- /dev/null +++ b/.changeset/brownfield-gradle-plugin-source.md @@ -0,0 +1,5 @@ +--- +'@callstack/react-native-brownfield': patch +--- + +Expose the Brownfield Android Gradle Plugin source from the npm package for opt-in local patching. diff --git a/docs/docs/docs/getting-started/android.mdx b/docs/docs/docs/getting-started/android.mdx index dab37ac1..eb9e89a1 100644 --- a/docs/docs/docs/getting-started/android.mdx +++ b/docs/docs/docs/getting-started/android.mdx @@ -59,6 +59,47 @@ react { } ``` +### Advanced: Load the Plugin from `node_modules` + +Use the source version only when you need to patch the Brownfield Gradle Plugin locally, for example with `patch-package`. + +Add the included build to the top of `android/settings.gradle.kts`: + +```kotlin +pluginManagement { + includeBuild( + File( + providers.exec { + workingDir(rootDir) + commandLine("node", "--print", "require.resolve('@callstack/react-native-brownfield/package.json')") + }.standardOutput.asText.get().trim() + ).parentFile.resolve("gradle-plugin/brownfield") + ) +} +``` + +For standard React Native app layouts, this shorter form also works: + +```kotlin +pluginManagement { + includeBuild("../node_modules/@callstack/react-native-brownfield/gradle-plugin/brownfield") +} +``` + +Then remove the Maven classpath dependency from `android/build.gradle`: + +```diff +- classpath("com.callstack.react:brownfield-gradle-plugin:") +``` + +Keep applying the plugin in your brownfield Android library module as usual: + +```kotlin +plugins { + id("com.callstack.react.brownfield") +} +``` + ## 3. Add React Native Dependencies Add the code below to `reactnativeapp/build.gradle.kts` based on your React Native version. diff --git a/gradle-plugins/react/README.md b/gradle-plugins/react/README.md index 62d90ef9..9a0bd4b4 100644 --- a/gradle-plugins/react/README.md +++ b/gradle-plugins/react/README.md @@ -81,6 +81,42 @@ react { val appProject = project(":app") ``` +### From `node_modules` Source + +The Maven dependency is the recommended default because it uses the prebuilt plugin and is faster. +Use this source-based setup only when you need to patch the Brownfield Gradle Plugin locally. + +Add this to the top of `android/settings.gradle.kts`: + +```kotlin +pluginManagement { + includeBuild( + File( + providers.exec { + workingDir(rootDir) + commandLine("node", "--print", "require.resolve('@callstack/react-native-brownfield/package.json')") + }.standardOutput.asText.get().trim() + ).parentFile.resolve("gradle-plugin/brownfield") + ) +} +``` + +For standard React Native app layouts, this shorter form also works: + +```kotlin +pluginManagement { + includeBuild("../node_modules/@callstack/react-native-brownfield/gradle-plugin/brownfield") +} +``` + +Then remove the Maven classpath dependency: + +```diff +- classpath("com.callstack.react:brownfield-gradle-plugin:1.1.0") +``` + +Keep using `id("com.callstack.react.brownfield")` in your brownfield Android library module. + ## API Usage - **About Dependencies** diff --git a/packages/react-native-brownfield/package.json b/packages/react-native-brownfield/package.json index 8aa49e47..f87bf13f 100644 --- a/packages/react-native-brownfield/package.json +++ b/packages/react-native-brownfield/package.json @@ -49,8 +49,9 @@ "build": "bob build", "dev": "nodemon --exitcrash false --ext '*' --watch src --exec \"bob build\"", "build:brownfield": "yarn run build", - "prepack": "cp ../../README.md ./README.md", - "postpack": "rm ./README.md", + "sync:gradle-plugin": "node ./scripts/sync-gradle-plugin-source.js", + "prepack": "node ./scripts/sync-gradle-plugin-source.js && cp ../../README.md ./README.md", + "postpack": "node ./scripts/sync-gradle-plugin-source.js --clean && rm ./README.md", "test": "vitest run" }, "keywords": [ @@ -63,6 +64,7 @@ "src", "lib", "scripts", + "gradle-plugin", "android", "ios", "cpp", diff --git a/packages/react-native-brownfield/scripts/sync-gradle-plugin-source.js b/packages/react-native-brownfield/scripts/sync-gradle-plugin-source.js new file mode 100644 index 00000000..e462fba3 --- /dev/null +++ b/packages/react-native-brownfield/scripts/sync-gradle-plugin-source.js @@ -0,0 +1,48 @@ +const fs = require('node:fs'); +const path = require('node:path'); + +const packageRoot = path.resolve(__dirname, '..'); +const repoRoot = path.resolve(packageRoot, '..', '..'); + +const sourceBrownfieldPath = path.join( + repoRoot, + 'gradle-plugins', + 'react', + 'brownfield' +); +const sourceVersionCatalogPath = path.join( + repoRoot, + 'gradle-plugins', + 'react', + 'gradle', + 'libs.versions.toml' +); + +const targetRoot = path.join(packageRoot, 'gradle-plugin'); +const targetBrownfieldPath = path.join(targetRoot, 'brownfield'); +const targetGradlePath = path.join(targetRoot, 'gradle'); + +if (process.argv.includes('--clean')) { + fs.rmSync(targetRoot, { recursive: true, force: true }); + process.exit(0); +} + +fs.rmSync(targetRoot, { recursive: true, force: true }); +fs.mkdirSync(targetGradlePath, { recursive: true }); + +fs.cpSync(sourceBrownfieldPath, targetBrownfieldPath, { + recursive: true, + filter(source) { + const relativePath = path.relative(sourceBrownfieldPath, source); + const parts = relativePath.split(path.sep); + + return !parts.some((part) => + ['build', '.gradle', 'local.properties'].includes(part) + ); + }, +}); + +fs.copyFileSync( + sourceVersionCatalogPath, + path.join(targetGradlePath, 'libs.versions.toml') +); From ccc8ee293f66dadf350a8cb00d494e0725f5973a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Tue, 2 Jun 2026 10:02:44 +0200 Subject: [PATCH 2/5] feat: small readme update --- docs/docs/docs/getting-started/android.mdx | 2 +- gradle-plugins/react/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/docs/getting-started/android.mdx b/docs/docs/docs/getting-started/android.mdx index eb9e89a1..75443353 100644 --- a/docs/docs/docs/getting-started/android.mdx +++ b/docs/docs/docs/getting-started/android.mdx @@ -59,7 +59,7 @@ react { } ``` -### Advanced: Load the Plugin from `node_modules` +### Advanced: Load the Plugin from Node Modules Use the source version only when you need to patch the Brownfield Gradle Plugin locally, for example with `patch-package`. diff --git a/gradle-plugins/react/README.md b/gradle-plugins/react/README.md index 9a0bd4b4..821a9aea 100644 --- a/gradle-plugins/react/README.md +++ b/gradle-plugins/react/README.md @@ -84,7 +84,7 @@ react { ### From `node_modules` Source The Maven dependency is the recommended default because it uses the prebuilt plugin and is faster. -Use this source-based setup only when you need to patch the Brownfield Gradle Plugin locally. +Use this source-based setup only when you need to patch the Brownfield Gradle Plugin locally. See the [Android integration docs](../../docs/docs/docs/getting-started/android.mdx#advanced-load-the-plugin-from-node-modules) for details. Add this to the top of `android/settings.gradle.kts`: From 7497ab3ee2aa4ff3f799acaddb296e7856df269c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Muzyk?= Date: Wed, 3 Jun 2026 14:50:41 +0200 Subject: [PATCH 3/5] fix: bump koltin and update docs --- docs/docs/docs/getting-started/android.mdx | 10 ++++++++++ gradle-plugins/react/brownfield/settings.gradle | 13 +++++++++++++ .../react/brownfield/processors/VariantProcessor.kt | 2 +- gradle-plugins/react/gradle/libs.versions.toml | 2 +- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/docs/docs/docs/getting-started/android.mdx b/docs/docs/docs/getting-started/android.mdx index 75443353..69e8f8d8 100644 --- a/docs/docs/docs/getting-started/android.mdx +++ b/docs/docs/docs/getting-started/android.mdx @@ -75,6 +75,11 @@ pluginManagement { }.standardOutput.asText.get().trim() ).parentFile.resolve("gradle-plugin/brownfield") ) + repositories { + google() + mavenCentral() + gradlePluginPortal() + } } ``` @@ -83,6 +88,11 @@ For standard React Native app layouts, this shorter form also works: ```kotlin pluginManagement { includeBuild("../node_modules/@callstack/react-native-brownfield/gradle-plugin/brownfield") + repositories { + google() + mavenCentral() + gradlePluginPortal() + } } ``` diff --git a/gradle-plugins/react/brownfield/settings.gradle b/gradle-plugins/react/brownfield/settings.gradle index b5a0fabf..1cfe1c52 100644 --- a/gradle-plugins/react/brownfield/settings.gradle +++ b/gradle-plugins/react/brownfield/settings.gradle @@ -1,4 +1,17 @@ +pluginManagement { + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + dependencyResolutionManagement { + repositories { + google() + mavenCentral() + } + versionCatalogs { create("libs") { from(files("../gradle/libs.versions.toml")) diff --git a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantProcessor.kt b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantProcessor.kt index fc42b678..9c6efc8d 100644 --- a/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantProcessor.kt +++ b/gradle-plugins/react/brownfield/src/main/kotlin/com/callstack/react/brownfield/processors/VariantProcessor.kt @@ -112,7 +112,7 @@ class VariantProcessor(private val variant: LibraryVariant) : BaseProject() { it.dependsOn(mergeClassTask) } extractAnnotationsTask.configure { - it.mustRunAfter(mergeClassTask) + it.mustRunAfter(mergeClassTask!!) } if (!variant.buildType.isMinifyEnabled) { diff --git a/gradle-plugins/react/gradle/libs.versions.toml b/gradle-plugins/react/gradle/libs.versions.toml index e7d148f1..b1b59971 100644 --- a/gradle-plugins/react/gradle/libs.versions.toml +++ b/gradle-plugins/react/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -kotlinJvm = "2.0.21" +kotlinJvm = "2.2.21" ktlint = "12.1.1" detekt = "1.23.7" agp = "8.5.2" From f2e8fd279172df5a60292f826432d1b6df4fefb1 Mon Sep 17 00:00:00 2001 From: artus9033 Date: Wed, 3 Jun 2026 17:05:00 +0200 Subject: [PATCH 4/5] fix: sync-gradle-plugin-source script to ignore additional paths --- .../scripts/sync-gradle-plugin-source.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native-brownfield/scripts/sync-gradle-plugin-source.js b/packages/react-native-brownfield/scripts/sync-gradle-plugin-source.js index e462fba3..bff60bf3 100644 --- a/packages/react-native-brownfield/scripts/sync-gradle-plugin-source.js +++ b/packages/react-native-brownfield/scripts/sync-gradle-plugin-source.js @@ -37,7 +37,7 @@ fs.cpSync(sourceBrownfieldPath, targetBrownfieldPath, { const parts = relativePath.split(path.sep); return !parts.some((part) => - ['build', '.gradle', 'local.properties'].includes(part) + ['build', '.gradle', 'local.properties', '.kotlin', 'bin'].includes(part) ); }, }); From 997b62989b2e5867a30028b7476a275b91deb7fc Mon Sep 17 00:00:00 2001 From: artus9033 Date: Wed, 3 Jun 2026 17:05:54 +0200 Subject: [PATCH 5/5] chore: gitignore files copied by sync-gradle-plugin-source --- packages/react-native-brownfield/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 packages/react-native-brownfield/.gitignore diff --git a/packages/react-native-brownfield/.gitignore b/packages/react-native-brownfield/.gitignore new file mode 100644 index 00000000..afb55c56 --- /dev/null +++ b/packages/react-native-brownfield/.gitignore @@ -0,0 +1 @@ +/gradle-plugin/