catalyst-core 0.2.0-beta.2 → 0.3.0-beta.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 (184) 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/index.jsx +8 -0
  6. package/dist/logger.js +74 -3
  7. package/dist/native/androidProject/app/build.gradle.kts +1 -0
  8. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +143 -4
  9. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +26 -6
  10. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MetricsMonitor.kt +143 -17
  11. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +6 -0
  12. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/WebCacheManager.kt +26 -0
  13. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +56 -1
  14. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/KeyboardUtil.kt +48 -8
  15. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/PerfDeliveryState.kt +36 -0
  16. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/PerfEventBuffer.kt +289 -0
  17. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/ProfilerNavigationState.kt +13 -0
  18. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/ProfilerLifecycleTest.kt +51 -0
  19. package/dist/native/androidSetup.js +300 -8
  20. package/dist/native/bridge/WebBridge.js +481 -59
  21. package/dist/native/bridge/constants/NativeInterfaces.js +290 -44
  22. package/dist/native/bridge/errors.js +250 -26
  23. package/dist/native/bridge/hooks/index.js +125 -1
  24. package/dist/native/bridge/hooks/useCamera.js +347 -9
  25. package/dist/native/bridge/hooks/useDataProtection.js +200 -2
  26. package/dist/native/bridge/hooks/useDeviceInfo.js +71 -2
  27. package/dist/native/bridge/hooks/useFilePicker.js +429 -2
  28. package/dist/native/bridge/hooks/useGoogleSignIn.js +81 -1
  29. package/dist/native/bridge/hooks/useHapticFeedback.js +196 -1
  30. package/dist/native/bridge/hooks/useIntent.js +95 -1
  31. package/dist/native/bridge/hooks/useNativeTransition.js +202 -27
  32. package/dist/native/bridge/hooks/useNetworkStatus.js +84 -3
  33. package/dist/native/bridge/hooks/useNotification.js +318 -2
  34. package/dist/native/bridge/hooks/useSafeArea.js +84 -2
  35. package/dist/native/bridge/hooks/useVideoStream.js +434 -6
  36. package/dist/native/bridge/hooks/utils.js +27 -1
  37. package/dist/native/bridge/hooks.js +16 -1
  38. package/dist/native/bridge/perf/collectors/ApiCollector.js +85 -0
  39. package/dist/native/bridge/perf/collectors/CacheCollector.js +215 -0
  40. package/dist/native/bridge/perf/collectors/HookCollector.js +165 -0
  41. package/dist/native/bridge/perf/collectors/InsightsCollector.js +505 -0
  42. package/dist/native/bridge/perf/collectors/InteractionCollector.js +273 -0
  43. package/dist/native/bridge/perf/collectors/KeyboardCollector.js +124 -0
  44. package/dist/native/bridge/perf/collectors/NavigationCollector.js +294 -0
  45. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +183 -0
  46. package/dist/native/bridge/perf/collectors/RenderCollector.js +284 -0
  47. package/dist/native/bridge/perf/collectors/ScrollCollector.js +111 -0
  48. package/dist/native/bridge/perf/collectors/ThreadCollector.js +111 -0
  49. package/dist/native/bridge/perf/core/constants.js +126 -0
  50. package/dist/native/bridge/perf/core/measure.js +189 -0
  51. package/dist/native/bridge/perf/core/session.js +100 -0
  52. package/dist/native/bridge/perf/core/store.js +554 -0
  53. package/dist/native/bridge/perf/core/summary.js +174 -0
  54. package/dist/native/bridge/perf/core/traceExport.js +240 -0
  55. package/dist/native/bridge/perf/core/waterfalls.js +146 -0
  56. package/dist/native/bridge/perf/index.js +402 -0
  57. package/dist/native/bridge/safeArea.js +53 -5
  58. package/dist/native/bridge/standardizedHooks.js +595 -82
  59. package/dist/native/bridge/useBaseHook.js +274 -38
  60. package/dist/native/bridge/utils/CameraUtils.js +201 -56
  61. package/dist/native/bridge/utils/FileObjectConverter.js +129 -18
  62. package/dist/native/bridge/utils/NativeBridge.js +425 -141
  63. package/dist/native/buildAndroid/assets.js +510 -13
  64. package/dist/native/buildAndroid/build.js +296 -19
  65. package/dist/native/buildAndroid/config.js +87 -1
  66. package/dist/native/buildAndroid/index.js +374 -1
  67. package/dist/native/buildAppAndroid.js +34 -2
  68. package/dist/native/buildAppIos.js +62 -2
  69. package/dist/native/buildIos/assets.js +609 -8
  70. package/dist/native/buildIos/build.js +639 -20
  71. package/dist/native/buildIos/config.js +294 -12
  72. package/dist/native/buildIos/index.js +343 -6
  73. package/dist/native/buildIos/plugins.js +343 -16
  74. package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +43 -0
  75. package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +28 -0
  76. package/dist/native/internal-plugins/device-info-plugin/manifest.json +19 -0
  77. package/dist/native/internalPluginUtils.js +232 -7
  78. package/dist/native/iosnativeWebView/Sources/Core/Constants/CatalystConstants.swift +19 -0
  79. package/dist/native/iosnativeWebView/Sources/Core/Constants/ConfigConstants.swift +4 -0
  80. package/dist/native/iosnativeWebView/Sources/Core/Utils/BootTimingUtility.swift +5 -1
  81. package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +10 -0
  82. package/dist/native/iosnativeWebView/Sources/Core/Utils/DisplayLinkPerfMonitor.swift +89 -0
  83. package/dist/native/iosnativeWebView/Sources/Core/Utils/PerfEventBuffer.swift +373 -0
  84. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeFileHandler.swift +35 -1
  85. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +24 -11
  86. package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +9 -0
  87. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +126 -0
  88. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +75 -1
  89. package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeMessageValidatorTests.swift +13 -0
  90. package/dist/native/package.json +3 -0
  91. package/dist/native/plugin-bridge/PluginBridge.js +227 -1
  92. package/dist/native/pluginComposerAndroid.js +407 -23
  93. package/dist/native/pluginComposerIos.js +435 -11
  94. package/dist/native/renameAndroidProject.js +1094 -88
  95. package/dist/native/setupEmulatorIos.js +289 -22
  96. package/dist/native/setupServer.js +160 -19
  97. package/dist/native/terminalProgress.js +199 -11
  98. package/dist/native/utils.js +193 -15
  99. package/dist/offline/catalyst-sw.js +145 -1
  100. package/dist/offline/registerServiceWorker.js +33 -1
  101. package/dist/otel.js +375 -46
  102. package/dist/router/ClientRouter.js +14 -2
  103. package/dist/router/ServerRouter.js +9 -1
  104. package/dist/router.js +1 -1
  105. package/dist/scripts/build.js +112 -3
  106. package/dist/scripts/checkVersion.js +3 -1
  107. package/dist/scripts/cleanCache.js +30 -1
  108. package/dist/scripts/generateOfflineManifest.js +116 -1
  109. package/dist/scripts/loadEnvironmentVariables.js +32 -6
  110. package/dist/scripts/plugins.js +309 -1
  111. package/dist/scripts/preServerInit.js +13 -0
  112. package/dist/scripts/scriptUtils.js +54 -3
  113. package/dist/scripts/serve.js +51 -4
  114. package/dist/scripts/start.js +47 -10
  115. package/dist/scripts/validator.js +149 -1
  116. package/dist/sentry.cjs +127 -0
  117. package/dist/sentry.js +125 -5
  118. package/dist/server/expressServer.js +250 -5
  119. package/dist/server/manifestCache.js +36 -0
  120. package/dist/server/renderer/ChunkExtractor.js +144 -0
  121. package/dist/server/renderer/document/Body.jsx +59 -0
  122. package/dist/server/renderer/document/FastRefresh.jsx +20 -0
  123. package/dist/server/renderer/document/Head.jsx +60 -0
  124. package/dist/server/renderer/document/index.jsx +2 -0
  125. package/dist/server/renderer/extract.js +149 -31
  126. package/dist/server/renderer/handler.jsx +433 -0
  127. package/dist/server/renderer/index.js +8 -1
  128. package/dist/server/renderer/render.js +47 -0
  129. package/dist/server/utils/botDetectionMiddleware.js +29 -0
  130. package/dist/server/utils/cjsRequire.js +10 -0
  131. package/dist/server/utils/userAgentUtil.js +87 -9
  132. package/dist/server/utils/validator.js +164 -1
  133. package/dist/vite/FastRefresh.jsx +20 -0
  134. package/dist/vite/inject-cache-key-plugin.js +276 -0
  135. package/dist/vite/loadCustomViteConfig.js +22 -0
  136. package/dist/vite/manifest-categorization-plugin.js +455 -0
  137. package/dist/vite/node-loader.mjs +458 -0
  138. package/dist/vite/scssParams.js +21 -0
  139. package/dist/vite/vite.config.client.js +130 -0
  140. package/dist/vite/vite.config.js +422 -0
  141. package/dist/vite/vite.config.server.js +84 -0
  142. package/dist/web-router/components/MetaTag.jsx +34 -0
  143. package/dist/web-router/components/RouterDataProvider.jsx +332 -0
  144. package/dist/web-router/components/Split.jsx +200 -0
  145. package/dist/web-router/components/SplitInview.jsx +106 -0
  146. package/dist/web-router/components/SsrRequestContext.jsx +14 -0
  147. package/dist/web-router/context.jsx +12 -0
  148. package/dist/web-router/hooks.jsx +72 -0
  149. package/dist/web-router/types/components/MetaTag.d.ts +2 -0
  150. package/dist/web-router/types/components/RouterDataProvider.d.ts +95 -0
  151. package/dist/web-router/types/context.d.ts +14 -0
  152. package/dist/web-router/types/hooks.d.ts +1 -0
  153. package/dist/web-router/types/index.d.ts +3 -0
  154. package/dist/web-router/types/utils/metaDataUtils.d.ts +24 -0
  155. package/dist/web-router/utils/metaDataUtils.jsx +119 -0
  156. package/mcp_v2/conversion-tasks.json +35 -17
  157. package/mcp_v2/knowledge-base.json +124 -13
  158. package/mcp_v2/lib/helpers.js +10 -0
  159. package/mcp_v2/mcp.js +4 -4
  160. package/mcp_v2/package.json +1 -0
  161. package/mcp_v2/tools/build.js +116 -20
  162. package/mcp_v2/tools/conversion.js +106 -5
  163. package/mcp_v2/tools/tasks.js +18 -14
  164. package/package.json +35 -68
  165. package/babel.config.js +0 -32
  166. package/dist/index.js +0 -1
  167. package/dist/scripts/devBuild.js +0 -3
  168. package/dist/scripts/devServe.js +0 -3
  169. package/dist/scripts/loadScriptsBeforeServerStarts.js +0 -4
  170. package/dist/scripts/registerAliases.js +0 -6
  171. package/dist/server/renderer/document/Body.js +0 -11
  172. package/dist/server/renderer/document/Head.js +0 -9
  173. package/dist/server/renderer/document/index.js +0 -1
  174. package/dist/server/renderer/handler.js +0 -47
  175. package/dist/server/startServer.js +0 -57
  176. package/dist/webpack/babel-plugins/remove-client.plugin.js +0 -5
  177. package/dist/webpack/babel-plugins/remove-ssr.plugin.js +0 -5
  178. package/dist/webpack/babel.config.client.js +0 -1
  179. package/dist/webpack/babel.config.ssr.js +0 -1
  180. package/dist/webpack/base.babel.js +0 -7
  181. package/dist/webpack/development.client.babel.js +0 -10
  182. package/dist/webpack/production.client.babel.js +0 -4
  183. package/dist/webpack/production.ssr.babel.js +0 -8
  184. package/dist/webpack/scssParams.js +0 -9
