react-native-config-ultimate 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +138 -0
  3. package/android/build.gradle +180 -0
  4. package/android/rncu.gradle +132 -0
  5. package/android/src/main/AndroidManifest.xml +4 -0
  6. package/android/src/main/java/com/reactnativeultimateconfig/UltimateConfigModule.java +56 -0
  7. package/android/src/main/java/com/reactnativeultimateconfig/UltimateConfigPackage.java +53 -0
  8. package/bin.js +5 -0
  9. package/index.js +9 -0
  10. package/index.ts +18 -0
  11. package/ios/ConfigValues.h +1 -0
  12. package/ios/UltimateConfig.h +12 -0
  13. package/ios/UltimateConfig.mm +27 -0
  14. package/ios/UltimateConfig.xcodeproj/project.pbxproj +274 -0
  15. package/override.js +1 -0
  16. package/package.json +110 -0
  17. package/react-native-config-ultimate.podspec +41 -0
  18. package/src/NativeUltimateConfig.js +4 -0
  19. package/src/NativeUltimateConfig.ts +15 -0
  20. package/src/bin.spec.ts +36 -0
  21. package/src/cli.js +177 -0
  22. package/src/cli.spec.ts +224 -0
  23. package/src/cli.ts +166 -0
  24. package/src/flatten.js +22 -0
  25. package/src/flatten.spec.ts +16 -0
  26. package/src/flatten.ts +26 -0
  27. package/src/load-env.js +107 -0
  28. package/src/load-env.spec.ts +163 -0
  29. package/src/load-env.ts +84 -0
  30. package/src/main.js +34 -0
  31. package/src/main.spec.ts +171 -0
  32. package/src/main.ts +39 -0
  33. package/src/render-env.js +110 -0
  34. package/src/render-env.ts +115 -0
  35. package/src/resolve-env.js +12 -0
  36. package/src/resolve-env.spec.ts +25 -0
  37. package/src/resolve-env.ts +45 -0
  38. package/src/templates/ConfigValues.h.handlebars +24 -0
  39. package/src/templates/index.d.ts.handlebars +18 -0
  40. package/src/templates/index.web.js.handlebars +1 -0
  41. package/src/templates/override.js.handlebars +16 -0
  42. package/src/templates/rncu.xcconfig.handlebars +4 -0
  43. package/src/templates/rncu.yaml.handlebars +7 -0
  44. package/src/validate-env.js +53 -0
  45. package/src/validate-env.spec.ts +164 -0
  46. package/src/validate-env.ts +68 -0
  47. package/src/write-env.js +99 -0
  48. package/src/write-env.spec.ts +105 -0
  49. package/src/write-env.ts +67 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Maksym Komarychev
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # react-native-config-ultimate
2
+
3
+ _Config that works_
4
+
5
+ [![NPM](https://img.shields.io/npm/l/react-native-config-ultimate)](https://www.npmjs.com/package/react-native-config-ultimate)
6
+ [![npm](https://img.shields.io/npm/v/react-native-config-ultimate?color=green&label=version)](https://www.npmjs.com/package/react-native-config-ultimate)
7
+ [![npm](https://img.shields.io/npm/dw/react-native-config-ultimate?color=green)](https://www.npmjs.com/package/react-native-config-ultimate)
8
+
9
+ ---
10
+
11
+ > **This is a community-maintained fork** of
12
+ > [`react-native-ultimate-config`](https://github.com/maxkomarychev/react-native-ultimate-config)
13
+ > originally created by [Max Komarychev](https://github.com/maxkomarychev).
14
+ >
15
+ > The original library has not received updates since September 2023 and does not
16
+ > support React Native's New Architecture (TurboModules), React 19, or modern
17
+ > tooling. This fork picks up where it left off.
18
+ >
19
+ > **Full credit and gratitude to Max** for the original design, architecture, and
20
+ > years of maintenance. This project would not exist without his work.
21
+ > The MIT license is preserved in its entirety.
22
+
23
+ ---
24
+
25
+ ## Gradle compatibility
26
+
27
+ | react-native-config-ultimate | gradle |
28
+ | ---------------------------- | ------ |
29
+ | ^7 | 8 |
30
+
31
+ > For older versions see the original package [`react-native-ultimate-config`](https://github.com/maxkomarychev/react-native-ultimate-config).
32
+
33
+ ## React Native compatibility
34
+
35
+ | react-native-config-ultimate | react-native | react | New Architecture |
36
+ | ---------------------------- | ------------ | ------ | ---------------- |
37
+ | ^7 | >=0.60 <1.x | >=19 | ✅ TurboModules |
38
+
39
+ ## TL;DR usage
40
+
41
+ 1. install
42
+ | npm | yarn |
43
+ |-|-|
44
+ |`npm install react-native-config-ultimate` | `yarn add react-native-config-ultimate`|
45
+ 2. [one-off setup for native projects](./docs/quickstart.md)
46
+ 3. initialize env
47
+ | npm | yarn |
48
+ |-|-|
49
+ |`npx rncu .env`|`yarn rncu .env`|
50
+ 4. build! `react-native run-{ios,android}`
51
+
52
+ ## ☝❗Approach to versioning and breaking changes
53
+
54
+ This library is using [semver](https://semver.org/) and heavily relying on codegeneration. Many new features and/or bugfixes will require these files to be regenerated. Changes to codegenerated files will not be considered breaking
55
+ unless they affect behavior of API or CLI.
56
+
57
+ Therefore every time this library is updated all files MUST be regenerated using `rncu` command.
58
+
59
+ ## Table of contents
60
+
61
+ 1. [Features 🎆](#features)
62
+ 1. [Mission 🥾](#mission)
63
+ 1. [Quickstart Guide 🏃](./docs/quickstart.md)
64
+ 1. [API 🧰](./docs/api.md)
65
+ 1. [Testing Guide 🧪](./docs/testing.md)
66
+ 1. [Changelog 📓](./packages/react-native-ultimate-config/CHANGELOG.md)
67
+ 1. [Cookbook 🥦](./docs/cookbook.md)
68
+ 1. [Troubleshooting 🎱](./docs/troubleshooting.md)
69
+ 1. [Contributor notes](./docs/contributor-notes.md)
70
+ 1. [Alternatives](./docs/alternatives.md)
71
+
72
+ ## Features
73
+
74
+ 1. Simple one-off [setup](./docs/quickstart.md) for native projects
75
+ 1. No need to mess with xcode schemes or android flavors
76
+ 1. Access from [javascript](./docs/api.md#javascript)
77
+ 1. Access from native code: [java](./docs/api.md#java) and [objective-c](./docs/api.md#objective-c)
78
+ 1. Access in build tools: [xcode](./docs/api.md#ios), [gradle](./docs/api.md#buildgradle) and [AndroidManifest.xml](./docs/api.md#androidmanifestxml)
79
+ 1. [Web support](./docs/api.md#web) (works with React Native for Web)
80
+ 1. [Hooks](./docs/api.md#hooks)
81
+ 1. [Monorepo support](./docs/monorepo-tips.md) (yarn workspaces or lerna)
82
+ 1. **[New Architecture](./docs/api.md#new-architecture)** — TurboModules support (RN 0.68+), fully backward-compatible with old arch
83
+ 1. **[Multi-env file merging](./docs/api.md#multi-env-file-merging)** — `rncu .env.base .env.staging` (v7+)
84
+ 1. **[Dotenv variable expansion](./docs/api.md#dotenv-variable-expansion)** — `API_URL=$BASE_URL/v1` (v7+)
85
+ 1. **[Schema validation](./docs/api.md#schema-validation)** — fail at build time on missing or invalid vars (v7+)
86
+ 1. Unit tested with jest (83 tests)
87
+ 1. Written in TypeScript with strict mode — [exact typings](./docs/api.md#typescript) generated for your env vars
88
+ 1. Supports [dotenv and yaml](./docs/api.md#files)
89
+ 1. [Fully typed](./docs/api.md#note-about-types) values available when using yaml config
90
+ 1. Configure values [per platform](./docs/api.md#per-platform-values) in one file
91
+
92
+ ## Mission
93
+
94
+ React-Native brings together 3 platforms: ios, android, javascript each of
95
+ which have different conventions and approaches how to manage environment
96
+ settings.
97
+
98
+ A typical app is usually operating in some environment defined by server urls
99
+ various api keys or feature flags. When dealing with react-native such things
100
+ often need to exist in 3 places: ios, android and js code. Even managing things
101
+ as simple as application name or bundle id needs to be done in 2 places:
102
+ `Info.plist` and `AndroidManifest.xml`
103
+
104
+ `react-native-config-ultimate` tries to reduce friction in managing these things
105
+ by abstracting away from nuances of native projects.
106
+
107
+ With `react-native-config-ultimate` it is possible to [consume](./docs/api.md) variables in
108
+ every place of a typical react-native app:
109
+
110
+ - javascript
111
+ - native code
112
+ - java
113
+ - objective-c
114
+ - native build configuration
115
+ - ios
116
+ - build settings
117
+ - infoplist
118
+ - android
119
+ - build config
120
+ - string resources
121
+ - project.ext
122
+
123
+ ```
124
+ |-------------------------------------------------------|
125
+ | |
126
+ | javascript |
127
+ | |
128
+ |-------------------------------------------------------|
129
+ | | |
130
+ | objective-c | java |
131
+ | | |
132
+ |-------------------------------------------------------|
133
+ | | |
134
+ | build settings | AndroidManifest.xml |
135
+ | infoplist | build.gradle |
136
+ | | |
137
+ |-------------------------------------------------------|
138
+ ```
@@ -0,0 +1,180 @@
1
+ buildscript {
2
+ repositories {
3
+ google()
4
+ mavenCentral()
5
+ }
6
+
7
+ dependencies {
8
+ classpath "com.android.tools.build:gradle:7.2.1"
9
+ }
10
+ }
11
+
12
+ def safeExtGet(prop, fallback) {
13
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
14
+ }
15
+
16
+ def resolveReactNativeDirectory() {
17
+ def reactNativeLocation = safeExtGet("REACT_NATIVE_NODE_MODULES_DIR", null)
18
+ if (reactNativeLocation != null) {
19
+ println "Using react-native dir from REACT_NATIVE_NODE_MODULES_DIR:" + reactNativeLocation
20
+ return file(reactNativeLocation)
21
+ }
22
+
23
+ // monorepo workaround
24
+ // react-native can be hoisted or in project's own node_modules
25
+ def reactNativeFromProjectNodeModules = file("${rootProject.projectDir}/../node_modules/react-native")
26
+ println "Using react-native dir from default location:" + reactNativeFromProjectNodeModules
27
+ if (reactNativeFromProjectNodeModules.exists()) {
28
+ return reactNativeFromProjectNodeModules
29
+ }
30
+
31
+ throw new Exception(
32
+ "[react-native-config-ultimate] Unable to resolve react-native location in " +
33
+ "node_modules. You should add project extension property (in app/build.gradle) " +
34
+ "`REACT_NATIVE_NODE_MODULES_DIR` with path to react-native."
35
+ )
36
+ }
37
+
38
+ def isNewArchitectureEnabled() {
39
+ return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true"
40
+ }
41
+
42
+ apply plugin: 'com.android.library'
43
+ apply plugin: 'maven-publish'
44
+
45
+ // Apply the React Native Gradle Plugin for New Architecture support (TurboModules, Codegen).
46
+ // When newArchEnabled=true, this plugin runs Codegen to generate NativeUltimateConfigSpec.
47
+ if (isNewArchitectureEnabled()) {
48
+ apply plugin: "com.facebook.react"
49
+ }
50
+
51
+ def getExtOrDefault(name) {
52
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["UltimateConfig_" + name]
53
+ }
54
+
55
+ def getExtOrIntegerDefault(name) {
56
+ return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["UltimateConfig_" + name]).toInteger()
57
+ }
58
+
59
+ def REACT_NATIVE_DIR = resolveReactNativeDirectory()
60
+
61
+ def reactProperties = new Properties()
62
+ file("$REACT_NATIVE_DIR/ReactAndroid/gradle.properties").withInputStream { reactProperties.load(it) }
63
+
64
+ def REACT_NATIVE_VERSION = reactProperties.getProperty("VERSION_NAME")
65
+ def REACT_NATIVE_MINOR_VERSION = REACT_NATIVE_VERSION.startsWith("0.0.0-") ? 1000 : REACT_NATIVE_VERSION.split("\\.")[1].toInteger()
66
+
67
+ android {
68
+ // 'namespace' is required by Android Gradle Plugin 8.0+.
69
+ // For AGP < 8.0 this property is silently ignored.
70
+ namespace "com.reactnativeultimateconfig"
71
+
72
+ compileSdkVersion getExtOrIntegerDefault("compileSdkVersion")
73
+
74
+ defaultConfig {
75
+ minSdkVersion getExtOrIntegerDefault("minSdkVersion")
76
+ targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
77
+ buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
78
+ }
79
+ buildTypes {
80
+ release {
81
+ minifyEnabled false
82
+ }
83
+ }
84
+
85
+ lintOptions {
86
+ disable "GradleCompatible"
87
+ }
88
+
89
+ // Java 11 is the minimum for React Native 0.73+; Java 17 is recommended for RN 0.74+.
90
+ compileOptions {
91
+ sourceCompatibility JavaVersion.VERSION_17
92
+ targetCompatibility JavaVersion.VERSION_17
93
+ }
94
+ }
95
+
96
+ repositories {
97
+ mavenCentral()
98
+ google()
99
+ }
100
+
101
+
102
+ dependencies {
103
+ // For < 0.71, this will be from the local maven repo.
104
+ // For >= 0.71, version is substituted by the React Native Gradle Plugin (RNGP).
105
+ //noinspection GradleDynamicVersion
106
+ if (REACT_NATIVE_MINOR_VERSION >= 71) {
107
+ implementation "com.facebook.react:react-android:+" // version substituted by RNGP
108
+ } else {
109
+ implementation 'com.facebook.react:react-native:+' // from node_modules
110
+ }
111
+ }
112
+
113
+ // Codegen configuration — only runs when New Architecture is enabled.
114
+ // Reads NativeUltimateConfig.ts (in src/) and generates NativeUltimateConfigSpec.java.
115
+ if (isNewArchitectureEnabled()) {
116
+ react {
117
+ jsRootDir = file("../src/")
118
+ libraryName = "UltimateConfig"
119
+ codegenJavaPackageName = "com.reactnativeultimateconfig"
120
+ }
121
+ }
122
+
123
+ afterEvaluate { project ->
124
+ task androidSourcesJar(type: Jar) {
125
+ archiveClassifier = 'sources'
126
+ from android.sourceSets.main.java.srcDirs
127
+ include '**/*.java'
128
+ }
129
+
130
+ android.libraryVariants.all { variant ->
131
+ def name = variant.name.capitalize()
132
+ def javaCompileTask = variant.javaCompileProvider.get()
133
+
134
+ task "jar${name}"(type: Jar, dependsOn: javaCompileTask) {
135
+ from javaCompileTask.destinationDir
136
+ }
137
+ }
138
+
139
+ artifacts {
140
+ archives androidSourcesJar
141
+ }
142
+
143
+ publishing {
144
+ publications {
145
+ maven(MavenPublication) {
146
+ def packageJson = new groovy.json.JsonSlurper().parseText(file('../package.json').text)
147
+
148
+ pom {
149
+ name = packageJson.title
150
+ artifactId = packageJson.name
151
+ version = packageJson.version
152
+ group = "com.reactnativeultimateconfig"
153
+ description packageJson.description
154
+ url = packageJson.repository.url
155
+
156
+ licenses {
157
+ license {
158
+ name = packageJson.license
159
+ url = packageJson.repository.baseUrl + '/blob/master/' + packageJson.licenseFilename
160
+ distribution = 'repo'
161
+ }
162
+ }
163
+
164
+ developers {
165
+ developer {
166
+ id = packageJson.author.username
167
+ name = packageJson.author.name
168
+ }
169
+ }
170
+ }
171
+ }
172
+ }
173
+
174
+ repositories {
175
+ maven {
176
+ url "file://${projectDir}/../android/maven"
177
+ }
178
+ }
179
+ }
180
+ }
@@ -0,0 +1,132 @@
1
+ // DO NOT COMMIT OR EDIT THIS FILE
2
+ import java.nio.file.Paths
3
+
4
+ buildscript {
5
+ repositories {
6
+ mavenCentral()
7
+ }
8
+ dependencies {
9
+ classpath group: 'org.yaml', name: 'snakeyaml', version: '1.19'
10
+ }
11
+ }
12
+
13
+
14
+ def readYaml(yamlPath) {
15
+ def yamlFile = new File(yamlPath.toAbsolutePath().toString())
16
+ if (!yamlFile.exists()) {
17
+ throw new GradleException("yaml file at path ${yamlPath.toAbsolutePath().toString()} does not exist")
18
+ }
19
+ def cfg = new org.yaml.snakeyaml.Yaml().load(yamlFile.newInputStream())
20
+ if (
21
+ cfg instanceof String
22
+ || cfg instanceof Integer
23
+ || cfg instanceof Double
24
+ || cfg instanceof Boolean
25
+ ) {
26
+ throw new GradleException("could not read object from ${yamlPath} but got '${cfg.class.name}'")
27
+ } else if (cfg == null) {
28
+ throw new GradleException("could not read object from ${yamlPath} but got 'null'")
29
+ }
30
+ return cfg
31
+ }
32
+
33
+ def buildRootConfig() {
34
+ def flavorMappingExists = project.ext.has('flavorEnvMapping')
35
+ if (flavorMappingExists) {
36
+ def config = [:]
37
+ def flavorMapping = project.ext.get('flavorEnvMapping')
38
+ println "Flavor mapping detected: ${flavorMapping}"
39
+ flavorMapping.each { flavor, yamlPath ->
40
+ def absolutePath = Paths.get(project.rootDir.getAbsolutePath(), yamlPath)
41
+ config.put(flavor, readYaml(absolutePath))
42
+ }
43
+ return config
44
+ } else {
45
+ println "No flavor mapping detected. Reading injected file"
46
+ def cfg = readYaml(Paths.get(buildscript.sourceFile.getParent(), "rncu.yaml"))
47
+ return cfg
48
+ }
49
+ }
50
+
51
+ def flattenConfig(cfg) {
52
+ def result = [:]
53
+ cfg.each { k,v ->
54
+ if (
55
+ v instanceof String
56
+ || v instanceof Integer
57
+ || v instanceof Double
58
+ || v instanceof Boolean
59
+ ) {
60
+ result.put(k,v)
61
+ } else {
62
+ def androidValue = v.get("android")
63
+ if (androidValue == null) {
64
+ throw new GradleException("key ${k} is missing android value")
65
+ }
66
+ result.put(k, v.get("android"))
67
+ }
68
+ }
69
+ return result
70
+ }
71
+
72
+ def rootConfig = buildRootConfig()
73
+ println "Root config ${rootConfig}"
74
+
75
+ def configPerFlavor(flavorName, rootConfig) {
76
+ def mapping = project.ext.has('flavorEnvMapping')
77
+ if (mapping && !flavorName.isEmpty()) {
78
+ println "mapping exists"
79
+ def f = rootConfig.get(flavorName)
80
+ return f
81
+ } else {
82
+ println "mapping does not exist or flavor is not defined"
83
+ return rootConfig
84
+ }
85
+ }
86
+
87
+ def escape_double(str) {
88
+ return str.replace('"', '\\"')
89
+ }
90
+ def escape_single(str) {
91
+ return str.replace("'", "\\'")
92
+ }
93
+
94
+ android {
95
+ // patch all variants with config values
96
+ applicationVariants.all { variant ->
97
+ println "processing variant ${variant.name}"
98
+ def flavorName = variant.flavorName
99
+
100
+ def cfg = flattenConfig(configPerFlavor(flavorName, rootConfig))
101
+ println "config per flavor ${flavorName} ${cfg}"
102
+ def keySet = cfg.keySet().collect()
103
+ println "all keys ${keySet}"
104
+ def rncuKeys = java.lang.String.join(",", keySet)
105
+ println "all keys as string ${rncuKeys}"
106
+ variant.buildConfigField "String", "__RNCU_KEYS", "\"${rncuKeys}\""
107
+ variant.mergedFlavor.manifestPlaceholders.putAll(cfg)
108
+
109
+ cfg.each { k,v ->
110
+ if (v instanceof java.lang.String) {
111
+ variant.buildConfigField "String", k, "\"${escape_double(v)}\""
112
+ variant.resValue "string", k, "${escape_single(v)}"
113
+ } else if (v instanceof Integer) {
114
+ variant.buildConfigField "int", k, "${v}"
115
+ variant.resValue "integer", k, "${v}"
116
+ } else if (v instanceof Double) {
117
+ variant.buildConfigField "double", k, "${v}"
118
+ variant.resValue "string", k, "${v}"
119
+ } else if (v instanceof Boolean) {
120
+ variant.buildConfigField "boolean", k, "${v}"
121
+ variant.resValue "bool", k, "${v}"
122
+ } else {
123
+ throw new GradleException("unknown type for key ${k} in flavor ${flavorName}: ${v.getClass()}")
124
+ }
125
+ }
126
+
127
+ }
128
+ defaultConfig {
129
+ project.ext.set("config", rootConfig)
130
+ }
131
+ }
132
+
@@ -0,0 +1,4 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ package="com.reactnativeultimateconfig">
3
+
4
+ </manifest>
@@ -0,0 +1,56 @@
1
+ package com.reactnativeultimateconfig;
2
+
3
+ import android.util.Log;
4
+
5
+ import androidx.annotation.NonNull;
6
+ import androidx.annotation.Nullable;
7
+
8
+ import com.facebook.react.bridge.ReactApplicationContext;
9
+ import com.facebook.react.bridge.ReactContextBaseJavaModule;
10
+ import com.facebook.react.module.annotations.ReactModule;
11
+
12
+ import java.util.HashMap;
13
+ import java.util.Map;
14
+
15
+ @ReactModule(name = UltimateConfigModule.NAME)
16
+ public class UltimateConfigModule extends ReactContextBaseJavaModule {
17
+ public static final String NAME = "UltimateConfig";
18
+
19
+ @Nullable
20
+ private static Class<?> _buildConfig;
21
+
22
+ public static void setBuildConfig(Class<?> buildConfig) {
23
+ _buildConfig = buildConfig;
24
+ }
25
+
26
+ public UltimateConfigModule(ReactApplicationContext reactContext) {
27
+ super(reactContext);
28
+ }
29
+
30
+ @Override
31
+ @NonNull
32
+ public String getName() {
33
+ return NAME;
34
+ }
35
+
36
+ @Override
37
+ @NonNull
38
+ public Map<String, Object> getConstants() {
39
+ final Map<String, Object> constants = new HashMap<>();
40
+ try {
41
+ Class<?> act = _buildConfig;
42
+ if (act == null) return constants;
43
+ String keys = (String) act.getField("__RNUC_KEYS").get(act);
44
+ if (keys == null || keys.isEmpty()) return constants;
45
+ for (String k : keys.split(",")) {
46
+ Object value = act.getField(k).get(act);
47
+ if (value != null) {
48
+ constants.put(k, value);
49
+ }
50
+ }
51
+ } catch (Exception e) {
52
+ Log.w(NAME, "Failed to read config constants from BuildConfig: " + e.getMessage());
53
+ }
54
+ return constants;
55
+ }
56
+ }
@@ -0,0 +1,53 @@
1
+ package com.reactnativeultimateconfig;
2
+
3
+ import androidx.annotation.NonNull;
4
+ import androidx.annotation.Nullable;
5
+
6
+ import com.facebook.react.TurboReactPackage;
7
+ import com.facebook.react.bridge.NativeModule;
8
+ import com.facebook.react.bridge.ReactApplicationContext;
9
+ import com.facebook.react.module.model.ReactModuleInfo;
10
+ import com.facebook.react.module.model.ReactModuleInfoProvider;
11
+
12
+ import java.util.HashMap;
13
+ import java.util.Map;
14
+
15
+ /**
16
+ * React Native package for UltimateConfig.
17
+ *
18
+ * Extends TurboReactPackage to support both the Old Architecture (Bridge)
19
+ * and the New Architecture (TurboModules / JSI). When IS_NEW_ARCHITECTURE_ENABLED
20
+ * is true the module is registered as a TurboModule; otherwise it runs as a
21
+ * classic bridge module.
22
+ */
23
+ public class UltimateConfigPackage extends TurboReactPackage {
24
+
25
+ @Nullable
26
+ @Override
27
+ public NativeModule getModule(@NonNull String name, @NonNull ReactApplicationContext reactContext) {
28
+ if (UltimateConfigModule.NAME.equals(name)) {
29
+ return new UltimateConfigModule(reactContext);
30
+ }
31
+ return null;
32
+ }
33
+
34
+ @Override
35
+ public ReactModuleInfoProvider getReactModuleInfoProvider() {
36
+ return () -> {
37
+ final Map<String, ReactModuleInfo> moduleInfos = new HashMap<>();
38
+ moduleInfos.put(
39
+ UltimateConfigModule.NAME,
40
+ new ReactModuleInfo(
41
+ UltimateConfigModule.NAME,
42
+ UltimateConfigModule.NAME,
43
+ false, // canOverrideExistingModule
44
+ false, // needsEagerInit
45
+ true, // hasConstants — constants are the primary API
46
+ false, // isCxxModule
47
+ BuildConfig.IS_NEW_ARCHITECTURE_ENABLED // isTurboModule
48
+ )
49
+ );
50
+ return moduleInfos;
51
+ };
52
+ }
53
+ }
package/bin.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+
3
+ const cli = require("./src/cli").default;
4
+
5
+ cli();
package/index.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const react_native_1 = require("react-native");
4
+ // override.js is dynamically generated by the rncu CLI.
5
+ // It contains platform-specific overrides. Do not commit override.js to git.
6
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
7
+ const override = require('./override');
8
+ const { UltimateConfig } = react_native_1.NativeModules;
9
+ exports.default = Object.assign(Object.assign({}, UltimateConfig), override);
package/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { NativeModules } from 'react-native';
2
+ import type { ConfigValue } from './src/NativeUltimateConfig';
3
+
4
+ export type { ConfigValue, Spec } from './src/NativeUltimateConfig';
5
+
6
+ type Config = Record<string, ConfigValue>;
7
+
8
+ // override.js is dynamically generated by the rncu CLI.
9
+ // It contains platform-specific overrides. Do not commit override.js to git.
10
+ // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
11
+ const override: Config = require('./override');
12
+
13
+ const { UltimateConfig } = NativeModules;
14
+
15
+ export default {
16
+ ...(UltimateConfig as Config | undefined),
17
+ ...override,
18
+ } as Config;
@@ -0,0 +1 @@
1
+ #error "invoke bin.js with env file before compiling native project"
@@ -0,0 +1,12 @@
1
+
2
+ #ifdef RCT_NEW_ARCH_ENABLED
3
+ #import "RNUltimateConfigSpec.h"
4
+
5
+ @interface UltimateConfig : NSObject <NativeUltimateConfigSpec>
6
+ #else
7
+ #import <React/RCTBridgeModule.h>
8
+
9
+ @interface UltimateConfig : NSObject <RCTBridgeModule>
10
+ #endif
11
+
12
+ @end
@@ -0,0 +1,27 @@
1
+ #import "UltimateConfig.h"
2
+ #import "ConfigValues.h"
3
+
4
+ @implementation UltimateConfig
5
+ RCT_EXPORT_MODULE()
6
+
7
+ + (BOOL)requiresMainQueueSetup
8
+ {
9
+ // getConstants() reads from a static in-memory struct — no UI or main thread access needed.
10
+ return NO;
11
+ }
12
+
13
+ - (NSDictionary *)constantsToExport
14
+ {
15
+ return getValues();
16
+ }
17
+
18
+ // Don't compile this code when we build for the old architecture.
19
+ #ifdef RCT_NEW_ARCH_ENABLED
20
+ - (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
21
+ (const facebook::react::ObjCTurboModule::InitParams &)params
22
+ {
23
+ return std::make_shared<facebook::react::NativeUltimateConfigSpecJSI>(params);
24
+ }
25
+ #endif
26
+
27
+ @end