Skip to content

Instantly share code, notes, and snippets.

@ridvanaltun
Last active August 26, 2025 07:19
Show Gist options
  • Save ridvanaltun/5bf4e072e8db7a4ecd93770c542b1387 to your computer and use it in GitHub Desktop.
Save ridvanaltun/5bf4e072e8db7a4ecd93770c542b1387 to your computer and use it in GitHub Desktop.

Revisions

  1. ridvanaltun revised this gist Oct 26, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -72,7 +72,7 @@ Go to file: https://gist.github.com/ridvanaltun/5bf4e072e8db7a4ecd93770c542b1387

    Go to file: https://gist.github.com/ridvanaltun/5bf4e072e8db7a4ecd93770c542b1387#file-modelautolinkingdependenciesjson-diff

    After all this changes we need patch all of them
    9. After all this changes we need patch all of them

    ```sh
    npx patch-package @react-native-community/cli-config
  2. ridvanaltun renamed this gist Oct 25, 2024. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  3. ridvanaltun renamed this gist Oct 25, 2024. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  4. ridvanaltun revised this gist Oct 25, 2024. 3 changed files with 6 additions and 3 deletions.
    2 changes: 1 addition & 1 deletion GeneratePackageListTask.diff
    Original file line number Diff line number Diff line change
    @@ -98,7 +98,7 @@ abstract class GeneratePackageListTask : DefaultTask() {
    val packages = model?.dependencies?.values ?: emptyList()
    return packages
    .filter { it.platforms?.android != null }
    + .filter { instantApp && it.instantApp !== false }
    + .filter { if (instantApp) it?.instantApp !== false else true }
    // The pure C++ dependencies won't have a .java/.kt file to import
    .filterNot { it.platforms?.android?.isPureCxxDependency == true }
    .associate { it.name to checkNotNull(it.platforms?.android) }
    2 changes: 1 addition & 1 deletion ModelAutolinkingDependenciesJson.diff
    Original file line number Diff line number Diff line change
    @@ -10,7 +10,7 @@ package com.facebook.react.model
    data class ModelAutolinkingDependenciesJson(
    val root: String,
    val name: String,
    + val instantApp: Boolean,
    + val instantApp: Boolean?,
    val platforms: ModelAutolinkingDependenciesPlatformJson?
    ) {
    val nameCleansed: String
    5 changes: 4 additions & 1 deletion ReactExtension.diff
    Original file line number Diff line number Diff line change
    @@ -166,12 +166,14 @@ abstract class ReactExtension @Inject constructor(val project: Project) {
    + *
    + * @param instantApp Instant app activated or not
    */
    - fun autolinkLibrariesWithApp() {
    + fun autolinkLibrariesWithApp(instantApp: Boolean = false) {
    val inputFile =
    project.rootProject.layout.buildDirectory
    .file("generated/autolinking/autolinking.json")
    .get()
    .asFile
    - val dependenciesToApply = getGradleDependenciesToApply(inputFile)
    + val dependenciesToApply = getGradleDependenciesToApply(inputFile, instantApp)
    dependenciesToApply.forEach { (configuration, path) ->
    project.dependencies.add(configuration, project.dependencies.project(mapOf("path" to path)))
    @@ -189,14 +191,15 @@ abstract class ReactExtension @Inject constructor(val project: Project) {
    + * @param instantApp Instant app activated or not
    * @return A list of Gradle Configuration <-> Project name pairs.
    */
    - internal fun getGradleDependenciesToApply(inputFile: File): MutableList<Pair<String, String>> {
    + internal fun getGradleDependenciesToApply(inputFile: File, instantApp: Boolean): MutableList<Pair<String, String>> {
    val model = JsonUtils.fromAutolinkingConfigJson(inputFile)
    val result = mutableListOf<Pair<String, String>>()
    model
    ?.dependencies
    ?.values
    ?.filter { it.platforms?.android !== null }
    + ?.filter { instantApp && it.instantApp !== false }
    + ?.filter { if (instantApp) it?.instantApp !== false else true }
    ?.filterNot { it.platforms?.android?.isPureCxxDependency == true }
    ?.forEach { deps ->
    val nameCleansed = deps.nameCleansed
  5. ridvanaltun revised this gist Oct 25, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -6,7 +6,7 @@ My approach is keeping the autolinking mechanism and use `react-native.config.js

    Following changes are necessary:

    1. Mark don't needed libraries with `instantApp` key
    1. Mark don't needed libraries with `instantApp` key in `react-native.config.js` file:

    ```js
    module.exports = {
  6. ridvanaltun revised this gist Oct 25, 2024. No changes.
  7. ridvanaltun revised this gist Oct 25, 2024. 1 changed file with 10 additions and 0 deletions.
    10 changes: 10 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -46,22 +46,32 @@ react {

    `node_modules/@react-native-community/cli-config/build/schema.js`

    Go to file: https://gist.github.com/ridvanaltun/5bf4e072e8db7a4ecd93770c542b1387#file-schema-diff

    5. Update `ReactExtension.kt` in `@react-native/gradle-plugin`:

    `node_modules/@react-native/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt`

    Go to file: https://gist.github.com/ridvanaltun/5bf4e072e8db7a4ecd93770c542b1387#file-reactextension-diff

    6. Update `ReactPlugin.kt` in `@react-native/gradle-plugin`:

    `node_modules/@react-native/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt`

    Go to file: https://gist.github.com/ridvanaltun/5bf4e072e8db7a4ecd93770c542b1387#file-reactplugin-diff

    7. Update `GeneratePackageListTask.kt` in `@react-native/gradle-plugin`:

    `node_modules/@react-native/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GeneratePackageListTask.kt`

    Go to file: https://gist.github.com/ridvanaltun/5bf4e072e8db7a4ecd93770c542b1387#file-generatepackagelisttask-diff

    8. Update `ModelAutolinkingDependenciesJson.kt` in `@react-native/gradle-plugin`:

    `node_modules/@react-native/gradle-plugin/shared/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesJson.kt`

    Go to file: https://gist.github.com/ridvanaltun/5bf4e072e8db7a4ecd93770c542b1387#file-modelautolinkingdependenciesjson-diff

    After all this changes we need patch all of them

    ```sh
  8. ridvanaltun created this gist Oct 25, 2024.
    206 changes: 206 additions & 0 deletions GeneratePackageListTask.diff
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,206 @@
    /*
    * Copyright (c) Meta Platforms, Inc. and affiliates.
    *
    * This source code is licensed under the MIT license found in the
    * LICENSE file in the root directory of this source tree.
    */

    package com.facebook.react.tasks

    import com.facebook.react.model.ModelAutolinkingConfigJson
    import com.facebook.react.model.ModelAutolinkingDependenciesPlatformAndroidJson
    import com.facebook.react.utils.JsonUtils
    import java.io.File
    import org.gradle.api.DefaultTask
    import org.gradle.api.file.DirectoryProperty
    import org.gradle.api.file.RegularFileProperty
    +import org.gradle.api.tasks.Input
    import org.gradle.api.tasks.InputFile
    import org.gradle.api.tasks.OutputDirectory
    import org.gradle.api.tasks.TaskAction

    abstract class GeneratePackageListTask : DefaultTask() {

    init {
    group = "react"
    }

    @get:InputFile abstract val autolinkInputFile: RegularFileProperty

    + @Input
    + var instantApp: Boolean = false

    @get:OutputDirectory abstract val generatedOutputDirectory: DirectoryProperty

    @TaskAction
    fun taskAction() {
    val model =
    JsonUtils.fromAutolinkingConfigJson(autolinkInputFile.get().asFile)
    ?: error(
    """
    RNGP - Autolinking: Could not parse autolinking config file:
    ${autolinkInputFile.get().asFile.absolutePath}

    The file is either missing or not containing valid JSON so the build won't succeed.
    """
    .trimIndent())

    val packageName =
    model.project?.android?.packageName
    ?: error(
    "RNGP - Autolinking: Could not find project.android.packageName in react-native config output! Could not autolink packages without this field.")

    val androidPackages = filterAndroidPackages(model)
    val packageImports = composePackageImports(packageName, androidPackages)
    val packageClassInstance = composePackageInstance(packageName, androidPackages)
    val generatedFileContents = composeFileContent(packageImports, packageClassInstance)

    val outputDir = generatedOutputDirectory.get().asFile
    outputDir.mkdirs()
    File(outputDir, GENERATED_FILENAME).apply {
    parentFile.mkdirs()
    writeText(generatedFileContents)
    }
    }

    internal fun composePackageImports(
    packageName: String,
    packages: Map<String, ModelAutolinkingDependenciesPlatformAndroidJson>
    ) =
    packages.entries.joinToString("\n") { (name, dep) ->
    val packageImportPath =
    requireNotNull(dep.packageImportPath) {
    "RNGP - Autolinking: Missing `packageImportPath` in `config` for dependency $name. This is required to generate the autolinking package list."
    }
    "// $name\n${interpolateDynamicValues(packageImportPath, packageName)}"
    }

    internal fun composePackageInstance(
    packageName: String,
    packages: Map<String, ModelAutolinkingDependenciesPlatformAndroidJson>
    ) =
    if (packages.isEmpty()) {
    ""
    } else {
    ",\n " +
    packages.entries.joinToString(",\n ") { (name, dep) ->
    val packageInstance =
    requireNotNull(dep.packageInstance) {
    "RNGP - Autolinking: Missing `packageInstance` in `config` for dependency $name. This is required to generate the autolinking package list."
    }
    interpolateDynamicValues(packageInstance, packageName)
    }
    }

    internal fun filterAndroidPackages(
    model: ModelAutolinkingConfigJson?
    ): Map<String, ModelAutolinkingDependenciesPlatformAndroidJson> {
    val packages = model?.dependencies?.values ?: emptyList()
    return packages
    .filter { it.platforms?.android != null }
    + .filter { instantApp && it.instantApp !== false }
    // The pure C++ dependencies won't have a .java/.kt file to import
    .filterNot { it.platforms?.android?.isPureCxxDependency == true }
    .associate { it.name to checkNotNull(it.platforms?.android) }
    }

    internal fun composeFileContent(packageImports: String, packageClassInstance: String): String =
    generatedFileContentsTemplate
    .replace("{{ packageImports }}", packageImports)
    .replace("{{ packageClassInstances }}", packageClassInstance)

    companion object {
    const val GENERATED_FILENAME = "com/facebook/react/PackageList.java"

    /**
    * Before adding the package replacement mechanism, BuildConfig and R classes were imported
    * automatically into the scope of the file. We want to replace all non-FQDN references to those
    * classes with the package name of the MainApplication.
    *
    * We want to match "R" or "BuildConfig":
    * - new Package(R.string…),
    * - Module.configure(BuildConfig);
    * ^ hence including (BuildConfig|R)
    * but we don't want to match "R":
    * - new Package(getResources…),
    * - new PackageR…,
    * - new Royal…,
    * ^ hence excluding \w before and after matches
    * and "BuildConfig" that has FQDN reference:
    * - Module.configure(com.acme.BuildConfig);
    * ^ hence excluding . before the match.
    */
    internal fun interpolateDynamicValues(input: String, packageName: String): String =
    input.replace(Regex("([^.\\w])(BuildConfig|R)(\\W)")) { match ->
    val (prefix, className, suffix) = match.destructured
    "${prefix}${packageName}.${className}${suffix}"
    }

    // language=java
    val generatedFileContentsTemplate =
    """
    package com.facebook.react;

    import android.app.Application;
    import android.content.Context;
    import android.content.res.Resources;

    import com.facebook.react.ReactPackage;
    import com.facebook.react.shell.MainPackageConfig;
    import com.facebook.react.shell.MainReactPackage;
    import java.util.Arrays;
    import java.util.ArrayList;

    {{ packageImports }}

    public class PackageList {
    private Application application;
    private ReactNativeHost reactNativeHost;
    private MainPackageConfig mConfig;

    public PackageList(ReactNativeHost reactNativeHost) {
    this(reactNativeHost, null);
    }

    public PackageList(Application application) {
    this(application, null);
    }

    public PackageList(ReactNativeHost reactNativeHost, MainPackageConfig config) {
    this.reactNativeHost = reactNativeHost;
    mConfig = config;
    }

    public PackageList(Application application, MainPackageConfig config) {
    this.reactNativeHost = null;
    this.application = application;
    mConfig = config;
    }

    private ReactNativeHost getReactNativeHost() {
    return this.reactNativeHost;
    }

    private Resources getResources() {
    return this.getApplication().getResources();
    }

    private Application getApplication() {
    if (this.reactNativeHost == null) return this.application;
    return this.reactNativeHost.getApplication();
    }

    private Context getApplicationContext() {
    return this.getApplication().getApplicationContext();
    }

    public ArrayList<ReactPackage> getPackages() {
    return new ArrayList<>(Arrays.<ReactPackage>asList(
    new MainReactPackage(mConfig){{ packageClassInstances }}
    ));
    }
    }
    """
    .trimIndent()
    }
    }
    18 changes: 18 additions & 0 deletions ModelAutolinkingDependenciesJson.diff
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,18 @@
    /*
    * Copyright (c) Meta Platforms, Inc. and affiliates.
    *
    * This source code is licensed under the MIT license found in the
    * LICENSE file in the root directory of this source tree.
    */

    package com.facebook.react.model

    data class ModelAutolinkingDependenciesJson(
    val root: String,
    val name: String,
    + val instantApp: Boolean,
    val platforms: ModelAutolinkingDependenciesPlatformJson?
    ) {
    val nameCleansed: String
    get() = name.replace(Regex("[~*!'()]+"), "_").replace(Regex("^@([\\w-.]+)/"), "$1_")
    }
    72 changes: 72 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,72 @@
    I'm following this instruction for reduce Instant App bundle size: https://github.com/codibly/app-clip-instant-app-react-native/blob/b33afbf0fb559bcbfe464be31c6297c61ce117ac/Handling-Size-React-Native-InstantApp.md

    The author suggesting disable autolinking for Instant Apps to reduce app size. Unfortunately the document outdated and it's hard to apply for newer React-Native versions. I tried to apply same thing and failed.

    My approach is keeping the autolinking mechanism and use `react-native.config.js` file to select which libraries will be linked or not.

    Following changes are necessary:

    1. Mark don't needed libraries with `instantApp` key

    ```js
    module.exports = {
    dependencies: {
    ['react-native-camera']: {
    instantApp: false,
    },
    },
    };
    ```

    2. Update instantApp/build.gradle file:

    ```gradle
    react {
    // ...
    instantApp = true
    /* Autolinking */
    autolinkLibrariesWithApp(true)
    }
    ```

    3. Update app/build.gradle file:

    ```gradle
    react {
    // ...
    /* Autolinking */
    autolinkLibrariesWithApp(false)
    }
    ```

    4. Update `@react-native-community/cli-config`:

    `node_modules/@react-native-community/cli-config/build/schema.js`

    5. Update `ReactExtension.kt` in `@react-native/gradle-plugin`:

    `node_modules/@react-native/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactExtension.kt`

    6. Update `ReactPlugin.kt` in `@react-native/gradle-plugin`:

    `node_modules/@react-native/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/ReactPlugin.kt`

    7. Update `GeneratePackageListTask.kt` in `@react-native/gradle-plugin`:

    `node_modules/@react-native/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GeneratePackageListTask.kt`

    8. Update `ModelAutolinkingDependenciesJson.kt` in `@react-native/gradle-plugin`:

    `node_modules/@react-native/gradle-plugin/shared/src/main/kotlin/com/facebook/react/model/ModelAutolinkingDependenciesJson.kt`

    After all this changes we need patch all of them

    ```sh
    npx patch-package @react-native-community/cli-config
    npx patch-package @react-native/gradle-plugin
    ```

    I think thats all.
    217 changes: 217 additions & 0 deletions ReactExtension.diff
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,217 @@
    /*
    * Copyright (c) Meta Platforms, Inc. and affiliates.
    *
    * This source code is licensed under the MIT license found in the
    * LICENSE file in the root directory of this source tree.
    */

    package com.facebook.react

    import com.facebook.react.utils.JsonUtils
    import com.facebook.react.utils.projectPathToLibraryName
    import java.io.File
    import javax.inject.Inject
    import org.gradle.api.Project
    import org.gradle.api.file.DirectoryProperty
    import org.gradle.api.file.RegularFileProperty
    import org.gradle.api.provider.ListProperty
    import org.gradle.api.provider.Property

    abstract class ReactExtension @Inject constructor(val project: Project) {

    private val objects = project.objects

    /**
    * The path to the root of your project. This is the path to where the `package.json` lives. All
    * the CLI commands will be invoked from this folder as working directory.
    *
    * Default: ${rootProject.dir}/../
    */
    val root: DirectoryProperty =
    objects.directoryProperty().convention(project.rootProject.layout.projectDirectory.dir("../"))

    /**
    * The path to the react-native NPM package folder.
    *
    * Default: ${rootProject.dir}/../node_modules/react-native
    */
    val reactNativeDir: DirectoryProperty =
    objects.directoryProperty().convention(root.dir("node_modules/react-native"))

    /**
    * The path to the JS entry file. If not specified, the plugin will try to resolve it using a list
    * of known locations (e.g. `index.android.js`, `index.js`, etc.).
    */
    val entryFile: RegularFileProperty = objects.fileProperty()

    /**
    * The reference to the React Native CLI. If not specified, the plugin will try to resolve it
    * looking for `react-native` CLI inside `node_modules` in [root].
    */
    val cliFile: RegularFileProperty =
    objects.fileProperty().convention(reactNativeDir.file("cli.js"))

    /**
    * The path to the Node executable and extra args. By default it assumes that you have `node`
    * installed and configured in your $PATH. Default: ["node"]
    */
    val nodeExecutableAndArgs: ListProperty<String> =
    objects.listProperty(String::class.java).convention(listOf("node"))

    /** The command to use to invoke bundle. Default is `bundle` and will be invoked on [root]. */
    val bundleCommand: Property<String> = objects.property(String::class.java).convention("bundle")

    /**
    * Custom configuration file for the [bundleCommand]. If provided, it will be passed over with a
    * `--config` flag to the bundle command.
    */
    val bundleConfig: RegularFileProperty = objects.fileProperty()

    /**
    * The Bundle Asset name. This name will be used also for deriving other bundle outputs such as
    * the packager source map, the compiler source map and the output source map file.
    *
    * Default: index.android.bundle
    */
    val bundleAssetName: Property<String> =
    objects.property(String::class.java).convention("index.android.bundle")

    /**
    * Toggles the .so Cleanup step. If enabled, we will clean up all the unnecessary files before the
    * bundle task. If disabled, the developers will have to manually cleanup the files. Default: true
    */
    val enableSoCleanup: Property<Boolean> = objects.property(Boolean::class.java).convention(true)

    /** Extra args that will be passed to the [bundleCommand] Default: [] */
    val extraPackagerArgs: ListProperty<String> =
    objects.listProperty(String::class.java).convention(emptyList())

    /**
    * Allows to specify the debuggable variants (by default just 'debug'). Variants in this list will
    * not be bundled (the bundle file will not be created and won't be copied over).
    *
    * Default: ['debug']
    */
    val debuggableVariants: ListProperty<String> =
    objects.listProperty(String::class.java).convention(listOf("debug"))

    /** Hermes Config */

    /**
    * The command to use to invoke hermesc (the hermes compiler). Default is "", the plugin will
    * autodetect it.
    */
    val hermesCommand: Property<String> = objects.property(String::class.java).convention("")

    /**
    * Whether to enable Hermes only on certain variants. If specified as a non-empty list, hermesc
    * and the .so cleanup for Hermes will be executed only for variants in this list. An empty list
    * assumes you're either using Hermes for all variants or not (see [enableHermes]).
    *
    * Default: []
    */
    val enableHermesOnlyInVariants: ListProperty<String> =
    objects.listProperty(String::class.java).convention(emptyList())

    /** Flags to pass to Hermesc. Default: ["-O", "-output-source-map"] */
    val hermesFlags: ListProperty<String> =
    objects.listProperty(String::class.java).convention(listOf("-O", "-output-source-map"))

    /** Codegen Config */

    /**
    * The path to the react-native-codegen NPM package folder.
    *
    * Default: ${rootProject.dir}/../node_modules/@react-native/codegen
    */
    val codegenDir: DirectoryProperty =
    objects.directoryProperty().convention(root.dir("node_modules/@react-native/codegen"))

    /**
    * The root directory for all JS files for the app.
    *
    * Default: the parent folder of the `/android` folder.
    */
    val jsRootDir: DirectoryProperty = objects.directoryProperty()

    /**
    * The library name that will be used for the codegen artifacts.
    *
    * Default: <UpperCamelVersionOfProjectPath>Spec (e.g. for :example:project it will be
    * ExampleProjectSpec).
    */
    val libraryName: Property<String> =
    objects.property(String::class.java).convention(projectPathToLibraryName(project.path))

    /**
    * Java package name to use for any codegen artifacts produced during build time. Default:
    * com.facebook.fbreact.specs
    */
    val codegenJavaPackageName: Property<String> =
    objects.property(String::class.java).convention("com.facebook.fbreact.specs")

    + /**
    + * It will be used determine app is instant app or not
    + */
    + var instantApp: Boolean = false

    /** Auto-linking Utils */

    /**
    * Utility function to autolink libraries to the app.
    *
    * This function will read the autolinking configuration file and add Gradle dependencies to the
    * app. This function should be invoked inside the react {} block in the app's build.gradle and is
    * necessary for libraries to be linked correctly.
    + *
    + * @param instantApp Instant app activated or not
    */
    + fun autolinkLibrariesWithApp(instantApp: Boolean = false) {
    val inputFile =
    project.rootProject.layout.buildDirectory
    .file("generated/autolinking/autolinking.json")
    .get()
    .asFile
    + val dependenciesToApply = getGradleDependenciesToApply(inputFile, instantApp)
    dependenciesToApply.forEach { (configuration, path) ->
    project.dependencies.add(configuration, project.dependencies.project(mapOf("path" to path)))
    }
    }

    companion object {
    /**
    * Util function to construct a list of Gradle Configuration <-> Project name pairs for
    * autolinking. Pairs looks like: "implementation" -> ":react-native_oss-library-example"
    *
    * They will be applied to the Gradle project for linking the libraries.
    *
    * @param inputFile The file to read the autolinking configuration from.
    + * @param instantApp Instant app activated or not
    * @return A list of Gradle Configuration <-> Project name pairs.
    */
    + internal fun getGradleDependenciesToApply(inputFile: File, instantApp: Boolean): MutableList<Pair<String, String>> {
    val model = JsonUtils.fromAutolinkingConfigJson(inputFile)
    val result = mutableListOf<Pair<String, String>>()
    model
    ?.dependencies
    ?.values
    ?.filter { it.platforms?.android !== null }
    + ?.filter { instantApp && it.instantApp !== false }
    ?.filterNot { it.platforms?.android?.isPureCxxDependency == true }
    ?.forEach { deps ->
    val nameCleansed = deps.nameCleansed
    val dependencyConfiguration = deps.platforms?.android?.dependencyConfiguration
    val buildTypes = deps.platforms?.android?.buildTypes ?: emptyList()
    if (buildTypes.isEmpty()) {
    result.add((dependencyConfiguration ?: "implementation") to ":$nameCleansed")
    } else {
    buildTypes.forEach { buildType ->
    result.add(
    (dependencyConfiguration ?: "${buildType}Implementation") to ":$nameCleansed")
    }
    }
    }
    return result
    }
    }
    }
    270 changes: 270 additions & 0 deletions ReactPlugin.diff
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,270 @@
    /*
    * Copyright (c) Meta Platforms, Inc. and affiliates.
    *
    * This source code is licensed under the MIT license found in the
    * LICENSE file in the root directory of this source tree.
    */

    package com.facebook.react

    import com.android.build.api.variant.AndroidComponentsExtension
    import com.android.build.gradle.internal.tasks.factory.dependsOn
    import com.facebook.react.internal.PrivateReactExtension
    import com.facebook.react.tasks.GenerateAutolinkingNewArchitecturesFileTask
    import com.facebook.react.tasks.GenerateCodegenArtifactsTask
    import com.facebook.react.tasks.GenerateCodegenSchemaTask
    import com.facebook.react.tasks.GeneratePackageListTask
    import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForApp
    import com.facebook.react.utils.AgpConfiguratorUtils.configureBuildConfigFieldsForLibraries
    import com.facebook.react.utils.AgpConfiguratorUtils.configureDevPorts
    import com.facebook.react.utils.AgpConfiguratorUtils.configureNamespaceForLibraries
    import com.facebook.react.utils.BackwardCompatUtils.configureBackwardCompatibilityReactMap
    import com.facebook.react.utils.DependencyUtils.configureDependencies
    import com.facebook.react.utils.DependencyUtils.configureRepositories
    import com.facebook.react.utils.DependencyUtils.readVersionAndGroupStrings
    import com.facebook.react.utils.JdkConfiguratorUtils.configureJavaToolChains
    import com.facebook.react.utils.JsonUtils
    import com.facebook.react.utils.NdkConfiguratorUtils.configureReactNativeNdk
    import com.facebook.react.utils.ProjectUtils.isNewArchEnabled
    import com.facebook.react.utils.ProjectUtils.needsCodegenFromPackageJson
    import com.facebook.react.utils.ProjectUtils.shouldWarnIfNewArchFlagIsSetInPrealpha
    import com.facebook.react.utils.findPackageJsonFile
    import java.io.File
    import kotlin.system.exitProcess
    import org.gradle.api.Plugin
    import org.gradle.api.Project
    import org.gradle.api.Task
    import org.gradle.api.file.Directory
    import org.gradle.api.provider.Provider
    import org.gradle.internal.jvm.Jvm

    class ReactPlugin : Plugin<Project> {
    override fun apply(project: Project) {
    checkJvmVersion(project)
    val extension = project.extensions.create("react", ReactExtension::class.java, project)
    checkIfNewArchFlagIsSet(project, extension)

    // We register a private extension on the rootProject so that project wide configs
    // like codegen config can be propagated from app project to libraries.
    val rootExtension =
    project.rootProject.extensions.findByType(PrivateReactExtension::class.java)
    ?: project.rootProject.extensions.create(
    "privateReact", PrivateReactExtension::class.java, project)

    // App Only Configuration
    project.pluginManager.withPlugin("com.android.application") {
    // We wire the root extension with the values coming from the app (either user populated or
    // defaults).
    rootExtension.root.set(extension.root)
    rootExtension.reactNativeDir.set(extension.reactNativeDir)
    rootExtension.codegenDir.set(extension.codegenDir)
    rootExtension.nodeExecutableAndArgs.set(extension.nodeExecutableAndArgs)

    project.afterEvaluate {
    val reactNativeDir = extension.reactNativeDir.get().asFile
    val propertiesFile = File(reactNativeDir, "ReactAndroid/gradle.properties")
    val versionAndGroupStrings = readVersionAndGroupStrings(propertiesFile)
    val versionString = versionAndGroupStrings.first
    val groupString = versionAndGroupStrings.second
    configureDependencies(project, versionString, groupString)
    configureRepositories(project, reactNativeDir)
    }

    configureReactNativeNdk(project, extension)
    configureBuildConfigFieldsForApp(project, extension)
    configureDevPorts(project)
    configureBackwardCompatibilityReactMap(project)
    configureJavaToolChains(project)

    project.extensions.getByType(AndroidComponentsExtension::class.java).apply {
    onVariants(selector().all()) { variant ->
    project.configureReactTasks(variant = variant, config = extension)
    }
    }
    configureAutolinking(project, extension)
    configureCodegen(project, extension, rootExtension, isLibrary = false)
    }

    // Library Only Configuration
    configureBuildConfigFieldsForLibraries(project)
    configureNamespaceForLibraries(project)
    project.pluginManager.withPlugin("com.android.library") {
    configureCodegen(project, extension, rootExtension, isLibrary = true)
    }
    }

    private fun checkJvmVersion(project: Project) {
    val jvmVersion = Jvm.current()?.javaVersion?.majorVersion
    if ((jvmVersion?.toIntOrNull() ?: 0) <= 16) {
    project.logger.error(
    """

    ********************************************************************************

    ERROR: requires JDK17 or higher.
    Incompatible major version detected: '$jvmVersion'

    ********************************************************************************

    """
    .trimIndent())
    exitProcess(1)
    }
    }

    private fun checkIfNewArchFlagIsSet(project: Project, extension: ReactExtension) {
    if (project.shouldWarnIfNewArchFlagIsSetInPrealpha(extension)) {
    project.logger.warn(
    """

    ********************************************************************************

    WARNING: This version of React Native is ignoring the `newArchEnabled` flag you set. Please set it to true or remove it to suppress this warning.


    ********************************************************************************

    """
    .trimIndent())
    }
    }

    /** This function sets up `react-native-codegen` in our Gradle plugin. */
    @Suppress("UnstableApiUsage")
    private fun configureCodegen(
    project: Project,
    localExtension: ReactExtension,
    rootExtension: PrivateReactExtension,
    isLibrary: Boolean
    ) {
    // First, we set up the output dir for the codegen.
    val generatedSrcDir: Provider<Directory> =
    project.layout.buildDirectory.dir("generated/source/codegen")

    // We specify the default value (convention) for jsRootDir.
    // It's the root folder for apps (so ../../ from the Gradle project)
    // and the package folder for library (so ../ from the Gradle project)
    if (isLibrary) {
    localExtension.jsRootDir.convention(project.layout.projectDirectory.dir("../"))
    } else {
    localExtension.jsRootDir.convention(localExtension.root)
    }

    // We create the task to produce schema from JS files.
    val generateCodegenSchemaTask =
    project.tasks.register(
    "generateCodegenSchemaFromJavaScript", GenerateCodegenSchemaTask::class.java) { it ->
    it.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
    it.codegenDir.set(rootExtension.codegenDir)
    it.generatedSrcDir.set(generatedSrcDir)

    // We're reading the package.json at configuration time to properly feed
    // the `jsRootDir` @Input property of this task & the onlyIf. Therefore, the
    // parsePackageJson should be invoked inside this lambda.
    val packageJson = findPackageJsonFile(project, rootExtension.root)
    val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) }

    val jsSrcsDirInPackageJson = parsedPackageJson?.codegenConfig?.jsSrcsDir
    val includesGeneratedCode =
    parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false
    if (jsSrcsDirInPackageJson != null) {
    it.jsRootDir.set(File(packageJson.parentFile, jsSrcsDirInPackageJson))
    } else {
    it.jsRootDir.set(localExtension.jsRootDir)
    }
    val needsCodegenFromPackageJson =
    project.needsCodegenFromPackageJson(rootExtension.root)
    it.onlyIf { (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode }
    }

    // We create the task to generate Java code from schema.
    val generateCodegenArtifactsTask =
    project.tasks.register(
    "generateCodegenArtifactsFromSchema", GenerateCodegenArtifactsTask::class.java) {
    it.dependsOn(generateCodegenSchemaTask)
    it.reactNativeDir.set(rootExtension.reactNativeDir)
    it.nodeExecutableAndArgs.set(rootExtension.nodeExecutableAndArgs)
    it.generatedSrcDir.set(generatedSrcDir)
    it.packageJsonFile.set(findPackageJsonFile(project, rootExtension.root))
    it.codegenJavaPackageName.set(localExtension.codegenJavaPackageName)
    it.libraryName.set(localExtension.libraryName)

    // Please note that appNeedsCodegen is triggering a read of the package.json at
    // configuration time as we need to feed the onlyIf condition of this task.
    // Therefore, the appNeedsCodegen needs to be invoked inside this lambda.
    val needsCodegenFromPackageJson =
    project.needsCodegenFromPackageJson(rootExtension.root)
    val packageJson = findPackageJsonFile(project, rootExtension.root)
    val parsedPackageJson = packageJson?.let { JsonUtils.fromPackageJson(it) }
    val includesGeneratedCode =
    parsedPackageJson?.codegenConfig?.includesGeneratedCode ?: false
    it.onlyIf { (isLibrary || needsCodegenFromPackageJson) && !includesGeneratedCode }
    }

    // We update the android configuration to include the generated sources.
    // This equivalent to this DSL:
    //
    // android { sourceSets { main { java { srcDirs += "$generatedSrcDir/java" } } } }
    project.extensions.getByType(AndroidComponentsExtension::class.java).finalizeDsl { ext ->
    ext.sourceSets.getByName("main").java.srcDir(generatedSrcDir.get().dir("java").asFile)
    }

    // `preBuild` is one of the base tasks automatically registered by AGP.
    // This will invoke the codegen before compiling the entire project.
    project.tasks.named("preBuild", Task::class.java).dependsOn(generateCodegenArtifactsTask)
    }

    /** This function sets up Autolinking for App users */
    private fun configureAutolinking(
    project: Project,
    extension: ReactExtension,
    ) {
    val generatedAutolinkingJavaDir: Provider<Directory> =
    project.layout.buildDirectory.dir("generated/autolinking/src/main/java")
    val generatedAutolinkingJniDir: Provider<Directory> =
    project.layout.buildDirectory.dir("generated/autolinking/src/main/jni")

    // The autolinking.json file is available in the root build folder as it's generated
    // by ReactSettingsPlugin.kt
    val rootGeneratedAutolinkingFile =
    project.rootProject.layout.buildDirectory.file("generated/autolinking/autolinking.json")

    // We add a task called generateAutolinkingPackageList to do not clash with the existing task
    // called generatePackageList. This can to be renamed once we unlink the rn <-> cli
    // dependency.
    val generatePackageListTask =
    project.tasks.register(
    "generateAutolinkingPackageList", GeneratePackageListTask::class.java) { task ->
    task.autolinkInputFile.set(rootGeneratedAutolinkingFile)
    task.generatedOutputDirectory.set(generatedAutolinkingJavaDir)
    + task.instantApp = extension.instantApp
    }

    if (project.isNewArchEnabled(extension)) {
    // For New Arch, we also need to generate code for C++ Autolinking
    val generateAutolinkingNewArchitectureFilesTask =
    project.tasks.register(
    "generateAutolinkingNewArchitectureFiles",
    GenerateAutolinkingNewArchitecturesFileTask::class.java) { task ->
    task.autolinkInputFile.set(rootGeneratedAutolinkingFile)
    task.generatedOutputDirectory.set(generatedAutolinkingJniDir)
    }
    project.tasks
    .named("preBuild", Task::class.java)
    .dependsOn(generateAutolinkingNewArchitectureFilesTask)
    }

    // We let generateAutolinkingPackageList depend on the preBuild task so it's executed before
    // everything else.
    project.tasks.named("preBuild", Task::class.java).dependsOn(generatePackageListTask)

    // We tell Android Gradle Plugin that inside /build/generated/autolinking/src/main/java there
    // are sources to be compiled as well.
    project.extensions.getByType(AndroidComponentsExtension::class.java).apply {
    onVariants(selector().all()) { variant ->
    variant.sources.java?.addStaticSourceDirectory(
    generatedAutolinkingJavaDir.get().asFile.absolutePath)
    }
    }
    }
    }
    267 changes: 267 additions & 0 deletions schema.diff
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,267 @@
    'use strict';

    Object.defineProperty(exports, '__esModule', {
    value: true,
    });
    exports.projectConfig = exports.dependencyConfig = void 0;
    function _joi() {
    const data = _interopRequireDefault(require('joi'));
    _joi = function () {
    return data;
    };
    return data;
    }
    function _interopRequireDefault(obj) {
    return obj && obj.__esModule ? obj : {default: obj};
    }
    /**
    * This schema is used by `cli-config` to validate the structure. Make sure
    * this file stays up to date with `cli-types` package.
    *
    * In the future, it would be great to generate this file automatically from the
    * Typescript types.
    */

    const map = (key, value) =>
    _joi().default.object().unknown(true).pattern(key, value);

    /**
    * Schema for CommandT
    */
    const command = _joi().default.object({
    name: _joi().default.string().required(),
    description: _joi().default.string(),
    usage: _joi().default.string(),
    func: _joi().default.func().required(),
    options: _joi()
    .default.array()
    .items(
    _joi()
    .default.object({
    name: _joi().default.string().required(),
    description: _joi().default.string(),
    parse: _joi().default.func(),
    default: _joi()
    .default.alternatives()
    .try(
    _joi().default.bool(),
    _joi().default.number(),
    _joi().default.string().allow(''),
    _joi().default.func(),
    ),
    })
    .rename('command', 'name', {
    ignoreUndefined: true,
    }),
    ),
    examples: _joi()
    .default.array()
    .items(
    _joi().default.object({
    desc: _joi().default.string().required(),
    cmd: _joi().default.string().required(),
    }),
    ),
    });

    /**
    * Schema for HealthChecksT
    */
    const healthCheck = _joi().default.object({
    label: _joi().default.string().required(),
    healthchecks: _joi()
    .default.array()
    .items(
    _joi().default.object({
    label: _joi().default.string().required(),
    isRequired: _joi().default.bool(),
    description: _joi().default.string(),
    getDiagnostics: _joi().default.func(),
    win32AutomaticFix: _joi().default.func(),
    darwinAutomaticFix: _joi().default.func(),
    linuxAutomaticFix: _joi().default.func(),
    runAutomaticFix: _joi().default.func().required(),
    }),
    ),
    });

    /**
    * Schema for UserDependencyConfig
    */
    const dependencyConfig = _joi()
    .default.object({
    dependency: _joi()
    .default.object({
    platforms: map(_joi().default.string(), _joi().default.any())
    .keys({
    ios: _joi()
    .default // IOSDependencyParams
    .object({
    scriptPhases: _joi()
    .default.array()
    .items(_joi().default.object()),
    configurations: _joi()
    .default.array()
    .items(_joi().default.string())
    .default([]),
    })
    .allow(null),
    android: _joi()
    .default // AndroidDependencyParams
    .object({
    sourceDir: _joi().default.string(),
    manifestPath: _joi().default.string(),
    packageName: _joi().default.string(),
    packageImportPath: _joi().default.string(),
    packageInstance: _joi().default.string(),
    dependencyConfiguration: _joi().default.string(),
    buildTypes: _joi()
    .default.array()
    .items(_joi().default.string())
    .default([]),
    libraryName: _joi().default.string().allow(null),
    componentDescriptors: _joi()
    .default.array()
    .items(_joi().default.string())
    .allow(null),
    cmakeListsPath: _joi().default.string().allow(null),
    cxxModuleCMakeListsModuleName: _joi()
    .default.string()
    .allow(null),
    cxxModuleCMakeListsPath: _joi().default.string().allow(null),
    cxxModuleHeaderName: _joi().default.string().allow(null),
    })
    .allow(null),
    })
    .default(),
    })
    .default(),
    platforms: map(
    _joi().default.string(),
    _joi().default.object({
    npmPackageName: _joi().default.string().optional(),
    dependencyConfig: _joi().default.func(),
    projectConfig: _joi().default.func(),
    linkConfig: _joi().default.func(),
    }),
    ).default({}),
    commands: _joi().default.array().items(command).default([]),
    healthChecks: _joi().default.array().items(healthCheck).default([]),
    })
    .unknown(true)
    .default();

    /**
    * Schema for ProjectConfig
    */
    exports.dependencyConfig = dependencyConfig;
    const projectConfig = _joi()
    .default.object({
    dependencies: map(
    _joi().default.string(),
    _joi()
    .default.object({
    root: _joi().default.string(),
    + instantApp: _joi().default.bool(),
    platforms: map(_joi().default.string(), _joi().default.any()).keys({
    ios: _joi()
    .default // IOSDependencyConfig
    .object({
    podspecPath: _joi().default.string(),
    version: _joi().default.string(),
    configurations: _joi()
    .default.array()
    .items(_joi().default.string())
    .default([]),
    scriptPhases: _joi()
    .default.array()
    .items(_joi().default.object())
    .default([]),
    })
    .allow(null),
    android: _joi()
    .default // AndroidDependencyConfig
    .object({
    sourceDir: _joi().default.string(),
    packageImportPath: _joi().default.string(),
    packageInstance: _joi().default.string(),
    dependencyConfiguration: _joi().default.string(),
    buildTypes: _joi()
    .default.array()
    .items(_joi().default.string())
    .default([]),
    libraryName: _joi().default.string().allow(null),
    componentDescriptors: _joi()
    .default.array()
    .items(_joi().default.string())
    .allow(null),
    cmakeListsPath: _joi().default.string().allow(null),
    })
    .allow(null),
    }),
    })
    .allow(null),
    ).default({}),
    reactNativePath: _joi().default.string(),
    project: map(_joi().default.string(), _joi().default.any())
    .keys({
    ios: _joi()
    .default // IOSProjectParams
    .object({
    sourceDir: _joi().default.string(),
    watchModeCommandParams: _joi()
    .default.array()
    .items(_joi().default.string()),
    // @todo remove for RN 0.75
    unstable_reactLegacyComponentNames: _joi()
    .default.array()
    .items(_joi().default.string())
    .optional(),
    automaticPodsInstallation: _joi().default.bool().default(false),
    assets: _joi()
    .default.array()
    .items(_joi().default.string())
    .default([]),
    })
    .default({}),
    android: _joi()
    .default // AndroidProjectParams
    .object({
    sourceDir: _joi().default.string(),
    appName: _joi().default.string(),
    manifestPath: _joi().default.string(),
    packageName: _joi().default.string(),
    dependencyConfiguration: _joi().default.string(),
    watchModeCommandParams: _joi()
    .default.array()
    .items(_joi().default.string()),
    // @todo remove for RN 0.75
    unstable_reactLegacyComponentNames: _joi()
    .default.array()
    .items(_joi().default.string())
    .optional(),
    assets: _joi()
    .default.array()
    .items(_joi().default.string())
    .default([]),
    })
    .default({}),
    })
    .default(),
    assets: _joi().default.array().items(_joi().default.string()).default([]),
    commands: _joi().default.array().items(command).default([]),
    platforms: map(
    _joi().default.string(),
    _joi().default.object({
    npmPackageName: _joi().default.string().optional(),
    dependencyConfig: _joi().default.func(),
    projectConfig: _joi().default.func(),
    linkConfig: _joi().default.func(),
    }),
    ).default({}),
    })
    .unknown(true)
    .default();
    exports.projectConfig = projectConfig;

    //# sourceMappingURL=/Users/thymikee/Developer/oss/rncli/packages/cli-config/build/schema.js.map