@granite-js/screen 1.0.0

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 (99) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/GraniteScreen.podspec +25 -0
  3. package/LICENSE +202 -0
  4. package/android/CMakeLists.txt +62 -0
  5. package/android/build.gradle +63 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/cpp/BundleEvaluator.cpp +27 -0
  8. package/android/src/main/cpp/BundleEvaluator.h +17 -0
  9. package/android/src/main/cpp/onLoad.cpp +6 -0
  10. package/android/src/main/kotlin/run/granite/BundleEvaluator.kt +50 -0
  11. package/android/src/main/kotlin/run/granite/BundleLoader.kt +40 -0
  12. package/android/src/main/kotlin/run/granite/DefaultBundleLoader.kt +20 -0
  13. package/android/src/main/kotlin/run/granite/DefaultErrorView.kt +58 -0
  14. package/android/src/main/kotlin/run/granite/DefaultLoadingView.kt +51 -0
  15. package/android/src/main/kotlin/run/granite/GraniteReactDelegate.kt +76 -0
  16. package/android/src/main/kotlin/run/granite/GraniteReactDelegateImpl.kt +448 -0
  17. package/android/src/main/kotlin/run/granite/GraniteReactHost.kt +113 -0
  18. package/android/src/main/kotlin/run/granite/ReactHostFactory.kt +106 -0
  19. package/gradle-plugin/LICENSE +201 -0
  20. package/gradle-plugin/README.md +578 -0
  21. package/gradle-plugin/build.gradle.kts +97 -0
  22. package/gradle-plugin/gradle/libs.versions.toml +17 -0
  23. package/gradle-plugin/gradle/wrapper/gradle-wrapper.jar +0 -0
  24. package/gradle-plugin/gradle/wrapper/gradle-wrapper.properties +7 -0
  25. package/gradle-plugin/gradle.properties +12 -0
  26. package/gradle-plugin/gradlew +248 -0
  27. package/gradle-plugin/gradlew.bat +93 -0
  28. package/gradle-plugin/settings.gradle.kts +1 -0
  29. package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteExtension.kt +225 -0
  30. package/gradle-plugin/src/main/kotlin/run/granite/gradle/GranitePlugin.kt +784 -0
  31. package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteRootExtension.kt +107 -0
  32. package/gradle-plugin/src/main/kotlin/run/granite/gradle/GraniteRootProjectPlugin.kt +290 -0
  33. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/BuildConfigConfigurator.kt +69 -0
  34. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DependencyConfigurator.kt +232 -0
  35. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DependencyCoordinates.kt +29 -0
  36. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/DevServerResourceConfigurator.kt +101 -0
  37. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/JniPackagingConfigurator.kt +160 -0
  38. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/NdkConfigurator.kt +135 -0
  39. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/RepositoryConfigurator.kt +148 -0
  40. package/gradle-plugin/src/main/kotlin/run/granite/gradle/config/ResourceConfigurator.kt +56 -0
  41. package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/CMakeGenerator.kt +105 -0
  42. package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/CppAutolinkingGenerator.kt +152 -0
  43. package/gradle-plugin/src/main/kotlin/run/granite/gradle/generators/EntryPointGenerator.kt +100 -0
  44. package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/AndroidDependencyConfig.kt +23 -0
  45. package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/AutolinkingConfig.kt +89 -0
  46. package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/CMakeEntry.kt +47 -0
  47. package/gradle-plugin/src/main/kotlin/run/granite/gradle/models/NativeModule.kt +177 -0
  48. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/AssetPackagingTask.kt +194 -0
  49. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/AutolinkingTask.kt +431 -0
  50. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/BundleTask.kt +275 -0
  51. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/CodegenArtifactsTask.kt +218 -0
  52. package/gradle-plugin/src/main/kotlin/run/granite/gradle/tasks/CodegenSchemaTask.kt +186 -0
  53. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/AutolinkingParser.kt +128 -0
  54. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/ConflictDetector.kt +121 -0
  55. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/JdkValidator.kt +73 -0
  56. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/NodeExecutableFinder.kt +43 -0
  57. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/ReactNativeVersionReader.kt +329 -0
  58. package/gradle-plugin/src/main/kotlin/run/granite/gradle/utils/TaskDependencyValidator.kt +198 -0
  59. package/gradle-plugin/src/test/kotlin/run/granite/gradle/GraniteExtensionTest.kt +191 -0
  60. package/gradle-plugin/src/test/kotlin/run/granite/gradle/GranitePluginTest.kt +156 -0
  61. package/gradle-plugin/src/test/kotlin/run/granite/gradle/GraniteRootProjectPluginTest.kt +87 -0
  62. package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/BuildConfigConfiguratorTest.kt +115 -0
  63. package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/DependencyConfiguratorTest.kt +338 -0
  64. package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/DevServerResourceConfiguratorTest.kt +205 -0
  65. package/gradle-plugin/src/test/kotlin/run/granite/gradle/config/ResourceConfiguratorTest.kt +131 -0
  66. package/gradle-plugin/src/test/kotlin/run/granite/gradle/fixtures/NativeModuleFixtures.kt +67 -0
  67. package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/CMakeGeneratorTest.kt +71 -0
  68. package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/CppAutolinkingGeneratorTest.kt +344 -0
  69. package/gradle-plugin/src/test/kotlin/run/granite/gradle/generators/EntryPointGeneratorTest.kt +40 -0
  70. package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/AutolinkingConfigTest.kt +350 -0
  71. package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/CMakeEntryTest.kt +200 -0
  72. package/gradle-plugin/src/test/kotlin/run/granite/gradle/models/NativeModuleTest.kt +562 -0
  73. package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/AssetPackagingTaskTest.kt +318 -0
  74. package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/AutolinkingTaskTest.kt +89 -0
  75. package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/BundleTaskTest.kt +68 -0
  76. package/gradle-plugin/src/test/kotlin/run/granite/gradle/tasks/CodegenTasksTest.kt +410 -0
  77. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/AutolinkingParserTest.kt +335 -0
  78. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/ConflictDetectorTest.kt +75 -0
  79. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/JdkValidatorTest.kt +88 -0
  80. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/ReactNativeVersionReaderTest.kt +585 -0
  81. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/TaskDependencyValidatorTest.kt +123 -0
  82. package/gradle-plugin/src/test/kotlin/run/granite/gradle/utils/TaskTestUtils.kt +88 -0
  83. package/gradle-plugin/src/test/resources/fixtures/sample-rn-config.json +45 -0
  84. package/ios/BundleLoader/BundleEvaluator.h +16 -0
  85. package/ios/BundleLoader/BundleEvaluator.mm +76 -0
  86. package/ios/BundleLoader/BundleLoadable.swift +91 -0
  87. package/ios/GraniteBundleLoaderTypes.swift +7 -0
  88. package/ios/GraniteScreen.h +12 -0
  89. package/ios/ReactNativeHosting/DefaultViews.swift +138 -0
  90. package/ios/ReactNativeHosting/GraniteDefaultModuleProvider.h +24 -0
  91. package/ios/ReactNativeHosting/GraniteDefaultModuleProvider.mm +22 -0
  92. package/ios/ReactNativeHosting/GraniteHostingHelper.swift +103 -0
  93. package/ios/ReactNativeHosting/GraniteNativeFactory.swift +35 -0
  94. package/ios/ReactNativeHosting/GraniteNativeFactoryDelegateImpl.swift +30 -0
  95. package/ios/ReactNativeHosting/GraniteNativeFactoryImpl.swift +24 -0
  96. package/ios/ReactNativeHosting/GraniteReactHost.swift +39 -0
  97. package/ios/ReactNativeHosting/GraniteScreen-Bridging-Header.h +12 -0
  98. package/package.json +59 -0
  99. package/react-native.config.js +8 -0
