catalyst-core 0.2.0-beta.3 → 0.3.0-beta.2

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 (167) hide show
  1. package/babel.native.config.cjs +8 -0
  2. package/bin/catalyst.js +49 -105
  3. package/changelog.md +8 -0
  4. package/dist/caching.js +1 -1
  5. package/dist/globals.d.ts +7 -0
  6. package/dist/index.jsx +8 -0
  7. package/dist/logger.js +74 -3
  8. package/dist/native/androidProject/app/build.gradle.kts +45 -4
  9. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +86 -0
  10. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +8 -0
  11. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +173 -13
  12. package/dist/native/androidProject/settings.gradle.kts +10 -0
  13. package/dist/native/androidSetup.js +300 -8
  14. package/dist/native/bridge/WebBridge.js +481 -59
  15. package/dist/native/bridge/constants/NativeInterfaces.js +304 -45
  16. package/dist/native/bridge/errors.js +250 -26
  17. package/dist/native/bridge/hooks/index.js +132 -1
  18. package/dist/native/bridge/hooks/useAI.js +94 -0
  19. package/dist/native/bridge/hooks/useCamera.js +347 -9
  20. package/dist/native/bridge/hooks/useDataProtection.js +200 -2
  21. package/dist/native/bridge/hooks/useDeviceInfo.js +71 -2
  22. package/dist/native/bridge/hooks/useFilePicker.js +429 -2
  23. package/dist/native/bridge/hooks/useGoogleSignIn.js +81 -1
  24. package/dist/native/bridge/hooks/useHapticFeedback.js +196 -1
  25. package/dist/native/bridge/hooks/useIntent.js +95 -1
  26. package/dist/native/bridge/hooks/useNativeTransition.js +202 -27
  27. package/dist/native/bridge/hooks/useNetworkStatus.js +84 -3
  28. package/dist/native/bridge/hooks/useNotification.js +318 -2
  29. package/dist/native/bridge/hooks/useSafeArea.js +84 -2
  30. package/dist/native/bridge/hooks/useVideoStream.js +434 -6
  31. package/dist/native/bridge/hooks/utils.js +27 -1
  32. package/dist/native/bridge/hooks.js +16 -1
  33. package/dist/native/bridge/perf/collectors/ApiCollector.js +66 -3
  34. package/dist/native/bridge/perf/collectors/CacheCollector.js +186 -11
  35. package/dist/native/bridge/perf/collectors/HookCollector.js +141 -19
  36. package/dist/native/bridge/perf/collectors/InsightsCollector.js +481 -23
  37. package/dist/native/bridge/perf/collectors/InteractionCollector.js +245 -10
  38. package/dist/native/bridge/perf/collectors/KeyboardCollector.js +107 -8
  39. package/dist/native/bridge/perf/collectors/NavigationCollector.js +273 -32
  40. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +178 -2
  41. package/dist/native/bridge/perf/collectors/RenderCollector.js +258 -14
  42. package/dist/native/bridge/perf/collectors/ScrollCollector.js +95 -9
  43. package/dist/native/bridge/perf/collectors/ThreadCollector.js +90 -6
  44. package/dist/native/bridge/perf/core/constants.js +103 -20
  45. package/dist/native/bridge/perf/core/measure.js +179 -30
  46. package/dist/native/bridge/perf/core/session.js +89 -23
  47. package/dist/native/bridge/perf/core/store.js +554 -1
  48. package/dist/native/bridge/perf/core/summary.js +174 -1
  49. package/dist/native/bridge/perf/core/traceExport.js +240 -1
  50. package/dist/native/bridge/perf/core/waterfalls.js +146 -1
  51. package/dist/native/bridge/perf/index.js +359 -38
  52. package/dist/native/bridge/safeArea.js +53 -5
  53. package/dist/native/bridge/standardizedHooks.js +595 -82
  54. package/dist/native/bridge/useBaseHook.js +274 -38
  55. package/dist/native/bridge/utils/CameraUtils.js +201 -56
  56. package/dist/native/bridge/utils/FileObjectConverter.js +129 -18
  57. package/dist/native/bridge/utils/NativeBridge.js +425 -141
  58. package/dist/native/buildAndroid/assets.js +510 -13
  59. package/dist/native/buildAndroid/build.js +296 -19
  60. package/dist/native/buildAndroid/config.js +87 -1
  61. package/dist/native/buildAndroid/index.js +406 -1
  62. package/dist/native/buildAppAndroid.js +34 -2
  63. package/dist/native/buildAppIos.js +62 -2
  64. package/dist/native/buildIos/assets.js +609 -8
  65. package/dist/native/buildIos/build.js +639 -20
  66. package/dist/native/buildIos/config.js +294 -12
  67. package/dist/native/buildIos/index.js +343 -6
  68. package/dist/native/buildIos/plugins.js +343 -16
  69. package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +43 -0
  70. package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +28 -0
  71. package/dist/native/internal-plugins/device-info-plugin/manifest.json +19 -0
  72. package/dist/native/internalPluginUtils.js +232 -7
  73. package/dist/native/package.json +3 -0
  74. package/dist/native/plugin-bridge/PluginBridge.js +227 -1
  75. package/dist/native/pluginComposerAndroid.js +407 -23
  76. package/dist/native/pluginComposerIos.js +435 -11
  77. package/dist/native/renameAndroidProject.js +1094 -88
  78. package/dist/native/setupEmulatorIos.js +289 -22
  79. package/dist/native/setupServer.js +160 -19
  80. package/dist/native/terminalProgress.js +199 -11
  81. package/dist/native/utils.js +193 -15
  82. package/dist/offline/catalyst-sw.js +145 -1
  83. package/dist/offline/registerServiceWorker.js +33 -1
  84. package/dist/otel.js +375 -46
  85. package/dist/router/ClientRouter.js +14 -2
  86. package/dist/router/ServerRouter.js +9 -1
  87. package/dist/router.js +1 -1
  88. package/dist/scripts/build.js +112 -3
  89. package/dist/scripts/checkVersion.js +3 -1
  90. package/dist/scripts/cleanCache.js +30 -1
  91. package/dist/scripts/generateOfflineManifest.js +116 -1
  92. package/dist/scripts/loadEnvironmentVariables.js +32 -6
  93. package/dist/scripts/plugins.js +309 -1
  94. package/dist/scripts/preServerInit.js +13 -0
  95. package/dist/scripts/scriptUtils.js +54 -3
  96. package/dist/scripts/serve.js +51 -4
  97. package/dist/scripts/start.js +47 -10
  98. package/dist/scripts/validator.js +149 -1
  99. package/dist/sentry.cjs +127 -0
  100. package/dist/sentry.js +125 -5
  101. package/dist/server/expressServer.js +296 -5
  102. package/dist/server/manifestCache.js +36 -0
  103. package/dist/server/renderer/ChunkExtractor.js +144 -0
  104. package/dist/server/renderer/document/Body.jsx +59 -0
  105. package/dist/server/renderer/document/FastRefresh.jsx +20 -0
  106. package/dist/server/renderer/document/Head.jsx +60 -0
  107. package/dist/server/renderer/document/index.jsx +2 -0
  108. package/dist/server/renderer/extract.js +149 -31
  109. package/dist/server/renderer/handler.jsx +450 -0
  110. package/dist/server/renderer/index.js +8 -1
  111. package/dist/server/renderer/render.js +47 -0
  112. package/dist/server/utils/botDetectionMiddleware.js +29 -0
  113. package/dist/server/utils/cjsRequire.js +10 -0
  114. package/dist/server/utils/userAgentUtil.js +87 -9
  115. package/dist/server/utils/validator.js +164 -1
  116. package/dist/vite/FastRefresh.jsx +20 -0
  117. package/dist/vite/inject-cache-key-plugin.js +276 -0
  118. package/dist/vite/loadCustomViteConfig.js +22 -0
  119. package/dist/vite/manifest-categorization-plugin.js +455 -0
  120. package/dist/vite/node-loader.mjs +458 -0
  121. package/dist/vite/scssParams.js +21 -0
  122. package/dist/vite/vite.config.client.js +130 -0
  123. package/dist/vite/vite.config.js +498 -0
  124. package/dist/vite/vite.config.server.js +84 -0
  125. package/dist/web-router/components/MetaTag.jsx +34 -0
  126. package/dist/web-router/components/RouterDataProvider.jsx +332 -0
  127. package/dist/web-router/components/Split.jsx +200 -0
  128. package/dist/web-router/components/SplitInview.jsx +106 -0
  129. package/dist/web-router/components/SsrRequestContext.jsx +14 -0
  130. package/dist/web-router/context.jsx +12 -0
  131. package/dist/web-router/hooks.jsx +72 -0
  132. package/dist/web-router/types/components/MetaTag.d.ts +2 -0
  133. package/dist/web-router/types/components/RouterDataProvider.d.ts +95 -0
  134. package/dist/web-router/types/context.d.ts +14 -0
  135. package/dist/web-router/types/hooks.d.ts +1 -0
  136. package/dist/web-router/types/index.d.ts +3 -0
  137. package/dist/web-router/types/utils/metaDataUtils.d.ts +24 -0
  138. package/dist/web-router/utils/metaDataUtils.jsx +119 -0
  139. package/mcp_v2/conversion-tasks.json +35 -17
  140. package/mcp_v2/knowledge-base.json +176 -17
  141. package/mcp_v2/lib/helpers.js +10 -0
  142. package/mcp_v2/mcp.js +40 -8
  143. package/mcp_v2/package.json +1 -0
  144. package/mcp_v2/tools/build.js +161 -37
  145. package/mcp_v2/tools/conversion.js +106 -5
  146. package/mcp_v2/tools/tasks.js +18 -14
  147. package/package.json +35 -68
  148. package/babel.config.js +0 -32
  149. package/dist/index.js +0 -1
  150. package/dist/scripts/devBuild.js +0 -3
  151. package/dist/scripts/devServe.js +0 -3
  152. package/dist/scripts/loadScriptsBeforeServerStarts.js +0 -4
  153. package/dist/scripts/registerAliases.js +0 -6
  154. package/dist/server/renderer/document/Body.js +0 -11
  155. package/dist/server/renderer/document/Head.js +0 -9
  156. package/dist/server/renderer/document/index.js +0 -1
  157. package/dist/server/renderer/handler.js +0 -47
  158. package/dist/server/startServer.js +0 -57
  159. package/dist/webpack/babel-plugins/remove-client.plugin.js +0 -5
  160. package/dist/webpack/babel-plugins/remove-ssr.plugin.js +0 -5
  161. package/dist/webpack/babel.config.client.js +0 -1
  162. package/dist/webpack/babel.config.ssr.js +0 -1
  163. package/dist/webpack/base.babel.js +0 -7
  164. package/dist/webpack/development.client.babel.js +0 -10
  165. package/dist/webpack/production.client.babel.js +0 -4
  166. package/dist/webpack/production.ssr.babel.js +0 -8
  167. package/dist/webpack/scssParams.js +0 -9