@@ -0,0 +1,289 @@
1
+ package io.yourname.androidproject.utils
2
+
3
+ import android.os.Handler
4
+ import android.os.Looper
5
+ import android.os.SystemClock
6
+ import android.webkit.WebView
7
+ import io.yourname.androidproject.BuildConfig
8
+ import org.json.JSONArray
9
+ import org.json.JSONObject
10
+ import java.util.concurrent.ConcurrentLinkedQueue
11
+ import java.util.concurrent.atomic.AtomicBoolean
12
+
13
+ /**
14
+ * PerfEventBuffer — native ring buffer for Catalyst profiler events.
15
+ *
16
+ * Collects all events from onCreate (boot timing, cache, API calls) and flushes
17
+ * them as a single JSON array to the web layer after onPageFinished.
18
+ *
19
+ * Design:
20
+ * - add() is safe from any thread (ConcurrentLinkedQueue)
21
+ * - scheduleFlush() called from onPageFinished — posts to main thread after 250ms
22
+ * so the JS environment is ready to receive events
23
+ * - flush() serialises all events to JSON array, calls window.__catalystPerfBatch()
24
+ * - CacheSummarizer: running tally of hit/miss/fetch + top-5 min-heap for InsightsCollector
25
+ * - Max ring buffer size: 500 events (oldest dropped if exceeded)
26
+ *
27
+ * API call timing:
28
+ * - bridgeCallReceived(callId, method) → stamps t0, stores in pendingCalls map
29
+ * - bridgeCallDispatched(callId) → stamps t1, computes durationMs, adds api-call event to buffer
30
+ */
31
+ object PerfEventBuffer {
32
+
33
+ private const val TAG = "PerfEventBuffer"
34
+ private const val MAX_BUFFER_SIZE = 500
35
+ private const val FLUSH_DELAY_MS = 250L
36
+ private const val FLUSH_RETRY_DELAY_MS = 250L
37
+ private const val MAX_FLUSH_RETRIES = 5
38
+ private const val PERIODIC_FLUSH_INTERVAL_MS = 4000L
39
+
40
+ private val buffer = ConcurrentLinkedQueue<JSONObject>()
41
+ private val mainHandler = Handler(Looper.getMainLooper())
42
+ private val flushed = AtomicBoolean(false)
43
+ @Volatile private var profilerEnabled = false
44
+ private val deliveryState = PerfDeliveryState<JSONArray>()
45
+
46
+ // Periodic flush state
47
+ @Volatile private var periodicWebView: WebView? = null
48
+ private var lastFlushedSize = 0
49
+ private val periodicRunnable: Runnable = object : Runnable {
50
+ override fun run() {
51
+ if (!isEnabled()) return
52
+ val wv = periodicWebView ?: return
53
+ val pendingBatch = deliveryState.current()
54
+ if (pendingBatch != null) {
55
+ deliver(wv, pendingBatch)
56
+ } else if (buffer.size > lastFlushedSize) {
57
+ val currentSize = buffer.size
58
+ android.util.Log.d(TAG, "[PerfBuffer] periodicFlush() — $currentSize events since last flush")
59
+ beginFlush(wv, includeCacheSummary = false)
60
+ }
61
+ mainHandler.postDelayed(this, PERIODIC_FLUSH_INTERVAL_MS)
62
+ }
63
+ }
64
+
65
+ // Pending API call timing: callId → t0 nativeMs + method
66
+ private val pendingCalls = java.util.concurrent.ConcurrentHashMap<String, Pair<Long, String>>()
67
+
68
+ // ─── CacheSummarizer ─────────────────────────────────────────────────────
69
+
70
+ private var cacheHits = 0
71
+ private var cacheMisses = 0
72
+ private var cacheFetches = 0
73
+ private var cacheTotalMs = 0L
74
+ // top-5 slowest: list of Pair(durationMs, filename)
75
+ private val cacheTopSlow = java.util.PriorityQueue<Pair<Long, String>>(
76
+ 6, compareBy { it.first }
77
+ )
78
+
79
+ private fun updateCacheSummary(type: String, durationMs: Long, url: String) {
80
+ val filename = try {
81
+ val path = java.net.URI(url).path
82
+ path.split("/").filter { it.isNotEmpty() }.lastOrNull()?.take(40) ?: url.take(40)
83
+ } catch (e: Exception) { url.take(40) }
84
+
85
+ when (type) {
86
+ "cache-hit-memory", "cache-hit-disk" -> cacheHits++
87
+ "cache-miss-fetch" -> cacheMisses++
88
+ "network-fetch-complete" -> {
89
+ cacheFetches++
90
+ cacheTotalMs += durationMs
91
+ cacheTopSlow.add(Pair(durationMs, filename))
92
+ if (cacheTopSlow.size > 5) cacheTopSlow.poll() // remove smallest
93
+ }
94
+ }
95
+ }
96
+
97
+ private fun buildCacheSummaryEvent(): JSONObject? {
98
+ val total = cacheHits + cacheMisses
99
+ if (total == 0) return null
100
+ val hitRate = if (total > 0) (cacheHits * 100.0 / total) else 0.0
101
+ val avgFetchMs = if (cacheFetches > 0) cacheTotalMs / cacheFetches else 0L
102
+ val topSlow = JSONArray()
103
+ cacheTopSlow.sortedByDescending { it.first }.forEach { (ms, name) ->
104
+ topSlow.put(JSONObject().apply {
105
+ put("filename", name)
106
+ put("durationMs", ms)
107
+ })
108
+ }
109
+ return JSONObject().apply {
110
+ put("type", "cache-summary")
111
+ put("hits", cacheHits)
112
+ put("misses", cacheMisses)
113
+ put("fetches", cacheFetches)
114
+ put("total", total)
115
+ put("hitRatePct", Math.round(hitRate * 10) / 10.0)
116
+ put("avgFetchMs", avgFetchMs)
117
+ put("topSlowest", topSlow)
118
+ put("nativeTime", SystemClock.elapsedRealtime())
119
+ }
120
+ }
121
+
122
+ // ─── Public API ──────────────────────────────────────────────────────────
123
+
124
+ fun configure(enabled: Boolean) {
125
+ profilerEnabled = BuildConfig.DEBUG && enabled
126
+ if (!profilerEnabled) reset()
127
+ }
128
+
129
+ fun isEnabled(): Boolean = profilerEnabled
130
+
131
+ /** Add a perf event to the buffer. Safe from any thread. */
132
+ fun add(event: JSONObject) {
133
+ if (!isEnabled()) return
134
+ if (buffer.size >= MAX_BUFFER_SIZE) {
135
+ buffer.poll() // drop oldest
136
+ }
137
+ // Update cache summary tally
138
+ val type = event.optString("type")
139
+ if (type.startsWith("cache-") || type == "network-fetch-complete") {
140
+ updateCacheSummary(type, event.optLong("durationMs", 0), event.optString("url"))
141
+ }
142
+ buffer.add(event)
143
+ android.util.Log.d(TAG, "[PerfBuffer] add() type=$type bufferSize=${buffer.size}")
144
+ }
145
+
146
+ /**
147
+ * Record start of an @JavascriptInterface bridge call.
148
+ * Call this immediately when the method is invoked on the JavascriptInterface.
149
+ */
150
+ fun bridgeCallReceived(callId: String, method: String) {
151
+ if (!isEnabled()) return
152
+ pendingCalls[callId] = Pair(SystemClock.elapsedRealtime(), method)
153
+ }
154
+
155
+ /**
156
+ * Record the end of immediate native bridge-handler work and add an api-call event.
157
+ */
158
+ fun bridgeCallDispatched(callId: String) {
159
+ if (!isEnabled()) return
160
+ val pending = pendingCalls.remove(callId) ?: return
161
+ val (t0, method) = pending
162
+ val t1 = SystemClock.elapsedRealtime()
163
+ val durationMs = t1 - t0
164
+ add(JSONObject().apply {
165
+ put("type", "api-call")
166
+ put("callId", callId)
167
+ put("method", method)
168
+ put("nativeStartMs", t0)
169
+ put("nativeEndMs", t1)
170
+ put("durationMs", durationMs)
171
+ put("thread", Thread.currentThread().name)
172
+ })
173
+ }
174
+
175
+ /**
176
+ * Schedule a flush after onPageFinished (initial/full page load).
177
+ * Also starts the periodic flush loop for subsequent SPA navigations.
178
+ */
179
+ fun scheduleFlush(webView: WebView) {
180
+ if (!isEnabled()) return
181
+ if (flushed.getAndSet(true)) {
182
+ android.util.Log.d(TAG, "[PerfBuffer] scheduleFlush() skipped — already flushed")
183
+ return
184
+ }
185
+ android.util.Log.d(TAG, "[PerfBuffer] scheduleFlush() scheduled in ${FLUSH_DELAY_MS}ms, bufferSize=${buffer.size}")
186
+ mainHandler.postDelayed({
187
+ beginFlush(webView, includeCacheSummary = true)
188
+ // Start periodic flush loop for SPA navigations after initial load
189
+ startPeriodicFlush(webView)
190
+ }, FLUSH_DELAY_MS)
191
+ }
192
+
193
+ /**
194
+ * Flush any pending events immediately (e.g. before a full URL navigation).
195
+ * Does NOT reset the buffer — only drains buffered events up to this point.
196
+ */
197
+ fun flushNow(webView: WebView) {
198
+ if (!isEnabled()) return
199
+ mainHandler.post { beginFlush(webView, includeCacheSummary = false) }
200
+ }
201
+
202
+ /**
203
+ * Start periodic flush loop — fires every 4s and flushes only if new events exist.
204
+ * Call after onPageFinished so the JS receiver is registered.
205
+ */
206
+ fun startPeriodicFlush(webView: WebView) {
207
+ if (!isEnabled()) return
208
+ mainHandler.removeCallbacks(periodicRunnable)
209
+ periodicWebView = webView
210
+ lastFlushedSize = 0
211
+ mainHandler.postDelayed(periodicRunnable, PERIODIC_FLUSH_INTERVAL_MS)
212
+ android.util.Log.d(TAG, "[PerfBuffer] startPeriodicFlush() — interval=${PERIODIC_FLUSH_INTERVAL_MS}ms")
213
+ }
214
+
215
+ /** Stop periodic flush (call on destroy / loadUrl reset). */
216
+ fun stopPeriodicFlush() {
217
+ mainHandler.removeCallbacks(periodicRunnable)
218
+ periodicWebView = null
219
+ }
220
+
221
+ /**
222
+ * Reset the buffer for the next page load.
223
+ * Call this from loadUrl so boot timing events for the new page are captured.
224
+ */
225
+ fun reset() {
226
+ stopPeriodicFlush()
227
+ buffer.clear()
228
+ deliveryState.reset()
229
+ pendingCalls.clear()
230
+ flushed.set(false)
231
+ lastFlushedSize = 0
232
+ cacheHits = 0
233
+ cacheMisses = 0
234
+ cacheFetches = 0
235
+ cacheTotalMs = 0L
236
+ cacheTopSlow.clear()
237
+ }
238
+
239
+ // ─── Flush ───────────────────────────────────────────────────────────────
240
+
241
+ /** Moves the current queue into an immutable in-flight batch until JS acknowledges it. */
242
+ private fun beginFlush(webView: WebView, includeCacheSummary: Boolean) {
243
+ if (!isEnabled()) return
244
+ if (Looper.myLooper() != Looper.getMainLooper()) {
245
+ mainHandler.post { beginFlush(webView, includeCacheSummary) }
246
+ return
247
+ }
248
+ if (deliveryState.hasInFlight()) return
249
+ val events = JSONArray()
250
+ while (buffer.isNotEmpty()) {
251
+ buffer.poll()?.let { events.put(it) }
252
+ }
253
+ if (includeCacheSummary) buildCacheSummaryEvent()?.let { events.put(it) }
254
+ if (events.length() == 0) return
255
+
256
+ val batch = deliveryState.begin(events) ?: return
257
+ deliver(webView, batch)
258
+ }
259
+
260
+ private fun deliver(webView: WebView, batch: PerfDeliveryState.Batch<JSONArray>) {
261
+ if (!isEnabled() || !deliveryState.isCurrent(batch)) return
262
+ try {
263
+ val json = batch.payload.toString().replace("\\", "\\\\").replace("'", "\\'")
264
+ val batchId = JSONObject.quote(batch.id)
265
+ webView.evaluateJavascript(
266
+ "window.__catalystPerfBatch ? window.__catalystPerfBatch($batchId, '$json') : false",
267
+ ) { result ->
268
+ if (result == "true") acknowledge(batch) else retry(webView, batch)
269
+ }
270
+ } catch (_: Exception) {
271
+ retry(webView, batch)
272
+ }
273
+ }
274
+
275
+ private fun acknowledge(batch: PerfDeliveryState.Batch<JSONArray>) {
276
+ if (deliveryState.acknowledge(batch)) {
277
+ lastFlushedSize = 0
278
+ }
279
+ }
280
+
281
+ private fun retry(webView: WebView, batch: PerfDeliveryState.Batch<JSONArray>) {
282
+ val retry = deliveryState.retry(batch, MAX_FLUSH_RETRIES)
283
+ if (retry == null) {
284
+ android.util.Log.w(TAG, "[PerfBuffer] batch ${batch.id} was not acknowledged; retaining until reset")
285
+ return
286
+ }
287
+ mainHandler.postDelayed({ deliver(webView, retry) }, FLUSH_RETRY_DELAY_MS)
288
+ }
289
+ }
@@ -0,0 +1,13 @@
1
+ package io.yourname.androidproject.utils
2
+
3
+ internal class ProfilerNavigationState {
4
+ private var isInitialNavigation = true
5
+
6
+ data class Navigation(val url: String, val shouldReset: Boolean)
7
+
8
+ fun begin(url: String): Navigation {
9
+ val shouldReset = !isInitialNavigation
10
+ isInitialNavigation = false
11
+ return Navigation(url, shouldReset)
12
+ }
13
+ }
@@ -0,0 +1,51 @@
1
+ package io.yourname.androidproject
2
+
3
+ import io.yourname.androidproject.utils.PerfDeliveryState
4
+ import io.yourname.androidproject.utils.ProfilerNavigationState
5
+ import org.junit.Assert.assertEquals
6
+ import org.junit.Assert.assertFalse
7
+ import org.junit.Assert.assertNotNull
8
+ import org.junit.Assert.assertNull
9
+ import org.junit.Assert.assertTrue
10
+ import org.junit.Test
11
+
12
+ class ProfilerLifecycleTest {
13
+ @Test
14
+ fun `offline startup and retry navigation record separate page lifecycles`() {
15
+ val state = ProfilerNavigationState()
16
+ val offlineStart = state.begin("file:///android_asset/offline/offline.html")
17
+ val retry = state.begin("https://example.com")
18
+
19
+ assertFalse(offlineStart.shouldReset)
20
+ assertEquals("file:///android_asset/offline/offline.html", offlineStart.url)
21
+ assertTrue(retry.shouldReset)
22
+ assertEquals("https://example.com", retry.url)
23
+ }
24
+
25
+ @Test
26
+ fun `batch remains in flight until acknowledged and retries retain its id`() {
27
+ val state = PerfDeliveryState<List<String>>()
28
+ val batch = state.begin(listOf("boot-activity-created"))
29
+
30
+ assertNotNull(batch)
31
+ assertNull(state.begin(listOf("boot-page-started")))
32
+
33
+ val retry = state.retry(batch!!, maxRetries = 2)
34
+ assertNotNull(retry)
35
+ assertEquals(batch.id, retry!!.id)
36
+ assertEquals(1, retry.retryCount)
37
+ assertTrue(state.acknowledge(retry))
38
+ assertNotNull(state.begin(listOf("boot-page-started")))
39
+ }
40
+
41
+ @Test
42
+ fun `reset invalidates stale delivery callbacks`() {
43
+ val state = PerfDeliveryState<List<String>>()
44
+ val batch = state.begin(listOf("boot-load-url"))!!
45
+
46
+ state.reset()
47
+
48
+ assertFalse(state.acknowledge(batch))
49
+ assertNull(state.retry(batch, maxRetries = 2))
50
+ }
51
+ }
@@ -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();