@@ -0,0 +1,218 @@
1
+ package run.granite.gradle.tasks
2
+
3
+ import org.gradle.api.DefaultTask
4
+ import org.gradle.api.file.DirectoryProperty
5
+ import org.gradle.api.file.RegularFileProperty
6
+ import org.gradle.api.provider.Property
7
+ import org.gradle.api.tasks.*
8
+ import org.gradle.process.ExecOperations
9
+ import run.granite.gradle.utils.NodeExecutableFinder
10
+ import java.io.ByteArrayOutputStream
11
+ import java.io.File
12
+ import javax.inject.Inject
13
+
14
+ /**
15
+ * Gradle task for React Native Codegen artifact generation.
16
+ *
17
+ * This task:
18
+ * 1. Reads the schema.json generated by CodegenSchemaTask
19
+ * 2. Executes React Native codegen to generate native code
20
+ * 3. Outputs Java/Kotlin and C++ source files for TurboModules and Fabric components
21
+ *
22
+ * Generated artifacts include:
23
+ * - Java/Kotlin interfaces for TurboModules
24
+ * - JNI C++ bindings
25
+ * - Fabric component descriptors
26
+ *
27
+ * Inputs:
28
+ * - schema.json file
29
+ * - React Native directory (for codegen binary)
30
+ * - Package name for generated Java code
31
+ *
32
+ * Outputs:
33
+ * - Java sources in build/generated/codegen/java/
34
+ * - C++ sources in build/generated/codegen/jni/
35
+ */
36
+ @CacheableTask
37
+ abstract class CodegenArtifactsTask @Inject constructor(
38
+ private val execOperations: ExecOperations,
39
+ ) : DefaultTask() {
40
+
41
+ /**
42
+ * Schema file generated by CodegenSchemaTask.
43
+ */
44
+ @get:InputFile
45
+ @get:PathSensitive(PathSensitivity.RELATIVE)
46
+ abstract val schemaFile: RegularFileProperty
47
+
48
+ /**
49
+ * React Native directory containing codegen tools.
50
+ * Marked as Internal to avoid task output overlaps with other modules.
51
+ */
52
+ @get:Internal
53
+ abstract val reactNativeDir: DirectoryProperty
54
+
55
+ /**
56
+ * Node modules directory.
57
+ * Marked as Internal to avoid task output overlaps with other modules.
58
+ */
59
+ @get:Internal
60
+ abstract val nodeModulesDir: DirectoryProperty
61
+
62
+ /**
63
+ * Package name for generated Java code.
64
+ */
65
+ @get:Input
66
+ abstract val packageName: Property<String>
67
+
68
+ /**
69
+ * Library name for generated code.
70
+ */
71
+ @get:Input
72
+ abstract val libraryName: Property<String>
73
+
74
+ /**
75
+ * Output directory for generated Java sources.
76
+ */
77
+ @get:OutputDirectory
78
+ abstract val javaOutputDir: DirectoryProperty
79
+
80
+ /**
81
+ * Output directory for generated C++ JNI sources.
82
+ */
83
+ @get:OutputDirectory
84
+ abstract val jniOutputDir: DirectoryProperty
85
+
86
+ init {
87
+ group = "granite"
88
+ description = "Generates React Native Codegen artifacts (Java and C++)"
89
+ }
90
+
91
+ @TaskAction
92
+ fun execute() {
93
+ logger.lifecycle("Generating React Native Codegen artifacts...")
94
+
95
+ val schemaFileResolved = schemaFile.get().asFile
96
+
97
+ if (!schemaFileResolved.exists()) {
98
+ error(
99
+ """
100
+ |Schema file not found: ${schemaFileResolved.absolutePath}
101
+ |
102
+ |The schema should be generated by CodegenSchemaTask.
103
+ |Ensure CodegenSchemaTask runs before CodegenArtifactsTask.
104
+ """.trimMargin(),
105
+ )
106
+ }
107
+
108
+ // Execute codegen binary
109
+ executeCodegen()
110
+
111
+ logger.lifecycle("Codegen artifacts generated successfully")
112
+ }
113
+
114
+ /**
115
+ * Executes the React Native codegen binary.
116
+ *
117
+ * Implementation details in corresponding task.
118
+ */
119
+ private fun executeCodegen() {
120
+ val codegenBinary = findCodegenBinary()
121
+ val nodeExecutable = NodeExecutableFinder.findNodeExecutable()
122
+
123
+ if (!codegenBinary.exists()) {
124
+ logger.warn(
125
+ """
126
+ |React Native codegen binary not found: ${codegenBinary.absolutePath}
127
+ |
128
+ |Skipping codegen generation. This is expected if your library has no TurboModules or Fabric components.
129
+ """.trimMargin(),
130
+ )
131
+ // Create empty output directories
132
+ javaOutputDir.get().asFile.mkdirs()
133
+ jniOutputDir.get().asFile.mkdirs()
134
+ return
135
+ }
136
+
137
+ // Prepare output directories
138
+ val javaOutputDirFile = javaOutputDir.get().asFile
139
+ val jniOutputDirFile = jniOutputDir.get().asFile
140
+
141
+ javaOutputDirFile.mkdirs()
142
+ jniOutputDirFile.mkdirs()
143
+
144
+ // Execute codegen
145
+ // Command: node path/to/codegen/lib/cli/combine/combine-js-to-schema-cli.js schema-file output-dir
146
+ val command = listOf(
147
+ nodeExecutable.absolutePath,
148
+ codegenBinary.absolutePath,
149
+ "--schema", schemaFile.get().asFile.absolutePath,
150
+ "--javaPackageName", packageName.get(),
151
+ "--libraryName", libraryName.get(),
152
+ "--outputDir", javaOutputDirFile.absolutePath,
153
+ "--jniOutputDir", jniOutputDirFile.absolutePath,
154
+ )
155
+
156
+ logger.debug("Executing codegen: ${command.joinToString(" ")}")
157
+
158
+ val stdout = ByteArrayOutputStream()
159
+ val stderr = ByteArrayOutputStream()
160
+
161
+ val result = execOperations.exec {
162
+ commandLine = command
163
+ standardOutput = stdout
164
+ errorOutput = stderr
165
+ isIgnoreExitValue = true
166
+ }
167
+
168
+ if (result.exitValue != 0) {
169
+ logger.warn(
170
+ """
171
+ |Codegen execution completed with warnings (exit code: ${result.exitValue})
172
+ |
173
+ |This is expected if your library has no TurboModules or Fabric components.
174
+ |
175
+ |Error output:
176
+ |$stderr
177
+ """.trimMargin(),
178
+ )
179
+ } else {
180
+ logger.debug("Codegen output: $stdout")
181
+ }
182
+
183
+ // Verify output was generated
184
+ if (javaOutputDirFile.listFiles()?.isEmpty() != false &&
185
+ jniOutputDirFile.listFiles()?.isEmpty() != false
186
+ ) {
187
+ logger.lifecycle("No codegen artifacts generated (no TurboModules/Fabric components found)")
188
+ } else {
189
+ logger.lifecycle("Generated Java sources: ${javaOutputDirFile.absolutePath}")
190
+ logger.lifecycle("Generated C++ sources: ${jniOutputDirFile.absolutePath}")
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Finds the React Native codegen binary.
196
+ */
197
+ private fun findCodegenBinary(): File {
198
+ // Try multiple possible locations
199
+ val possiblePaths = listOf(
200
+ "react-native-codegen/lib/cli/combine/combine-js-to-schema-cli.js",
201
+ "@react-native/codegen/lib/cli/combine/combine-js-to-schema-cli.js",
202
+ "react-native/node_modules/@react-native/codegen/lib/cli/combine/combine-js-to-schema-cli.js",
203
+ )
204
+
205
+ val nodeModules = nodeModulesDir.get().asFile
206
+
207
+ for (path in possiblePaths) {
208
+ val codegenFile = nodeModules.resolve(path)
209
+ if (codegenFile.exists()) {
210
+ logger.debug("Found codegen binary: ${codegenFile.absolutePath}")
211
+ return codegenFile
212
+ }
213
+ }
214
+
215
+ // Return a placeholder file (will be checked for existence later)
216
+ return nodeModules.resolve(possiblePaths.first())
217
+ }
218
+ }
@@ -0,0 +1,186 @@
1
+ package run.granite.gradle.tasks
2
+
3
+ import com.google.gson.Gson
4
+ import com.google.gson.JsonArray
5
+ import com.google.gson.JsonObject
6
+ import org.gradle.api.DefaultTask
7
+ import org.gradle.api.file.DirectoryProperty
8
+ import org.gradle.api.file.RegularFileProperty
9
+ import org.gradle.api.provider.ListProperty
10
+ import org.gradle.api.tasks.*
11
+ import java.io.File
12
+
13
+ /**
14
+ * Gradle task for React Native Codegen schema generation.
15
+ *
16
+ * This task:
17
+ * 1. Scans JavaScript source files for TurboModule and Fabric component specifications
18
+ * 2. Extracts TypeScript/Flow type definitions
19
+ * 3. Generates a unified JSON schema file for codegen
20
+ *
21
+ * The schema is used by CodegenArtifactsTask to generate native code (Java/C++).
22
+ *
23
+ * Inputs:
24
+ * - JavaScript source directories
25
+ * - React Native directory (for codegen tools)
26
+ *
27
+ * Outputs:
28
+ * - schema.json file in build/generated/codegen/schema/
29
+ */
30
+ @CacheableTask
31
+ abstract class CodegenSchemaTask : DefaultTask() {
32
+
33
+ /**
34
+ * JavaScript source directories to scan for specs.
35
+ */
36
+ @get:InputFiles
37
+ @get:PathSensitive(PathSensitivity.RELATIVE)
38
+ abstract val jsSourceDirs: ListProperty<File>
39
+
40
+ /**
41
+ * React Native directory containing codegen tools.
42
+ * Marked as Internal to avoid task output overlaps with other modules.
43
+ */
44
+ @get:Internal
45
+ abstract val reactNativeDir: DirectoryProperty
46
+
47
+ /**
48
+ * Node modules directory.
49
+ * Marked as Internal to avoid task output overlaps with other modules.
50
+ */
51
+ @get:Internal
52
+ abstract val nodeModulesDir: DirectoryProperty
53
+
54
+ /**
55
+ * Output directory for schema file.
56
+ */
57
+ @get:OutputDirectory
58
+ abstract val outputDir: DirectoryProperty
59
+
60
+ /**
61
+ * Generated schema.json file.
62
+ */
63
+ @get:OutputFile
64
+ abstract val schemaFile: RegularFileProperty
65
+
66
+ init {
67
+ group = "granite"
68
+ description = "Scans JavaScript sources and generates React Native Codegen schema"
69
+ }
70
+
71
+ @TaskAction
72
+ fun execute() {
73
+ logger.lifecycle("Generating React Native Codegen schema...")
74
+
75
+ // Scan JavaScript files for specs
76
+ val specFiles = scanForSpecFiles()
77
+
78
+ if (specFiles.isEmpty()) {
79
+ logger.warn("No React Native spec files found in source directories")
80
+ // Generate empty schema
81
+ generateEmptySchema()
82
+ return
83
+ }
84
+
85
+ logger.lifecycle("Found ${specFiles.size} spec files")
86
+
87
+ // Generate schema from specs
88
+ generateSchema(specFiles)
89
+
90
+ logger.lifecycle("Codegen schema generated successfully")
91
+ }
92
+
93
+ /**
94
+ * Scans JavaScript source directories for spec files.
95
+ *
96
+ * Spec files are identified by patterns:
97
+ * - Native*.js, Native*.ts, Native*.jsx, Native*.tsx (TurboModules)
98
+ * - *NativeComponent.js, *NativeComponent.ts, etc. (Fabric components)
99
+ *
100
+ * Implementation details in corresponding task.
101
+ */
102
+ private fun scanForSpecFiles(): List<File> {
103
+ val specFiles = mutableListOf<File>()
104
+
105
+ for (sourceDir in jsSourceDirs.get()) {
106
+ if (!sourceDir.exists() || !sourceDir.isDirectory) {
107
+ logger.warn("JavaScript source directory not found: ${sourceDir.absolutePath}")
108
+ continue
109
+ }
110
+
111
+ sourceDir.walk()
112
+ .filter { it.isFile }
113
+ .filter { file ->
114
+ val name = file.name
115
+ // TurboModule specs
116
+ (
117
+ name.startsWith("Native") && (
118
+ name.endsWith(".js") || name.endsWith(".ts") ||
119
+ name.endsWith(".jsx") || name.endsWith(".tsx")
120
+ )
121
+ ) ||
122
+ // Fabric component specs
123
+ (
124
+ name.contains("NativeComponent") && (
125
+ name.endsWith(".js") || name.endsWith(".ts") ||
126
+ name.endsWith(".jsx") || name.endsWith(".tsx")
127
+ )
128
+ )
129
+ }
130
+ .forEach { specFiles.add(it) }
131
+ }
132
+
133
+ return specFiles
134
+ }
135
+
136
+ /**
137
+ * Generates JSON schema from spec files.
138
+ *
139
+ * Uses React Native's codegen tools to parse TypeScript/Flow specs.
140
+ */
141
+ private fun generateSchema(specFiles: List<File>) {
142
+ val outputFile = schemaFile.get().asFile
143
+ outputFile.parentFile.mkdirs()
144
+
145
+ // For a basic implementation, we'll generate a schema structure
146
+ // In production, this should invoke React Native's codegen parser
147
+ val schema = JsonObject()
148
+ schema.addProperty("version", "0.81.0")
149
+
150
+ val modules = JsonObject()
151
+
152
+ for (specFile in specFiles) {
153
+ val moduleName = specFile.nameWithoutExtension
154
+ val moduleSpec = JsonObject()
155
+ moduleSpec.addProperty("type", "NativeModule")
156
+ moduleSpec.addProperty("spec", specFile.absolutePath)
157
+
158
+ modules.add(moduleName, moduleSpec)
159
+
160
+ logger.debug("Added module to schema: $moduleName")
161
+ }
162
+
163
+ schema.add("modules", modules)
164
+
165
+ // Write schema to file
166
+ outputFile.writeText(Gson().toJson(schema))
167
+
168
+ logger.lifecycle("Schema written to: ${outputFile.absolutePath}")
169
+ }
170
+
171
+ /**
172
+ * Generates an empty schema when no spec files are found.
173
+ */
174
+ private fun generateEmptySchema() {
175
+ val outputFile = schemaFile.get().asFile
176
+ outputFile.parentFile.mkdirs()
177
+
178
+ val emptySchema = JsonObject()
179
+ emptySchema.addProperty("version", "0.81.0")
180
+ emptySchema.add("modules", JsonObject())
181
+
182
+ outputFile.writeText(Gson().toJson(emptySchema))
183
+
184
+ logger.debug("Empty schema written to: ${outputFile.absolutePath}")
185
+ }
186
+ }
@@ -0,0 +1,128 @@
1
+ package run.granite.gradle.utils
2
+
3
+ import com.google.gson.Gson
4
+ import com.google.gson.JsonSyntaxException
5
+ import run.granite.gradle.models.*
6
+
7
+ /**
8
+ * Parses react-native config JSON output into typed AutolinkingConfig models.
9
+ *
10
+ * Implements fail-fast error handling:
11
+ * - Throws IllegalArgumentException for malformed JSON with descriptive messages
12
+ * - Follows error format: "[Context] Failed: [Reason]. [Remediation]"
13
+ */
14
+ object AutolinkingParser {
15
+ private val gson = Gson()
16
+
17
+ /**
18
+ * Parses react-native config JSON string into AutolinkingConfig.
19
+ *
20
+ * @param json JSON string from react-native config command output
21
+ * @return Parsed AutolinkingConfig with validated models
22
+ * @throws IllegalArgumentException if JSON is malformed or missing required fields
23
+ */
24
+ fun parse(json: String): AutolinkingConfig {
25
+ try {
26
+ // Parse JSON into intermediate map structure
27
+ @Suppress("UNCHECKED_CAST")
28
+ val root = gson.fromJson(json, Map::class.java) as Map<String, Any>
29
+
30
+ // Extract project info
31
+ val projectMap = root["project"] as? Map<String, Any>
32
+ ?: throw IllegalArgumentException(
33
+ "react-native config: Failed to parse JSON - missing 'project' field. " +
34
+ "Ensure react-native CLI is properly configured.",
35
+ )
36
+
37
+ val project = parseProjectInfo(projectMap)
38
+
39
+ // Extract dependencies
40
+ @Suppress("UNCHECKED_CAST")
41
+ val dependenciesMap = root["dependencies"] as? Map<String, Any> ?: emptyMap()
42
+ val dependencies = parseDependencies(dependenciesMap as Map<String, Map<String, Any>>)
43
+
44
+ return AutolinkingConfig(
45
+ project = project,
46
+ dependencies = dependencies,
47
+ )
48
+ } catch (e: JsonSyntaxException) {
49
+ throw IllegalArgumentException(
50
+ "react-native config: Failed to parse JSON - syntax error. " +
51
+ "Verify command output is valid JSON. Error: ${e.message}",
52
+ e,
53
+ )
54
+ } catch (e: ClassCastException) {
55
+ throw IllegalArgumentException(
56
+ "react-native config: Failed to parse JSON - unexpected structure. " +
57
+ "Ensure react-native CLI version is compatible. Error: ${e.message}",
58
+ e,
59
+ )
60
+ }
61
+ }
62
+
63
+ private fun parseProjectInfo(map: Map<String, Any>): ProjectInfo {
64
+ @Suppress("UNCHECKED_CAST")
65
+ val androidMap = map["android"] as? Map<String, Any>
66
+
67
+ return ProjectInfo(
68
+ name = map["name"] as? String,
69
+ version = map["version"] as? String,
70
+ ios = map["ios"] as? Map<String, Any>,
71
+ android = androidMap?.let { parseAndroidProjectConfig(it) },
72
+ )
73
+ }
74
+
75
+ private fun parseAndroidProjectConfig(map: Map<String, Any>): AndroidProjectConfig = AndroidProjectConfig(
76
+ sourceDir = map["sourceDir"] as? String,
77
+ manifestPath = map["manifestPath"] as? String,
78
+ packageName = map["packageName"] as? String,
79
+ )
80
+
81
+ private fun parseDependencies(map: Map<String, Map<String, Any>>): Map<String, DependencyConfig> = map.mapValues { (name, depMap) ->
82
+ parseDependencyConfig(name, depMap)
83
+ }
84
+
85
+ private fun parseDependencyConfig(name: String, map: Map<String, Any>): DependencyConfig {
86
+ @Suppress("UNCHECKED_CAST")
87
+ val platformsMap = map["platforms"] as? Map<String, Any>
88
+
89
+ return DependencyConfig(
90
+ name = name,
91
+ root = map["root"] as? String ?: "",
92
+ platforms = platformsMap?.let { parsePlatformConfig(it) },
93
+ )
94
+ }
95
+
96
+ private fun parsePlatformConfig(map: Map<String, Any>): PlatformConfig {
97
+ @Suppress("UNCHECKED_CAST")
98
+ val androidMap = map["android"] as? Map<String, Any>
99
+
100
+ return PlatformConfig(
101
+ ios = map["ios"] as? Map<String, Any>,
102
+ android = androidMap?.let { parseAndroidDependencyConfig(it) },
103
+ )
104
+ }
105
+
106
+ private fun parseAndroidDependencyConfig(map: Map<String, Any>): AndroidDependencyConfig {
107
+ @Suppress("UNCHECKED_CAST")
108
+ val componentDescriptorsList = map["componentDescriptors"] as? List<String>
109
+
110
+ @Suppress("UNCHECKED_CAST")
111
+ val buildTypesList = map["buildTypes"] as? List<String>
112
+
113
+ return AndroidDependencyConfig(
114
+ sourceDir = map["sourceDir"] as? String,
115
+ packageImportPath = map["packageImportPath"] as? String,
116
+ packageInstance = map["packageInstance"] as? String,
117
+ dependencyConfiguration = map["dependencyConfiguration"] as? String,
118
+ buildTypes = buildTypesList,
119
+ libraryName = map["libraryName"] as? String,
120
+ componentDescriptors = componentDescriptorsList,
121
+ cmakeListsPath = map["cmakeListsPath"] as? String,
122
+ cxxModuleCMakeListsPath = map["cxxModuleCMakeListsPath"] as? String,
123
+ cxxModuleCMakeListsModuleName = map["cxxModuleCMakeListsModuleName"] as? String,
124
+ cxxModuleHeaderName = map["cxxModuleHeaderName"] as? String,
125
+ isPureCxxDependency = map["isPureCxxDependency"] as? Boolean,
126
+ )
127
+ }
128
+ }
@@ -0,0 +1,121 @@
1
+ package run.granite.gradle.utils
2
+
3
+ import org.gradle.api.Project
4
+
5
+ /**
6
+ * Detects conflicts with the standard React Native Gradle Plugin.
7
+ *
8
+ * The Granite plugin cannot coexist with the unpatched React Native Gradle Plugin
9
+ * because both configure React Native build processes (codegen, autolinking, bundling).
10
+ * Only one library module per dependency tree should use the Granite plugin.
11
+ */
12
+ object ConflictDetector {
13
+
14
+ private const val REACT_NATIVE_PLUGIN_ID = "com.facebook.react"
15
+ private const val REACT_NATIVE_APP_PLUGIN_ID = "com.facebook.react.application"
16
+ private const val REACT_NATIVE_LIBRARY_PLUGIN_ID = "com.facebook.react.library"
17
+
18
+ /**
19
+ * Validates that the React Native Gradle Plugin is not applied to the same project.
20
+ *
21
+ * @param project The Gradle project
22
+ * @throws IllegalStateException if conflicting plugin is detected
23
+ */
24
+ fun validateNoConflicts(project: Project) {
25
+ // Check if React Native plugin is applied
26
+ val hasReactPlugin = project.pluginManager.hasPlugin(REACT_NATIVE_PLUGIN_ID)
27
+ val hasReactAppPlugin = project.pluginManager.hasPlugin(REACT_NATIVE_APP_PLUGIN_ID)
28
+ val hasReactLibraryPlugin = project.pluginManager.hasPlugin(REACT_NATIVE_LIBRARY_PLUGIN_ID)
29
+
30
+ if (hasReactPlugin || hasReactAppPlugin || hasReactLibraryPlugin) {
31
+ val detectedPlugin = when {
32
+ hasReactPlugin -> REACT_NATIVE_PLUGIN_ID
33
+ hasReactAppPlugin -> REACT_NATIVE_APP_PLUGIN_ID
34
+ else -> REACT_NATIVE_LIBRARY_PLUGIN_ID
35
+ }
36
+
37
+ error(
38
+ """
39
+ |Granite plugin cannot coexist with the React Native Gradle Plugin.
40
+ |
41
+ |Detected conflicting plugin: $detectedPlugin
42
+ |Project: ${project.path}
43
+ |
44
+ |The Granite plugin provides its own implementation of React Native build tasks
45
+ |(codegen, autolinking, bundling) and cannot work alongside the standard plugin.
46
+ |
47
+ |Solutions:
48
+ | 1. Remove the React Native plugin from your library module:
49
+ | Remove: id("$detectedPlugin")
50
+ | Keep: id("run.granite.library")
51
+ |
52
+ | 2. If you need both plugins in your project:
53
+ | - Use Granite plugin ONLY in the library module that packages React Native
54
+ | - Application modules should NOT use either plugin
55
+ | - Only ONE library module per dependency tree can use Granite plugin
56
+ |
57
+ |Example (library module build.gradle.kts):
58
+ | plugins {
59
+ | id("com.android.library")
60
+ | id("run.granite.library") // Use Granite, not React Native plugin
61
+ | }
62
+ |
63
+ |Example (app module build.gradle.kts):
64
+ | dependencies {
65
+ | implementation(project(":your-library-module"))
66
+ | // No React Native plugin needed - library provides everything
67
+ | }
68
+ """.trimMargin(),
69
+ )
70
+ }
71
+
72
+ // Warn if multiple library modules might be using Granite plugin
73
+ // (This is detected by checking if dependencies include other Granite-enabled modules)
74
+ detectMultipleGraniteModules(project)
75
+
76
+ project.logger.lifecycle("Granite plugin: Conflict detection passed")
77
+ }
78
+
79
+ /**
80
+ * Detects if multiple library modules in the dependency tree are using Granite plugin.
81
+ *
82
+ * This is a warning, not an error, because it may be intentional in some cases.
83
+ */
84
+ private fun detectMultipleGraniteModules(project: Project) {
85
+ // Check if any dependencies also apply the Granite plugin
86
+ val graniteModuleCount = project.rootProject.subprojects.count { subproject ->
87
+ subproject.pluginManager.hasPlugin("run.granite.library")
88
+ }
89
+
90
+ if (graniteModuleCount > 1) {
91
+ project.logger.warn(
92
+ """
93
+ |
94
+ |⚠️ WARNING: Multiple modules in this project are using the Granite plugin.
95
+ |
96
+ |Detected $graniteModuleCount modules with 'run.granite.library' plugin applied.
97
+ |
98
+ |Best practice: Only ONE library module per dependency tree should use Granite plugin.
99
+ |Multiple Granite modules can cause:
100
+ | - Duplicate React Native initialization
101
+ | - Conflicting native module registration
102
+ | - Increased AAR size
103
+ | - Build performance issues
104
+ |
105
+ |If this is intentional, ensure the modules are not in the same dependency tree.
106
+ |
107
+ """.trimMargin(),
108
+ )
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Checks if a project has the React Native plugin applied.
114
+ *
115
+ * @param project The Gradle project to check
116
+ * @return true if React Native plugin is applied, false otherwise
117
+ */
118
+ fun hasReactNativePlugin(project: Project): Boolean = project.pluginManager.hasPlugin(REACT_NATIVE_PLUGIN_ID) ||
119
+ project.pluginManager.hasPlugin(REACT_NATIVE_APP_PLUGIN_ID) ||
120
+ project.pluginManager.hasPlugin(REACT_NATIVE_LIBRARY_PLUGIN_ID)
121
+ }