@@ -7,15 +7,21 @@ import io.ktor.http.*
7
7
  import io.ktor.server.application.*
8
8
  import io.ktor.server.engine.*
9
9
  import io.ktor.server.netty.*
10
+ import io.ktor.server.request.*
10
11
  import io.ktor.server.response.*
11
12
  import io.ktor.server.routing.*
13
+ import io.ktor.http.ContentType
14
+ import io.ktor.server.response.respondTextWriter
15
+ import io.ktor.server.sse.*
12
16
  import kotlinx.coroutines.*
17
+ import kotlinx.coroutines.flow.Flow
18
+ import org.json.JSONObject
13
19
  import java.io.File
14
20
  import java.io.IOException
15
21
  import java.net.InetSocketAddress
16
22
  import java.nio.channels.ServerSocketChannel
17
23
  import java.security.SecureRandom
18
- import java.util.*
24
+ import java.util.UUID
19
25
  import java.util.concurrent.ConcurrentHashMap
20
26
 
21
27
  /**
@@ -36,6 +42,7 @@ object FrameworkServerUtils {
36
42
  private const val FRAMEWORK_PORT_RANGE_START = 3000
37
43
  private const val FRAMEWORK_PORT_RANGE_END = 3099
38
44
  private const val SESSION_TIMEOUT_MS = 30 * 60 * 1000L // 30 minutes
45
+ private const val AI_GENERATE_TIMEOUT_MS = 120 * 1000L // 2 minutes
39
46
 
40
47
  // Server state
41
48
  private var server: EmbeddedServer<NettyApplicationEngine, NettyApplicationEngine.Configuration>? = null
@@ -49,7 +56,14 @@ object FrameworkServerUtils {
49
56
  // File management
50
57
  private val servedFiles = ConcurrentHashMap<String, ServedFile>()
51
58
  private var cacheDirectory: File? = null
52
-
59
+
60
+ // Native AI — supplier set by NativeBridge before POST /ai/stream is called
61
+ // Returns (conversationId, tokenFlow) so the route can emit conversationId as an SSE event
62
+ private var nativeAiSupplier: (suspend (prompt: String, genConfig: JSONObject, conversationId: String?) -> Pair<String, Flow<String>>)? = null
63
+
64
+ // System prompt injected into every native AI request (built from attachmentComponents at initAI time)
65
+ private var nativeSystemPrompt: String = ""
66
+
53
67
  // Coroutine scope for server operations
54
68
  private val serverScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
55
69
 
@@ -60,7 +74,7 @@ object FrameworkServerUtils {
60
74
  val fileId: String,
61
75
  val createdAt: Long = System.currentTimeMillis()
62
76
  )
63
-
77
+
64
78
  /**
65
79
  * Initialize and start the framework server
66
80
  * @param context Android context for file operations
@@ -223,7 +237,21 @@ object FrameworkServerUtils {
223
237
  * Get current session ID
224
238
  */
225
239
  fun getSessionId(): String = sessionId
226
-
240
+
241
+ /**
242
+ * Register the native AI supplier. Called by NativeBridge once LiteRT-LM is ready.
243
+ * The supplier receives the prompt, gen-config, and optional incomingConversationId,
244
+ * and returns (activeConversationId, tokenFlow) so stateful sessions can be chained.
245
+ */
246
+ fun setNativeAiSupplier(supplier: suspend (prompt: String, genConfig: JSONObject, conversationId: String?) -> Pair<String, Flow<String>>) {
247
+ nativeAiSupplier = supplier
248
+ }
249
+
250
+ fun setNativeSystemPrompt(systemPrompt: String) {
251
+ nativeSystemPrompt = systemPrompt
252
+ Log.d(TAG, "System prompt set (${systemPrompt.length} chars)")
253
+ }
254
+
227
255
  // Private implementation methods
228
256
 
229
257
  private fun initializeCacheDirectory(context: Context) {
@@ -291,7 +319,145 @@ object FrameworkServerUtils {
291
319
 
292
320
  private fun startKtorServer() {
293
321
  server = embeddedServer(Netty, port = serverPort) {
322
+ install(SSE)
294
323
  routing {
324
+ // Native AI streaming endpoint — POST body: { "prompt": "...", "genConfig": {...} }
325
+ // Writes SSE manually over chunked response (Ktor SSE plugin only supports GET).
326
+ post("/framework-$sessionId/ai/stream") {
327
+ call.response.header(HttpHeaders.AccessControlAllowOrigin, allowedOrigin)
328
+ call.response.header(HttpHeaders.AccessControlAllowHeaders, "*")
329
+ call.response.header(HttpHeaders.ContentType, "text/event-stream")
330
+ call.response.header(HttpHeaders.CacheControl, "no-cache")
331
+ call.response.header("X-Accel-Buffering", "no")
332
+
333
+ val body = try {
334
+ JSONObject(call.receiveText())
335
+ } catch (e: Exception) {
336
+ JSONObject()
337
+ }
338
+ val userPrompt = body.optString("prompt", "")
339
+ val genConfig = body.optJSONObject("genConfig") ?: JSONObject()
340
+ val incomingConvId = body.optString("conversationId", "").takeIf { it.isNotBlank() }
341
+ if (nativeSystemPrompt.isNotBlank()) Log.d(TAG, "Injecting system prompt (${nativeSystemPrompt.length} chars)")
342
+ val prompt = if (nativeSystemPrompt.isNotBlank()) "$nativeSystemPrompt\n\n$userPrompt" else userPrompt
343
+
344
+ call.respondTextWriter(contentType = ContentType.Text.EventStream) {
345
+ val supplier = nativeAiSupplier
346
+ if (supplier == null) {
347
+ write("data: {\"error\":\"Native AI not initialised — call initAI() first\"}\n\n")
348
+ flush()
349
+ } else if (prompt.isBlank()) {
350
+ write("data: {\"error\":\"prompt is empty\"}\n\n")
351
+ flush()
352
+ } else {
353
+ try {
354
+ val (activeConvId, tokenFlow) = supplier(prompt, genConfig, incomingConvId)
355
+ write("data: {\"conversationId\":${JSONObject.quote(activeConvId)}}\n\n")
356
+ flush()
357
+ tokenFlow.collect { token ->
358
+ write("data: {\"token\":${JSONObject.quote(token)}}\n\n")
359
+ flush()
360
+ }
361
+ write("data: {\"done\":true}\n\n")
362
+ flush()
363
+ } catch (e: java.io.IOException) {
364
+ // Client disconnected — channel closed, nothing to write
365
+ Log.d(TAG, "Native AI stream: client disconnected (${e.message})")
366
+ } catch (e: Exception) {
367
+ Log.e(TAG, "Native AI stream error", e)
368
+ try {
369
+ write("data: {\"error\":${JSONObject.quote(e.message ?: "stream error")}}\n\n")
370
+ flush()
371
+ } catch (_: java.io.IOException) {
372
+ // Channel already closed, ignore
373
+ }
374
+ }
375
+ }
376
+ }
377
+ }
378
+
379
+ // OPTIONS preflight for ai/stream
380
+ options("/framework-$sessionId/ai/stream") {
381
+ call.response.header(HttpHeaders.AccessControlAllowOrigin, allowedOrigin)
382
+ call.response.header(HttpHeaders.AccessControlAllowMethods, "POST, OPTIONS")
383
+ call.response.header(HttpHeaders.AccessControlAllowHeaders, "*")
384
+ call.response.header(HttpHeaders.AccessControlMaxAge, "86400")
385
+ call.respond(HttpStatusCode.OK)
386
+ }
387
+
388
+ // Native AI non-streaming endpoint — POST body: { "prompt": "...", "genConfig": {...} }
389
+ // Drains nativeAiSupplier's tokenFlow fully, then returns a single JSON response.
390
+ post("/framework-$sessionId/ai/generate") {
391
+ call.response.header(HttpHeaders.AccessControlAllowOrigin, allowedOrigin)
392
+ call.response.header(HttpHeaders.AccessControlAllowHeaders, "*")
393
+
394
+ val body = try {
395
+ JSONObject(call.receiveText())
396
+ } catch (e: Exception) {
397
+ JSONObject()
398
+ }
399
+ val userPrompt = body.optString("prompt", "")
400
+ val genConfig = body.optJSONObject("genConfig") ?: JSONObject()
401
+ val incomingConvId = body.optString("conversationId", "").takeIf { it.isNotBlank() }
402
+ if (nativeSystemPrompt.isNotBlank()) Log.d(TAG, "Injecting system prompt (${nativeSystemPrompt.length} chars)")
403
+ val prompt = if (nativeSystemPrompt.isNotBlank()) "$nativeSystemPrompt\n\n$userPrompt" else userPrompt
404
+
405
+ val supplier = nativeAiSupplier
406
+ if (supplier == null) {
407
+ call.respondText(
408
+ JSONObject().put("error", "Native AI not initialised — call initAI() first").toString(),
409
+ ContentType.Application.Json,
410
+ HttpStatusCode.ServiceUnavailable
411
+ )
412
+ return@post
413
+ }
414
+ if (prompt.isBlank()) {
415
+ call.respondText(
416
+ JSONObject().put("error", "prompt is empty").toString(),
417
+ ContentType.Application.Json,
418
+ HttpStatusCode.BadRequest
419
+ )
420
+ return@post
421
+ }
422
+
423
+ try {
424
+ val (activeConvId, tokenFlow) = supplier(prompt, genConfig, incomingConvId)
425
+ val output = StringBuilder()
426
+ var tokenCount = 0
427
+ withTimeout(AI_GENERATE_TIMEOUT_MS) {
428
+ tokenFlow.collect { token -> output.append(token); tokenCount++ }
429
+ }
430
+ val responseJson = JSONObject()
431
+ .put("output", output.toString())
432
+ .put("conversationId", activeConvId)
433
+ .put("tokenCount", tokenCount)
434
+ call.respondText(responseJson.toString(), ContentType.Application.Json, HttpStatusCode.OK)
435
+ } catch (e: TimeoutCancellationException) {
436
+ Log.e(TAG, "Native AI generate timed out after ${AI_GENERATE_TIMEOUT_MS}ms", e)
437
+ call.respondText(
438
+ JSONObject().put("error", "generate timed out after ${AI_GENERATE_TIMEOUT_MS / 1000}s").toString(),
439
+ ContentType.Application.Json,
440
+ HttpStatusCode.GatewayTimeout
441
+ )
442
+ } catch (e: Exception) {
443
+ Log.e(TAG, "Native AI generate error", e)
444
+ call.respondText(
445
+ JSONObject().put("error", (e.message ?: "generate error")).toString(),
446
+ ContentType.Application.Json,
447
+ HttpStatusCode.InternalServerError
448
+ )
449
+ }
450
+ }
451
+
452
+ // OPTIONS preflight for ai/generate
453
+ options("/framework-$sessionId/ai/generate") {
454
+ call.response.header(HttpHeaders.AccessControlAllowOrigin, allowedOrigin)
455
+ call.response.header(HttpHeaders.AccessControlAllowMethods, "POST, OPTIONS")
456
+ call.response.header(HttpHeaders.AccessControlAllowHeaders, "*")
457
+ call.response.header(HttpHeaders.AccessControlMaxAge, "86400")
458
+ call.respond(HttpStatusCode.OK)
459
+ }
460
+
295
461
  get("/framework-$sessionId/file-{fileId}") {
296
462
  val fileId = call.parameters["fileId"]
297
463
 
@@ -385,22 +551,16 @@ object FrameworkServerUtils {
385
551
  val expiredFiles = servedFiles.filter { (_, servedFile) ->
386
552
  currentTime - servedFile.createdAt > SESSION_TIMEOUT_MS
387
553
  }
388
-
389
554
  if (expiredFiles.isNotEmpty()) {
390
555
  Log.d(TAG, "Cleaning up ${expiredFiles.size} expired files")
391
-
392
- expiredFiles.forEach { (fileId, _) ->
393
- removeServedFile(fileId)
394
- }
556
+ expiredFiles.forEach { (fileId, _) -> removeServedFile(fileId) }
395
557
  }
558
+
396
559
  }
397
560
 
398
561
  private fun cleanupAllFiles() {
399
562
  Log.d(TAG, "Cleaning up all served files (${servedFiles.size} files)")
400
-
401
- servedFiles.keys.toList().forEach { fileId ->
402
- removeServedFile(fileId)
403
- }
563
+ servedFiles.keys.toList().forEach { fileId -> removeServedFile(fileId) }
404
564
 
405
565
  // Clean cache directory
406
566
  cacheDirectory?.listFiles()?.forEach { file ->
@@ -33,3 +33,13 @@ dependencyResolutionManagement {
33
33
 
34
34
  rootProject.name = "Catalyst Application"
35
35
  include(":app")
36
+
37
+ // @catalyst/cloud-ai — include when the package is installed.
38
+ // The npm package ships an /android library module for native AI; wire it here so Gradle picks it up.
39
+ // After running: npm install @catalyst/cloud-ai && npm run sync-packages -- --packages cloud-ai
40
+ // Also set ai.enabled=true in your app config to enable useLegacyPackaging for GPU dlopen().
41
+ val cloudAiDir = File(rootDir, "../node_modules/@catalyst/cloud-ai/android")
42
+ if (cloudAiDir.exists()) {
43
+ include(":catalyst-cloud-ai")
44
+ project(":catalyst-cloud-ai").projectDir = cloudAiDir
45
+ }
@@ -1,9 +1,301 @@
1
- "use strict";var _fs=_interopRequireDefault(require("fs"));var _path=_interopRequireDefault(require("path"));var _child_process=require("child_process");var _utils=require("./utils.js");var _TerminalProgress=_interopRequireDefault(require("./TerminalProgress.js"));var _setupServer=require("./setupServer.js");function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const catalystCorePath=_path.default.dirname(require.resolve("catalyst-core/package.json"));const pwd=_path.default.join(catalystCorePath,"dist/native");const configPath=`${process.env.PWD}/config/config.json`;const steps={java:"Check Java Environment",config:"Initialize Configuration",androidTools:"Validate Android Tools",emulator:"Configure Android Emulator",properties:"Update Local Properties",saveConfig:"Save Configuration",setupServer:"Setup Server"};const progressPaddingConfig={titlePaddingTop:2,titlePaddingBottom:1,stepPaddingLeft:4,stepSpacing:1,errorPaddingLeft:6,bottomMargin:2};const progress=new _TerminalProgress.default(steps,"Catalyst Universal Android Setup",progressPaddingConfig);async function checkJavaInstallation(){try{(0,_utils.runCommand)("java -version");(0,_utils.runCommand)("javac -version");progress.log("Java is installed and configured","success");return true;}catch(error){progress.log("Java installation not found or not properly configured","error");return false;}}async function installJava(){progress.log("Installing Java (Zulu JDK 17)...");try{try{(0,_utils.runCommand)("brew --version");}catch(error){throw new Error("Homebrew is required but not installed. Please install Homebrew first.");}const javaHome="/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home";// Try installing Java
2
- try{await(0,_utils.runCommand)("brew install --cask zulu@17");}catch(error){if(error.message.includes("already installed")){progress.log("Attempting to reinstall Java...","info");try{await(0,_utils.runCommand)("brew reinstall --cask zulu@17");}catch(reinstallError){throw new Error(`Failed to reinstall Java: ${reinstallError.message}. Please run 'brew reinstall --cask zulu@17' manually.`);}}else{throw error;}}// Verify Java installation
3
- if(!_fs.default.existsSync(javaHome)){throw new Error(`Java installation not found at ${javaHome}. Please verify the installation manually.`);}// Set up environment variables
4
- const homeDir=process.env.HOME;const shellProfile=`${homeDir}/.zshrc`;const exportCommand=`\n# Java Configuration\nexport JAVA_HOME=${javaHome}\nexport PATH=$JAVA_HOME/bin:$PATH\n`;try{_fs.default.appendFileSync(shellProfile,exportCommand);}catch(error){throw new Error(`Failed to update ${shellProfile}: ${error.message}. Please add the following lines manually:\n${exportCommand}`);}// Set for current process
5
- process.env.JAVA_HOME=javaHome;process.env.PATH=`${javaHome}/bin:${process.env.PATH}`;// Verify Java configuration
6
- try{const javaVersion=(0,_utils.runCommand)("java -version");progress.log("Java installed and configured successfully","success");progress.log(`Java Version: ${javaVersion}`,"info");progress.log(`IMPORTANT: Please run the following commands to complete setup:`,"warning");progress.log(`1. source ${shellProfile}`,"info");progress.log(`2. echo $JAVA_HOME`,"info");return true;}catch(error){throw new Error(`Java installation verified but environment not configured. Please run:\n1. source ${shellProfile}\n2. echo $JAVA_HOME`);}}catch(error){progress.log(error.message,"error");progress.log("Manual Setup Instructions:","info");progress.log("1. Run: brew reinstall --cask zulu@17","info");progress.log("2. Add to ~/.zshrc:","info");progress.log(" export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home","info");progress.log(" export PATH=$JAVA_HOME/bin:$PATH","info");progress.log("3. Run: source ~/.zshrc","info");throw error;}}async function validateJavaEnvironment(){progress.start("java");const hasJava=await checkJavaInstallation();if(!hasJava){progress.log("Java installation required. Starting installation process...");const javaInstalled=await installJava();if(!javaInstalled){progress.fail("java","Failed to set up Java environment");throw new Error("Failed to set up Java environment");}}progress.complete("java");}async function initializeConfig(){const configFile=_fs.default.readFileSync(configPath,"utf8");const config=JSON.parse(configFile);const{WEBVIEW_CONFIG}=config;if(!WEBVIEW_CONFIG||Object.keys(WEBVIEW_CONFIG).length===0){progress.log("WebView Config missing in "+configPath,"error");process.exit(1);}if(!WEBVIEW_CONFIG.android){WEBVIEW_CONFIG.android={};}return{WEBVIEW_CONFIG};}async function saveConfig(newConfig){try{const existingConfigFile=_fs.default.readFileSync(configPath,"utf8");const existingConfig=JSON.parse(existingConfigFile);const updatedConfig={...existingConfig,WEBVIEW_CONFIG:{...existingConfig.WEBVIEW_CONFIG,android:newConfig.WEBVIEW_CONFIG.android}};_fs.default.writeFileSync(configPath,JSON.stringify(updatedConfig,null,2));progress.log("Configuration saved successfully","success");}catch(error){progress.log("Failed to save configuration: "+error.message,"error");process.exit(1);}}function validateAndroidTools(androidConfig){const ANDROID_SDK=androidConfig.sdkPath;const EMULATOR_PATH=`${ANDROID_SDK}/emulator/emulator`;const ADB_PATH=`${ANDROID_SDK}/platform-tools/adb`;if(!ANDROID_SDK){throw new Error("Android SDK path is not configured");}if(!_fs.default.existsSync(ANDROID_SDK)){throw new Error(`Android SDK path does not exist: ${ANDROID_SDK}`);}if(!_fs.default.existsSync(ADB_PATH)){throw new Error(`ADB not found at: ${ADB_PATH}`);}try{(0,_utils.runCommand)(`${ADB_PATH} version`);progress.log("ADB is valid","success");}catch(error){throw new Error(`ADB is not working properly: ${error.message}`);}if(!_fs.default.existsSync(EMULATOR_PATH)){throw new Error(`Emulator not found at: ${EMULATOR_PATH}`);}try{(0,_utils.runCommand)(`${EMULATOR_PATH} -version`);progress.log("Emulator is valid","success");}catch(error){throw new Error(`Emulator is not working properly: ${error.message}`);}try{const avdOutput=(0,_utils.runCommand)(`${EMULATOR_PATH} -list-avds`);if(!avdOutput.includes(androidConfig.emulatorName)){throw new Error(`Specified emulator "${androidConfig.emulatorName}" not found in available AVDs`);}progress.log(`Emulator "${androidConfig.emulatorName}" exists`,"success");}catch(error){throw new Error(`Error checking emulator AVD: ${error.message}`);}progress.log("Android tools validation completed successfully!","success");return{EMULATOR_PATH,ADB_PATH};}async function checkEmulator(ADB_PATH){try{const devices=(0,_utils.runCommand)(`${ADB_PATH} devices`);return devices.includes("emulator");}catch(error){progress.log(`Error checking emulator: ${error}`,"error");return false;}}async function startEmulator(EMULATOR_PATH,androidConfig){progress.log(`Starting emulator: ${androidConfig.emulatorName}...`);// eslint-disable-next-line security/detect-child-process
7
- // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - EMULATOR_PATH is derived from SDK configuration and emulatorName is local native setup config.
8
- (0,_child_process.exec)(`${EMULATOR_PATH} -avd ${androidConfig.emulatorName} -read-only > /dev/null &`,error=>{if(error){progress.log(`Error starting emulator: ${error}`,"error");}});}async function updateLocalProperties(sdkPath){progress.start("properties");try{(0,_utils.runCommand)(`cd ${pwd}/androidProject && ./gradlew updateSdkPath -PsdkPath="${sdkPath}"`);progress.log("Updated local.properties successfully","success");progress.complete("properties");}catch(error){progress.fail("properties",`Failed to update local.properties: ${error.message}`);throw error;}}async function setupAndroidEnvironment(){try{await validateJavaEnvironment();progress.start("config");await initializeConfig();const config=await(0,_utils.validateAndCompleteConfig)("android",configPath);progress.complete("config");progress.start("androidTools");const{EMULATOR_PATH,ADB_PATH}=validateAndroidTools(config.android);progress.complete("androidTools");progress.start("emulator");const emulatorRunning=await checkEmulator(ADB_PATH);if(!emulatorRunning){progress.log("No emulator running, attempting to start one...");await startEmulator(EMULATOR_PATH,config.android);}else{progress.log("Emulator already running, proceeding with installation...","success");}progress.complete("emulator");if(config.android?.sdkPath){await updateLocalProperties(config.android.sdkPath);}progress.start("saveConfig");await saveConfig({WEBVIEW_CONFIG:config});progress.complete("saveConfig");progress.start("setupServer");await(0,_setupServer.setupServer)(configPath);progress.complete("setupServer");progress.printTreeContent("Configuration Explanation",["WEBVIEW_CONFIG: Main configuration object for the WebView setup",{text:"port: Port number for the WebView server",indent:1,prefix:"├─ ",color:"gray"},{text:"android: Android-specific configuration",indent:1,prefix:"└─ ",color:"gray"},{text:"buildType: Build type (debug/release)",indent:2,prefix:"├─ ",color:"gray"},{text:"sdkPath: Android SDK path",indent:2,prefix:"├─ ",color:"gray"},{text:"emulatorName: Selected Android emulator name",indent:2,prefix:"└─ ",color:"gray"}]);progress.printTreeContent("Final Configuration",[JSON.stringify(config,null,2)]);process.exit(0);}catch(error){if(progress.currentStep){progress.fail(progress.currentStep.id,error.message);}process.exit(1);}}// Execute the main setup
1
+ "use strict";
2
+
3
+ var _fs = _interopRequireDefault(require("fs"));
4
+ var _path = _interopRequireDefault(require("path"));
5
+ var _child_process = require("child_process");
6
+ var _utils = require("./utils.js");
7
+ var _TerminalProgress = _interopRequireDefault(require("./TerminalProgress.js"));
8
+ var _setupServer = require("./setupServer.js");
9
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
10
+ const catalystCorePath = _path.default.dirname(require.resolve("catalyst-core/package.json"));
11
+ const pwd = _path.default.join(catalystCorePath, "dist/native");
12
+ const configPath = `${process.env.PWD}/config/config.json`;
13
+ const steps = {
14
+ java: "Check Java Environment",
15
+ config: "Initialize Configuration",
16
+ androidTools: "Validate Android Tools",
17
+ emulator: "Configure Android Emulator",
18
+ properties: "Update Local Properties",
19
+ saveConfig: "Save Configuration",
20
+ setupServer: "Setup Server"
21
+ };
22
+ const progressPaddingConfig = {
23
+ titlePaddingTop: 2,
24
+ titlePaddingBottom: 1,
25
+ stepPaddingLeft: 4,
26
+ stepSpacing: 1,
27
+ errorPaddingLeft: 6,
28
+ bottomMargin: 2
29
+ };
30
+ const progress = new _TerminalProgress.default(steps, "Catalyst Universal Android Setup", progressPaddingConfig);
31
+ async function checkJavaInstallation() {
32
+ try {
33
+ (0, _utils.runCommand)("java -version");
34
+ (0, _utils.runCommand)("javac -version");
35
+ progress.log("Java is installed and configured", "success");
36
+ return true;
37
+ } catch (error) {
38
+ progress.log("Java installation not found or not properly configured", "error");
39
+ return false;
40
+ }
41
+ }
42
+ async function installJava() {
43
+ progress.log("Installing Java (Zulu JDK 17)...");
44
+ try {
45
+ try {
46
+ (0, _utils.runCommand)("brew --version");
47
+ } catch (error) {
48
+ throw new Error("Homebrew is required but not installed. Please install Homebrew first.");
49
+ }
50
+ const javaHome = "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home";
51
+
52
+ // Try installing Java
53
+ try {
54
+ await (0, _utils.runCommand)("brew install --cask zulu@17");
55
+ } catch (error) {
56
+ if (error.message.includes("already installed")) {
57
+ progress.log("Attempting to reinstall Java...", "info");
58
+ try {
59
+ await (0, _utils.runCommand)("brew reinstall --cask zulu@17");
60
+ } catch (reinstallError) {
61
+ throw new Error(`Failed to reinstall Java: ${reinstallError.message}. Please run 'brew reinstall --cask zulu@17' manually.`);
62
+ }
63
+ } else {
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ // Verify Java installation
69
+ if (!_fs.default.existsSync(javaHome)) {
70
+ throw new Error(`Java installation not found at ${javaHome}. Please verify the installation manually.`);
71
+ }
72
+
73
+ // Set up environment variables
74
+ const homeDir = process.env.HOME;
75
+ const shellProfile = `${homeDir}/.zshrc`;
76
+ const exportCommand = `\n# Java Configuration\nexport JAVA_HOME=${javaHome}\nexport PATH=$JAVA_HOME/bin:$PATH\n`;
77
+ try {
78
+ _fs.default.appendFileSync(shellProfile, exportCommand);
79
+ } catch (error) {
80
+ throw new Error(`Failed to update ${shellProfile}: ${error.message}. Please add the following lines manually:\n${exportCommand}`);
81
+ }
82
+
83
+ // Set for current process
84
+ process.env.JAVA_HOME = javaHome;
85
+ process.env.PATH = `${javaHome}/bin:${process.env.PATH}`;
86
+
87
+ // Verify Java configuration
88
+ try {
89
+ const javaVersion = (0, _utils.runCommand)("java -version");
90
+ progress.log("Java installed and configured successfully", "success");
91
+ progress.log(`Java Version: ${javaVersion}`, "info");
92
+ progress.log(`IMPORTANT: Please run the following commands to complete setup:`, "warning");
93
+ progress.log(`1. source ${shellProfile}`, "info");
94
+ progress.log(`2. echo $JAVA_HOME`, "info");
95
+ return true;
96
+ } catch (error) {
97
+ throw new Error(`Java installation verified but environment not configured. Please run:\n1. source ${shellProfile}\n2. echo $JAVA_HOME`);
98
+ }
99
+ } catch (error) {
100
+ progress.log(error.message, "error");
101
+ progress.log("Manual Setup Instructions:", "info");
102
+ progress.log("1. Run: brew reinstall --cask zulu@17", "info");
103
+ progress.log("2. Add to ~/.zshrc:", "info");
104
+ progress.log(" export JAVA_HOME=/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home", "info");
105
+ progress.log(" export PATH=$JAVA_HOME/bin:$PATH", "info");
106
+ progress.log("3. Run: source ~/.zshrc", "info");
107
+ throw error;
108
+ }
109
+ }
110
+ async function validateJavaEnvironment() {
111
+ progress.start("java");
112
+ const hasJava = await checkJavaInstallation();
113
+ if (!hasJava) {
114
+ progress.log("Java installation required. Starting installation process...");
115
+ const javaInstalled = await installJava();
116
+ if (!javaInstalled) {
117
+ progress.fail("java", "Failed to set up Java environment");
118
+ throw new Error("Failed to set up Java environment");
119
+ }
120
+ }
121
+ progress.complete("java");
122
+ }
123
+ async function initializeConfig() {
124
+ const configFile = _fs.default.readFileSync(configPath, "utf8");
125
+ const config = JSON.parse(configFile);
126
+ const {
127
+ WEBVIEW_CONFIG
128
+ } = config;
129
+ if (!WEBVIEW_CONFIG || Object.keys(WEBVIEW_CONFIG).length === 0) {
130
+ progress.log("WebView Config missing in " + configPath, "error");
131
+ process.exit(1);
132
+ }
133
+ if (!WEBVIEW_CONFIG.android) {
134
+ WEBVIEW_CONFIG.android = {};
135
+ }
136
+ return {
137
+ WEBVIEW_CONFIG
138
+ };
139
+ }
140
+ async function saveConfig(newConfig) {
141
+ try {
142
+ const existingConfigFile = _fs.default.readFileSync(configPath, "utf8");
143
+ const existingConfig = JSON.parse(existingConfigFile);
144
+ const updatedConfig = {
145
+ ...existingConfig,
146
+ WEBVIEW_CONFIG: {
147
+ ...existingConfig.WEBVIEW_CONFIG,
148
+ android: newConfig.WEBVIEW_CONFIG.android
149
+ }
150
+ };
151
+ _fs.default.writeFileSync(configPath, JSON.stringify(updatedConfig, null, 2));
152
+ progress.log("Configuration saved successfully", "success");
153
+ } catch (error) {
154
+ progress.log("Failed to save configuration: " + error.message, "error");
155
+ process.exit(1);
156
+ }
157
+ }
158
+ function validateAndroidTools(androidConfig) {
159
+ const ANDROID_SDK = androidConfig.sdkPath;
160
+ const EMULATOR_PATH = `${ANDROID_SDK}/emulator/emulator`;
161
+ const ADB_PATH = `${ANDROID_SDK}/platform-tools/adb`;
162
+ if (!ANDROID_SDK) {
163
+ throw new Error("Android SDK path is not configured");
164
+ }
165
+ if (!_fs.default.existsSync(ANDROID_SDK)) {
166
+ throw new Error(`Android SDK path does not exist: ${ANDROID_SDK}`);
167
+ }
168
+ if (!_fs.default.existsSync(ADB_PATH)) {
169
+ throw new Error(`ADB not found at: ${ADB_PATH}`);
170
+ }
171
+ try {
172
+ (0, _utils.runCommand)(`${ADB_PATH} version`);
173
+ progress.log("ADB is valid", "success");
174
+ } catch (error) {
175
+ throw new Error(`ADB is not working properly: ${error.message}`);
176
+ }
177
+ if (!_fs.default.existsSync(EMULATOR_PATH)) {
178
+ throw new Error(`Emulator not found at: ${EMULATOR_PATH}`);
179
+ }
180
+ try {
181
+ (0, _utils.runCommand)(`${EMULATOR_PATH} -version`);
182
+ progress.log("Emulator is valid", "success");
183
+ } catch (error) {
184
+ throw new Error(`Emulator is not working properly: ${error.message}`);
185
+ }
186
+ try {
187
+ const avdOutput = (0, _utils.runCommand)(`${EMULATOR_PATH} -list-avds`);
188
+ if (!avdOutput.includes(androidConfig.emulatorName)) {
189
+ throw new Error(`Specified emulator "${androidConfig.emulatorName}" not found in available AVDs`);
190
+ }
191
+ progress.log(`Emulator "${androidConfig.emulatorName}" exists`, "success");
192
+ } catch (error) {
193
+ throw new Error(`Error checking emulator AVD: ${error.message}`);
194
+ }
195
+ progress.log("Android tools validation completed successfully!", "success");
196
+ return {
197
+ EMULATOR_PATH,
198
+ ADB_PATH
199
+ };
200
+ }
201
+ async function checkEmulator(ADB_PATH) {
202
+ try {
203
+ const devices = (0, _utils.runCommand)(`${ADB_PATH} devices`);
204
+ return devices.includes("emulator");
205
+ } catch (error) {
206
+ progress.log(`Error checking emulator: ${error}`, "error");
207
+ return false;
208
+ }
209
+ }
210
+ async function startEmulator(EMULATOR_PATH, androidConfig) {
211
+ progress.log(`Starting emulator: ${androidConfig.emulatorName}...`);
212
+ // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - EMULATOR_PATH is derived from SDK configuration and emulatorName is local native setup config.
213
+ // eslint-disable-next-line security/detect-child-process
214
+ (0, _child_process.exec)(`${EMULATOR_PATH} -avd ${androidConfig.emulatorName} -read-only > /dev/null &`, error => {
215
+ if (error) {
216
+ progress.log(`Error starting emulator: ${error}`, "error");
217
+ }
218
+ });
219
+ }
220
+ async function updateLocalProperties(sdkPath) {
221
+ progress.start("properties");
222
+ try {
223
+ (0, _utils.runCommand)(`cd ${pwd}/androidProject && ./gradlew updateSdkPath -PsdkPath="${sdkPath}"`);
224
+ progress.log("Updated local.properties successfully", "success");
225
+ progress.complete("properties");
226
+ } catch (error) {
227
+ progress.fail("properties", `Failed to update local.properties: ${error.message}`);
228
+ throw error;
229
+ }
230
+ }
231
+ async function setupAndroidEnvironment() {
232
+ try {
233
+ await validateJavaEnvironment();
234
+ progress.start("config");
235
+ await initializeConfig();
236
+ const config = await (0, _utils.validateAndCompleteConfig)("android", configPath);
237
+ progress.complete("config");
238
+ progress.start("androidTools");
239
+ const {
240
+ EMULATOR_PATH,
241
+ ADB_PATH
242
+ } = validateAndroidTools(config.android);
243
+ progress.complete("androidTools");
244
+ progress.start("emulator");
245
+ const emulatorRunning = await checkEmulator(ADB_PATH);
246
+ if (!emulatorRunning) {
247
+ progress.log("No emulator running, attempting to start one...");
248
+ await startEmulator(EMULATOR_PATH, config.android);
249
+ } else {
250
+ progress.log("Emulator already running, proceeding with installation...", "success");
251
+ }
252
+ progress.complete("emulator");
253
+ if (config.android?.sdkPath) {
254
+ await updateLocalProperties(config.android.sdkPath);
255
+ }
256
+ progress.start("saveConfig");
257
+ await saveConfig({
258
+ WEBVIEW_CONFIG: config
259
+ });
260
+ progress.complete("saveConfig");
261
+ progress.start("setupServer");
262
+ await (0, _setupServer.setupServer)(configPath);
263
+ progress.complete("setupServer");
264
+ progress.printTreeContent("Configuration Explanation", ["WEBVIEW_CONFIG: Main configuration object for the WebView setup", {
265
+ text: "port: Port number for the WebView server",
266
+ indent: 1,
267
+ prefix: "├─ ",
268
+ color: "gray"
269
+ }, {
270
+ text: "android: Android-specific configuration",
271
+ indent: 1,
272
+ prefix: "└─ ",
273
+ color: "gray"
274
+ }, {
275
+ text: "buildType: Build type (debug/release)",
276
+ indent: 2,
277
+ prefix: "├─ ",
278
+ color: "gray"
279
+ }, {
280
+ text: "sdkPath: Android SDK path",
281
+ indent: 2,
282
+ prefix: "├─ ",
283
+ color: "gray"
284
+ }, {
285
+ text: "emulatorName: Selected Android emulator name",
286
+ indent: 2,
287
+ prefix: "└─ ",
288
+ color: "gray"
289
+ }]);
290
+ progress.printTreeContent("Final Configuration", [JSON.stringify(config, null, 2)]);
291
+ process.exit(0);
292
+ } catch (error) {
293
+ if (progress.currentStep) {
294
+ progress.fail(progress.currentStep.id, error.message);
295
+ }
296
+ process.exit(1);
297
+ }
298
+ }
299
+
300
+ // Execute the main setup
9
301
  setupAndroidEnvironment();