catalyst-core 0.3.0-beta.5 → 0.3.0-canary.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 (316) hide show
  1. package/changelog.md +185 -71
  2. package/dist/errors/generateDocs.js +100 -0
  3. package/dist/errors/index.js +243 -0
  4. package/dist/errors/registry.js +483 -0
  5. package/dist/errors-index.json +506 -0
  6. package/dist/globals.d.ts +62 -0
  7. package/dist/index.d.ts +5 -0
  8. package/dist/index.jsx +5 -8
  9. package/dist/logger.js +1 -1
  10. package/dist/native/androidProject/app/build.gradle.kts +142 -7
  11. package/dist/native/androidProject/app/src/main/AndroidManifest.xml +4 -0
  12. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +2 -2
  13. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/BarcodeDetector.kt +3 -0
  14. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/CameraSessionManager.kt +3 -0
  15. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +43 -8
  16. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/BridgeUtilsTest.kt +181 -1
  17. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CameraUtilsTest.kt +156 -236
  18. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileSizeRouterUtilsTest.kt +471 -0
  19. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileUtilsTest.kt +222 -226
  20. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FrameworkServerUtilsTest.kt +300 -0
  21. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/IntentUtilsTest.kt +244 -0
  22. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/MetricsMonitorTest.kt +359 -0
  23. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NativeBridgeTest.kt +108 -63
  24. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NotificationUtilsTest.kt +329 -385
  25. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/OfflineCacheServiceTest.kt +302 -0
  26. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/PerfEventBufferTest.kt +133 -0
  27. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/UtilsKtTest.kt +152 -0
  28. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/WebCacheManagerTest.kt +145 -324
  29. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/BarcodeDetectorTest.kt +132 -0
  30. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/HoldControllerTest.kt +155 -0
  31. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/TorchControllerTest.kt +150 -0
  32. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/VideoStreamStateMachineTest.kt +120 -0
  33. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/VideoStreamStateTest.kt +62 -0
  34. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/ViewfinderMapperTest.kt +143 -0
  35. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/ZoomControllerTest.kt +421 -0
  36. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/design/DesignTokensTest.kt +207 -0
  37. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/plugins/PluginBridgeTest.kt +368 -0
  38. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/FridaDetectorTest.kt +193 -0
  39. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/SecurityCheckManagerTest.kt +260 -0
  40. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/SecurityCheckSchedulerTest.kt +166 -0
  41. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/DeviceInfoUtilsTest.kt +167 -0
  42. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/DownloadUtilsTest.kt +197 -0
  43. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/NetworkMonitorTest.kt +271 -0
  44. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/PushNotificationUtilsTest.kt +119 -0
  45. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/SafeAreaUtilsTest.kt +108 -0
  46. package/dist/native/androidProject/build.gradle.kts +1 -0
  47. package/dist/native/androidProject/gradle/libs.versions.toml +26 -0
  48. package/dist/native/androidProject/settings.gradle.kts +11 -8
  49. package/dist/native/androidSetup.js +2 -2
  50. package/dist/native/bridge/WebBridge.d.ts +98 -0
  51. package/dist/native/bridge/WebBridge.js +40 -6
  52. package/dist/native/bridge/constants/NativeInterfaces.d.ts +391 -0
  53. package/dist/native/bridge/errors.d.ts +67 -0
  54. package/dist/native/bridge/errors.js +66 -151
  55. package/dist/native/bridge/hooks/index.d.ts +13 -0
  56. package/dist/native/bridge/hooks/index.js +0 -6
  57. package/dist/native/bridge/hooks/useAI.d.ts +1 -0
  58. package/dist/native/bridge/hooks/useAI.js +3 -1
  59. package/dist/native/bridge/hooks/useCamera.d.ts +86 -0
  60. package/dist/native/bridge/hooks/useCamera.js +35 -6
  61. package/dist/native/bridge/hooks/useDataProtection.d.ts +17 -0
  62. package/dist/native/bridge/hooks/useDeviceInfo.d.ts +34 -0
  63. package/dist/native/bridge/hooks/useDeviceInfo.js +5 -0
  64. package/dist/native/bridge/hooks/useFilePicker.d.ts +53 -0
  65. package/dist/native/bridge/hooks/useFilePicker.js +13 -0
  66. package/dist/native/bridge/hooks/useGoogleSignIn.d.ts +12 -0
  67. package/dist/native/bridge/hooks/useHapticFeedback.d.ts +76 -0
  68. package/dist/native/bridge/hooks/useHapticFeedback.js +6 -1
  69. package/dist/native/bridge/hooks/useIntent.d.ts +33 -0
  70. package/dist/native/bridge/hooks/useIntent.js +2 -0
  71. package/dist/native/bridge/hooks/useNativeTransition.d.ts +53 -0
  72. package/dist/native/bridge/hooks/useNativeTransition.js +12 -5
  73. package/dist/native/bridge/hooks/useNetworkStatus.d.ts +22 -0
  74. package/dist/native/bridge/hooks/useNetworkStatus.js +7 -0
  75. package/dist/native/bridge/hooks/useNotification.d.ts +83 -0
  76. package/dist/native/bridge/hooks/useNotification.js +81 -34
  77. package/dist/native/bridge/hooks/useSafeArea.d.ts +12 -0
  78. package/dist/native/bridge/hooks/useSafeArea.js +8 -0
  79. package/dist/native/bridge/hooks/useVideoStream.d.ts +67 -0
  80. package/dist/native/bridge/hooks/useVideoStream.js +38 -52
  81. package/dist/native/bridge/hooks/utils.d.ts +4 -0
  82. package/dist/native/bridge/hooks.d.ts +1 -0
  83. package/dist/native/bridge/perf/collectors/ApiCollector.d.ts +30 -0
  84. package/dist/native/bridge/perf/collectors/ApiCollector.js +4 -0
  85. package/dist/native/bridge/perf/collectors/CacheCollector.d.ts +43 -0
  86. package/dist/native/bridge/perf/collectors/CacheCollector.js +4 -0
  87. package/dist/native/bridge/perf/collectors/HookCollector.d.ts +37 -0
  88. package/dist/native/bridge/perf/collectors/HookCollector.js +4 -0
  89. package/dist/native/bridge/perf/collectors/InsightsCollector.d.ts +103 -0
  90. package/dist/native/bridge/perf/collectors/InsightsCollector.js +4 -0
  91. package/dist/native/bridge/perf/collectors/InteractionCollector.d.ts +53 -0
  92. package/dist/native/bridge/perf/collectors/InteractionCollector.js +4 -0
  93. package/dist/native/bridge/perf/collectors/KeyboardCollector.d.ts +30 -0
  94. package/dist/native/bridge/perf/collectors/KeyboardCollector.js +4 -0
  95. package/dist/native/bridge/perf/collectors/NavigationCollector.d.ts +37 -0
  96. package/dist/native/bridge/perf/collectors/NavigationCollector.js +4 -0
  97. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.d.ts +30 -0
  98. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +4 -0
  99. package/dist/native/bridge/perf/collectors/RenderCollector.d.ts +47 -0
  100. package/dist/native/bridge/perf/collectors/RenderCollector.js +8 -0
  101. package/dist/native/bridge/perf/collectors/ScrollCollector.d.ts +29 -0
  102. package/dist/native/bridge/perf/collectors/ScrollCollector.js +4 -0
  103. package/dist/native/bridge/perf/collectors/ThreadCollector.d.ts +31 -0
  104. package/dist/native/bridge/perf/collectors/ThreadCollector.js +4 -0
  105. package/dist/native/bridge/perf/core/constants.d.ts +76 -0
  106. package/dist/native/bridge/perf/core/measure.d.ts +39 -0
  107. package/dist/native/bridge/perf/core/measure.js +4 -0
  108. package/dist/native/bridge/perf/core/session.d.ts +49 -0
  109. package/dist/native/bridge/perf/core/session.js +4 -0
  110. package/dist/native/bridge/perf/core/store.d.ts +37 -0
  111. package/dist/native/bridge/perf/core/store.js +4 -0
  112. package/dist/native/bridge/perf/core/summary.d.ts +60 -0
  113. package/dist/native/bridge/perf/core/traceExport.d.ts +10 -0
  114. package/dist/native/bridge/perf/core/waterfalls.d.ts +2 -0
  115. package/dist/native/bridge/perf/index.d.ts +63 -0
  116. package/dist/native/bridge/perf/index.js +4 -0
  117. package/dist/native/bridge/safeArea.d.ts +11 -0
  118. package/dist/native/bridge/safeArea.js +2 -0
  119. package/dist/native/bridge/useBaseHook.d.ts +137 -0
  120. package/dist/native/bridge/useBaseHook.js +34 -4
  121. package/dist/native/bridge/utils/CameraUtils.d.ts +94 -0
  122. package/dist/native/bridge/utils/CameraUtils.js +4 -0
  123. package/dist/native/bridge/utils/FileObjectConverter.d.ts +36 -0
  124. package/dist/native/bridge/utils/NativeBridge.d.ts +270 -0
  125. package/dist/native/bridge/utils/NativeBridge.js +10 -0
  126. package/dist/native/buildAndroid/index.js +1 -1
  127. package/dist/native/buildAppAndroid.js +9 -0
  128. package/dist/native/buildAppIos.js +16 -1
  129. package/dist/native/buildErrorFormat.js +57 -0
  130. package/dist/native/buildIos/config.js +15 -1
  131. package/dist/native/buildIos/index.js +1 -1
  132. package/dist/native/buildIos/plugins.js +10 -1
  133. package/dist/native/iosnativeWebView/CoreLogic/Package.swift +36 -0
  134. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Camera/VideoStreamState.swift +1 -1
  135. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Camera/VideoStreamStateMachine.swift +6 -4
  136. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/BootTimingUtility.swift +3 -3
  137. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/FrameworkServerUtils.swift +14 -9
  138. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NetworkMonitor.swift +11 -9
  139. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NotificationHandlerProvider.swift +1 -1
  140. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/URLWhitelistManager.swift +7 -7
  141. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/AnyCodableTests.swift +154 -0
  142. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/BootTimingUtilityTests.swift +23 -5
  143. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/CatalystConstantsTests.swift +192 -0
  144. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/ConfigMappingTests.swift +1 -1
  145. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/FrameworkServerUtilsErrorPathTests.swift +106 -0
  146. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/FrameworkServerUtilsLoopbackTests.swift +243 -0
  147. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/FrameworkServerUtilsTests.swift +1 -1
  148. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/NetworkMonitorTests.swift +119 -0
  149. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/NotificationHandlerProviderTests.swift +59 -0
  150. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/NotificationHandlerTests.swift +1 -1
  151. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/URLWhitelistManagerTests.swift +1 -1
  152. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/VideoStreamStateMachineTests.swift +171 -0
  153. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/VideoStreamStateTests.swift +91 -0
  154. package/dist/native/iosnativeWebView/Package.swift +10 -9
  155. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/LocalNotificationHandler.swift +7 -2
  156. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/NotificationManager.swift +1 -2
  157. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/PushNotificationHandler.swift +5 -4
  158. package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraSessionManager.swift +1 -0
  159. package/dist/native/iosnativeWebView/Sources/Core/Camera/HoldController.swift +1 -0
  160. package/dist/native/iosnativeWebView/Sources/Core/Camera/NativeCameraManager.swift +6 -5
  161. package/dist/native/iosnativeWebView/Sources/Core/Camera/ZoomController.swift +1 -0
  162. package/dist/native/iosnativeWebView/Sources/Core/Plugins/PluginBridge.swift +1 -0
  163. package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +31 -30
  164. package/dist/native/iosnativeWebView/Sources/Core/Utils/CameraTransportUtils.swift +2 -1
  165. package/dist/native/iosnativeWebView/Sources/Core/Utils/DeviceInfoUtils.swift +1 -1
  166. package/dist/native/iosnativeWebView/Sources/Core/Utils/FilePickerHandler.swift +22 -19
  167. package/dist/native/iosnativeWebView/Sources/Core/Utils/ImageHandler.swift +40 -39
  168. package/dist/native/iosnativeWebView/Sources/Core/Utils/PerfEventBuffer.swift +1 -0
  169. package/dist/native/iosnativeWebView/Sources/Core/Utils/ResourceURLProtocol.swift +15 -15
  170. package/dist/native/iosnativeWebView/Sources/Core/Utils/WebKitConfig.swift +4 -3
  171. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeCommandHandler.swift +6 -1
  172. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeDelegateHandler.swift +1 -0
  173. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeFileHandler.swift +2 -1
  174. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeJavaScriptInterface.swift +2 -1
  175. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +3 -5
  176. package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +20 -13
  177. package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashView.swift +6 -5
  178. package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashViewModel.swift +14 -13
  179. package/dist/native/iosnativeWebView/Sources/Core/WebView/TransitionManager.swift +3 -3
  180. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +9 -8
  181. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewModel.swift +7 -6
  182. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +24 -25
  183. package/dist/native/iosnativeWebView/iosnativeWebView/AppDelegate.swift +6 -1
  184. package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +1 -0
  185. package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebViewApp.swift +4 -0
  186. package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +2 -22
  187. package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeMessageValidatorTests.swift +1 -0
  188. package/dist/native/iosnativeWebView/iosnativeWebViewTests/CacheManagerTests.swift +1 -0
  189. package/dist/native/iosnativeWebView/iosnativeWebViewTests/FilePickerHandlerTests.swift +2 -1
  190. package/dist/native/iosnativeWebView/iosnativeWebViewTests/NativeBridgeTests.swift +15 -9
  191. package/dist/native/iosnativeWebView/iosnativeWebViewTests/WebViewTests.swift +1 -0
  192. package/dist/native/setupEmulatorIos.js +2 -2
  193. package/dist/native/terminalProgress.js +17 -17
  194. package/dist/router/ClientRouter.d.ts +8 -0
  195. package/dist/router/ClientRouter.js +13 -11
  196. package/dist/router/ServerRouter.d.ts +13 -0
  197. package/dist/router/ServerRouter.js +19 -7
  198. package/dist/scripts/build.js +14 -2
  199. package/dist/scripts/loadEnvironmentVariables.js +3 -2
  200. package/dist/scripts/plugins.js +1 -1
  201. package/dist/scripts/preServerInit.js +49 -3
  202. package/dist/scripts/preflight.js +111 -0
  203. package/dist/scripts/scriptUtils.js +46 -1
  204. package/dist/scripts/serve.js +7 -3
  205. package/dist/scripts/start.js +8 -3
  206. package/dist/server/expressServer.d.ts +1 -0
  207. package/dist/server/expressServer.js +247 -271
  208. package/dist/server/manifestCache.d.ts +2 -0
  209. package/dist/server/manifestCache.js +25 -33
  210. package/dist/server/renderer/ChunkExtractor.d.ts +44 -0
  211. package/dist/server/renderer/ChunkExtractor.js +113 -125
  212. package/dist/server/renderer/document/Body.d.ts +21 -0
  213. package/dist/server/renderer/document/Body.jsx +45 -45
  214. package/dist/server/renderer/document/FastRefresh.d.ts +3 -0
  215. package/dist/server/renderer/document/FastRefresh.jsx +7 -13
  216. package/dist/server/renderer/document/Head.d.ts +20 -0
  217. package/dist/server/renderer/document/Head.jsx +32 -32
  218. package/dist/server/renderer/document/index.d.ts +2 -0
  219. package/dist/server/renderer/document/index.jsx +2 -2
  220. package/dist/server/renderer/extract.d.ts +67 -0
  221. package/dist/server/renderer/extract.js +92 -89
  222. package/dist/server/renderer/handler.d.ts +2 -0
  223. package/dist/server/renderer/handler.jsx +401 -404
  224. package/dist/server/renderer/index.d.ts +2 -0
  225. package/dist/server/renderer/index.js +5 -8
  226. package/dist/server/renderer/render.d.ts +35 -0
  227. package/dist/server/renderer/render.js +25 -32
  228. package/dist/server/utils/botDetectionMiddleware.d.ts +1 -0
  229. package/dist/server/utils/botDetectionMiddleware.js +33 -23
  230. package/dist/server/utils/cjsRequire.d.ts +8 -0
  231. package/dist/server/utils/cjsRequire.js +2 -2
  232. package/dist/server/utils/userAgentUtil.d.ts +7 -0
  233. package/dist/server/utils/userAgentUtil.js +68 -71
  234. package/dist/server/utils/validator.d.ts +31 -0
  235. package/dist/server/utils/validator.js +133 -158
  236. package/dist/vite/inject-cache-key-plugin.js +3 -27
  237. package/dist/vite/moduleAliases.js +36 -0
  238. package/dist/vite/node-loader.mjs +2 -29
  239. package/dist/vite/resolveDevServerConfig.js +4 -1
  240. package/dist/vite/vite.config.js +2 -29
  241. package/dist/web-router/components/MetaTag.d.ts +4 -0
  242. package/dist/web-router/components/MetaTag.jsx +26 -28
  243. package/dist/web-router/components/MetaTag.test.jsx +100 -0
  244. package/dist/web-router/components/RouterDataProvider.d.ts +96 -0
  245. package/dist/web-router/components/RouterDataProvider.jsx +260 -259
  246. package/dist/web-router/components/RouterDataProvider.server.test.jsx +98 -0
  247. package/dist/web-router/components/RouterDataProvider.test.jsx +310 -0
  248. package/dist/web-router/components/Split.d.ts +74 -0
  249. package/dist/web-router/components/Split.jsx +147 -156
  250. package/dist/web-router/components/Split.server.test.jsx +52 -0
  251. package/dist/web-router/components/Split.test.jsx +167 -0
  252. package/dist/web-router/components/SplitInview.d.ts +22 -0
  253. package/dist/web-router/components/SplitInview.jsx +78 -89
  254. package/dist/web-router/components/SplitInview.test.jsx +112 -0
  255. package/dist/web-router/components/SsrRequestContext.d.ts +20 -0
  256. package/dist/web-router/components/SsrRequestContext.jsx +17 -7
  257. package/dist/web-router/components/SsrRequestContext.test.jsx +53 -0
  258. package/dist/web-router/context.d.ts +15 -0
  259. package/dist/web-router/context.jsx +5 -5
  260. package/dist/web-router/context.test.jsx +10 -0
  261. package/dist/web-router/utils/metaDataUtils.d.ts +24 -0
  262. package/dist/web-router/utils/metaDataUtils.jsx +86 -86
  263. package/dist/web-router/utils/metaDataUtils.test.jsx +188 -0
  264. package/dist/web-router/vitest.setup.js +12 -0
  265. package/mcp_v2/knowledge-base.json +115 -17
  266. package/mcp_v2/mcp.js +141 -3
  267. package/mcp_v2/package-lock.json +2031 -0
  268. package/mcp_v2/package.json +13 -2
  269. package/mcp_v2/scripts/copyErrorsIndex.cjs +34 -0
  270. package/mcp_v2/test/errors.test.ts +123 -0
  271. package/mcp_v2/test/github.test.ts +177 -0
  272. package/mcp_v2/tools/errors.d.ts +20 -0
  273. package/mcp_v2/tools/errors.js +63 -0
  274. package/mcp_v2/tools/github.d.ts +41 -0
  275. package/mcp_v2/tools/github.js +231 -0
  276. package/mcp_v2/vitest.config.mjs +22 -0
  277. package/package.json +60 -11
  278. package/test/errors/docsDrift.test.ts +113 -0
  279. package/test/errors/index.test.ts +200 -0
  280. package/test/errors/registry.test.ts +108 -0
  281. package/test/scripts/preflight.test.ts +118 -0
  282. package/test/server/ChunkExtractor.test.ts +97 -0
  283. package/test/server/extract.test.ts +128 -0
  284. package/test/server/fixtures/template/server/document.jsx +19 -0
  285. package/test/server/fixtures/template/server/index.js +10 -0
  286. package/test/server/fixtures/template/src/js/containers/App/index.jsx +33 -0
  287. package/test/server/fixtures/template/src/js/routes/index.jsx +72 -0
  288. package/test/server/fixtures/template/src/js/routes/utils.jsx +39 -0
  289. package/test/server/fixtures/template/src/js/store/index.js +19 -0
  290. package/test/server/handler.assets.test.ts +330 -0
  291. package/test/server/handler.test.ts +208 -0
  292. package/test/server/manifestCache.test.ts +71 -0
  293. package/test/server/render.test.ts +71 -0
  294. package/test/server/scriptUtils.test.ts +103 -0
  295. package/test/server/userAgentUtil.test.ts +48 -0
  296. package/test/server/validator.test.ts +225 -0
  297. package/test/vite/resolveDevServerConfig.test.js +4 -1
  298. package/tsconfig.test.json +23 -0
  299. package/vitest.config.ts +93 -0
  300. package/dist/caching.js +0 -1
  301. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/DeviceInfoUtilsTest.kt +0 -187
  302. package/dist/native/bridge/standardizedHooks.js +0 -611
  303. package/dist/router.js +0 -1
  304. package/dist/scripts/validator.js +0 -149
  305. package/dist/web-router/hooks.jsx +0 -72
  306. package/dist/web-router/types/components/MetaTag.d.ts +0 -2
  307. package/dist/web-router/types/components/RouterDataProvider.d.ts +0 -95
  308. package/dist/web-router/types/context.d.ts +0 -14
  309. package/dist/web-router/types/hooks.d.ts +0 -1
  310. package/dist/web-router/types/index.d.ts +0 -3
  311. package/dist/web-router/types/utils/metaDataUtils.d.ts +0 -40
  312. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Constants/CatalystConstants.swift +0 -0
  313. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Constants/ConfigConstants.swift +0 -0
  314. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Notifications/NotificationModels.swift +0 -0
  315. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Protocols/NotificationHandlerProtocol.swift +0 -0
  316. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NullNotificationHandler.swift +0 -0
@@ -0,0 +1,200 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import {
3
+ ERROR_CODES,
4
+ CatalystError,
5
+ createError,
6
+ wrapForeignError,
7
+ wrapSSRError,
8
+ wrapProviderError,
9
+ formatError,
10
+ } from "../../src/errors/index.js"
11
+
12
+ // Framework-level (Tier 1) behavioral contract tests for errors/index.js —
13
+ // the "external contributor can't quietly change core behavior" lock. These
14
+ // fail the moment formatting/wrapping semantics drift from what's documented
15
+ // here, independent of the registry's actual content (see registry.test.js
16
+ // for that).
17
+
18
+ describe("createError()", () => {
19
+ it("uses registry defaults when no overrides are given", () => {
20
+ const err = createError(ERROR_CODES.PREFLIGHT_CONFIG_MISSING)
21
+ expect(err).toBeInstanceOf(CatalystError)
22
+ expect(err.code).toBe(ERROR_CODES.PREFLIGHT_CONFIG_MISSING)
23
+ expect(err.category).toBe("PREFLIGHT")
24
+ expect(err.message).toBe("config not found in config folder")
25
+ expect(err.details).toBe("A config object must be exported from the config folder in your project root.")
26
+ expect(err.suggestedAction).toContain("config/config.json")
27
+ expect(err.docUrl).toBe("https://github.com/tata1mg/catalyst-core/blob/main/errors/PREFLIGHT/PREFLIGHT-001.md")
28
+ expect(err.cause).toBeUndefined()
29
+ })
30
+
31
+ it("overrides message/details/suggestedAction when provided", () => {
32
+ const err = createError(ERROR_CODES.PREFLIGHT_CONFIG_KEY_MISSING, {
33
+ details: "NODE_SERVER_PORT key not found inside config.json",
34
+ })
35
+ expect(err.details).toBe("NODE_SERVER_PORT key not found inside config.json")
36
+ // message/suggestedAction fall back to the registry default when not overridden
37
+ expect(err.message).toBe("required key missing inside config.json")
38
+ })
39
+
40
+ it("attaches cause only when explicitly provided (not even as undefined-valued own property)", () => {
41
+ const withoutCause = createError(ERROR_CODES.PREFLIGHT_CONFIG_MISSING)
42
+ expect("cause" in withoutCause).toBe(false)
43
+
44
+ const original = new Error("boom")
45
+ const withCause = createError(ERROR_CODES.PREFLIGHT_CONFIG_MISSING, { cause: original })
46
+ expect(withCause.cause).toBe(original)
47
+ })
48
+
49
+ it("falls back to the UNKNOWN-category generic definition for an unrecognized code", () => {
50
+ const err = createError("NOT-A-REAL-CODE")
51
+ expect(err.category).toBe("UNKNOWN")
52
+ expect(err.code).toBe("NOT-A-REAL-CODE")
53
+ })
54
+
55
+ it("sets name to CatalystError and is a real Error instance", () => {
56
+ const err = createError(ERROR_CODES.PREFLIGHT_CONFIG_MISSING)
57
+ expect(err.name).toBe("CatalystError")
58
+ expect(err).toBeInstanceOf(Error)
59
+ })
60
+ })
61
+
62
+ describe("wrapForeignError()", () => {
63
+ it("maps BUNDLE/IOS/ANDROID stages to their per-stage wrapper code", () => {
64
+ expect(wrapForeignError("BUNDLE", new Error()).code).toBe(ERROR_CODES.BUNDLE_UPSTREAM_ERROR)
65
+ expect(wrapForeignError("IOS", new Error()).code).toBe(ERROR_CODES.IOS_UPSTREAM_ERROR)
66
+ expect(wrapForeignError("ANDROID", new Error()).code).toBe(ERROR_CODES.ANDROID_UPSTREAM_ERROR)
67
+ })
68
+
69
+ it("throws on an unknown stage rather than silently producing a bad code", () => {
70
+ expect(() => wrapForeignError("NOT_A_STAGE", new Error())).toThrow(/unknown stage/)
71
+ })
72
+
73
+ it("preserves the original error as cause, never reinterpreting its message", () => {
74
+ const original = new Error("EACCES: permission denied")
75
+ const wrapped = wrapForeignError("BUNDLE", original)
76
+ expect(wrapped.cause).toBe(original)
77
+ expect(wrapped.message).not.toBe(original.message)
78
+ })
79
+
80
+ it("shows the upstream named error code when present (e.g. Rollup's PLUGIN_LOAD_ERROR)", () => {
81
+ // .code isn't part of the standard Error interface — it's a Node
82
+ // convention (see NodeJS.ErrnoException) that upstream build tools
83
+ // like Rollup also follow for their own error codes.
84
+ const original: NodeJS.ErrnoException = new Error("failed to load")
85
+ original.code = "PLUGIN_LOAD_ERROR"
86
+ const wrapped = wrapForeignError("BUNDLE", original)
87
+ expect(wrapped.message).toContain("PLUGIN_LOAD_ERROR")
88
+ })
89
+
90
+ it("does NOT show a bare numeric process exit code as if it were a named upstream code", () => {
91
+ const original = new Error("process exited") as Error & { code: number }
92
+ original.code = 1 // numeric, e.g. a spawned child's exit status — not a named identifier
93
+ const wrapped = wrapForeignError("BUNDLE", original)
94
+ expect(wrapped.message).not.toContain("1")
95
+ expect(wrapped.message).toBe("Build failed (upstream: Vite/Rollup)")
96
+ })
97
+
98
+ it("handles an error with no .code at all", () => {
99
+ const wrapped = wrapForeignError("IOS", new Error("generic failure"))
100
+ expect(wrapped.message).toBe("Build failed (upstream: Xcode/CocoaPods)")
101
+ })
102
+ })
103
+
104
+ describe("wrapSSRError()", () => {
105
+ it("maps each SSR stage to its RUNTIME-WEB code", () => {
106
+ expect(wrapSSRError("RENDER", new Error()).code).toBe(ERROR_CODES.RUNTIME_WEB_RENDER_FAILED)
107
+ expect(wrapSSRError("FETCHER", new Error()).code).toBe(ERROR_CODES.RUNTIME_WEB_FETCHER_FAILED)
108
+ expect(wrapSSRError("SERVER_SIDE_FUNCTION", new Error()).code).toBe(ERROR_CODES.RUNTIME_WEB_SERVER_SIDE_FUNCTION_FAILED)
109
+ expect(wrapSSRError("REQUEST_HANDLING", new Error()).code).toBe(ERROR_CODES.RUNTIME_WEB_REQUEST_HANDLING_FAILED)
110
+ })
111
+
112
+ it("throws on an unknown stage rather than silently producing a bad code", () => {
113
+ expect(() => wrapSSRError("NOT_A_STAGE", new Error())).toThrow(/unknown stage/)
114
+ })
115
+
116
+ it("preserves the original error verbatim as cause — never reinterprets it (could be app code, a dep, or catalyst-core itself)", () => {
117
+ const original = new TypeError("Cannot read properties of undefined")
118
+ const wrapped = wrapSSRError("FETCHER", original)
119
+ expect(wrapped.cause).toBe(original)
120
+ })
121
+ })
122
+
123
+ describe("wrapProviderError()", () => {
124
+ it("constructs a cause carrying provider/status, and includes both in the message", () => {
125
+ const wrapped = wrapProviderError("openai", 429, '{"error":"rate limited"}')
126
+ expect(wrapped.code).toBe(ERROR_CODES.AI_UPSTREAM_ERROR)
127
+ expect(wrapped.cause.provider).toBe("openai")
128
+ expect(wrapped.cause.status).toBe(429)
129
+ expect(wrapped.cause.message).toContain("rate limited")
130
+ expect(wrapped.message).toContain("openai")
131
+ expect(wrapped.message).toContain("429")
132
+ })
133
+ })
134
+
135
+ describe("formatError()", () => {
136
+ const err = createError(ERROR_CODES.PREFLIGHT_CONFIG_MISSING)
137
+ const errWithCause = createError(ERROR_CODES.PREFLIGHT_CONFIG_MISSING, { cause: new Error("ENOENT: no such file") })
138
+
139
+ it("defaults to \"default\" mode when no mode is passed (WebBridge.js's browser call sites rely on this)", () => {
140
+ expect(formatError(err)).toBe(formatError(err, "default"))
141
+ })
142
+
143
+ it("default mode: single-line-per-field, shows cause message when present", () => {
144
+ const output = formatError(errWithCause, "default")
145
+ expect(output).toContain(`[${ERROR_CODES.PREFLIGHT_CONFIG_MISSING}]`)
146
+ expect(output).toContain("ENOENT: no such file")
147
+ expect(output).toContain("Suggested action:")
148
+ expect(output).toContain("Docs:")
149
+ })
150
+
151
+ it("default mode: falls back to details when there is no cause", () => {
152
+ const output = formatError(err, "default")
153
+ expect(output).toContain(err.details)
154
+ })
155
+
156
+ it("verbose mode: includes code/category/problem/solution/docs, boxed", () => {
157
+ const output = formatError(errWithCause, "verbose")
158
+ expect(output).toContain(`Code: ${ERROR_CODES.PREFLIGHT_CONFIG_MISSING}`)
159
+ expect(output).toContain("Category: PREFLIGHT")
160
+ expect(output).toContain("Problem: ENOENT: no such file")
161
+ expect(output).toContain("Solution:")
162
+ expect(output).toContain("Docs:")
163
+ expect(output).toContain("┌")
164
+ expect(output).toContain("└")
165
+ })
166
+
167
+ it("verbose mode: category falls back to UNKNOWN when the error has none", () => {
168
+ const bareErr = new CatalystError("SOME-CODE", { message: "x" })
169
+ const output = formatError(bareErr, "verbose")
170
+ expect(output).toContain("Category: UNKNOWN")
171
+ })
172
+
173
+ it("debug mode: includes the full cause chain and a stack trace", () => {
174
+ const inner = new Error("root cause")
175
+ const middle = new Error("middle layer")
176
+ middle.cause = inner
177
+ const wrapped = createError(ERROR_CODES.PREFLIGHT_CONFIG_MISSING, { cause: middle })
178
+
179
+ const output = formatError(wrapped, "debug", { node: "v20.4.0", platform: "darwin" })
180
+ expect(output).toContain("Caused by: middle layer")
181
+ expect(output).toContain(" Caused by: root cause")
182
+ expect(output).toContain("Environment:")
183
+ expect(output).toContain("node: v20.4.0")
184
+ expect(output).toContain("Stack trace:")
185
+ })
186
+
187
+ it("debug mode: omits the Environment section when no env is passed", () => {
188
+ const output = formatError(err, "debug")
189
+ expect(output).not.toContain("Environment:")
190
+ })
191
+
192
+ it("debug mode: uses the cause's stack when a cause is present, not the wrapper's own stack", () => {
193
+ const original = new Error("original failure")
194
+ const wrapped = createError(ERROR_CODES.PREFLIGHT_CONFIG_MISSING, { cause: original })
195
+ const output = formatError(wrapped, "debug")
196
+ // The wrapper error's own stack frame (this test file) would appear either way,
197
+ // but the cause's message must be the one whose stack header shows up.
198
+ expect(output).toContain("original failure")
199
+ })
200
+ })
@@ -0,0 +1,108 @@
1
+ import { describe, it, expect } from "vitest"
2
+ import { ERROR_CODES, ERROR_DEFINITIONS, getDefinition, getDocUrl } from "../../src/errors/registry.js"
3
+
4
+ // Framework-level (Tier 1) contract tests for the error registry itself —
5
+ // not testing any consumer of it. These exist so a malformed new error code
6
+ // fails CI with no human review needed (see issue #340/#411).
7
+
8
+ const CODE_FORMAT_RE = /^[A-Z][A-Z-]*-\d{3}$/
9
+
10
+ describe("ERROR_CODES / ERROR_DEFINITIONS shape invariants", () => {
11
+ const codeEntries = Object.entries(ERROR_CODES)
12
+ const codeValues = Object.values(ERROR_CODES)
13
+
14
+ it("has at least one code defined", () => {
15
+ expect(codeEntries.length).toBeGreaterThan(0)
16
+ })
17
+
18
+ it("every ERROR_CODES value has a matching ERROR_DEFINITIONS entry", () => {
19
+ const missing = codeValues.filter((code) => !(code in ERROR_DEFINITIONS))
20
+ expect(missing).toEqual([])
21
+ })
22
+
23
+ it("every ERROR_DEFINITIONS key corresponds to a declared ERROR_CODES value", () => {
24
+ const definedCodes = Object.keys(ERROR_DEFINITIONS)
25
+ const orphaned = definedCodes.filter((code) => !codeValues.includes(code))
26
+ expect(orphaned).toEqual([])
27
+ })
28
+
29
+ it("code values are unique — no two ERROR_CODES keys share a code string", () => {
30
+ const seen = new Set()
31
+ const duplicates = []
32
+ for (const code of codeValues) {
33
+ if (seen.has(code)) duplicates.push(code)
34
+ seen.add(code)
35
+ }
36
+ expect(duplicates).toEqual([])
37
+ })
38
+
39
+ it("every code matches the CATEGORY-NNN format", () => {
40
+ const malformed = codeValues.filter((code) => !CODE_FORMAT_RE.test(code))
41
+ expect(malformed).toEqual([])
42
+ })
43
+
44
+ it("every code's category prefix matches its definition's category field", () => {
45
+ const mismatches = []
46
+ for (const [key, code] of codeEntries) {
47
+ const def = ERROR_DEFINITIONS[code]
48
+ if (!def) continue // covered by the "matching entry" test above
49
+ const prefix = code.slice(0, code.lastIndexOf("-"))
50
+ if (prefix !== def.category) {
51
+ mismatches.push(`${key} (${code}): prefix "${prefix}" !== category "${def.category}"`)
52
+ }
53
+ }
54
+ expect(mismatches).toEqual([])
55
+ })
56
+
57
+ it("every definition has all required fields, non-empty", () => {
58
+ const REQUIRED_FIELDS = ["category", "defaultMessage", "defaultDetails", "suggestedAction"] as const
59
+ const problems: string[] = []
60
+ for (const [code, def] of Object.entries(ERROR_DEFINITIONS)) {
61
+ for (const field of REQUIRED_FIELDS) {
62
+ const value = def[field]
63
+ if (typeof value !== "string" || value.trim().length === 0) {
64
+ problems.push(`${code}: missing or empty "${field}"`)
65
+ }
66
+ }
67
+ }
68
+ expect(problems).toEqual([])
69
+ })
70
+
71
+ it("no definition carries a `recoverable` field (dropped per 18530a89 — never reinstate)", () => {
72
+ const withRecoverable = Object.entries(ERROR_DEFINITIONS)
73
+ .filter(([, def]) => "recoverable" in def)
74
+ .map(([code]) => code)
75
+ expect(withRecoverable).toEqual([])
76
+ })
77
+ })
78
+
79
+ describe("getDefinition()", () => {
80
+ it("returns the real definition for a known code", () => {
81
+ const def = getDefinition(ERROR_CODES.PREFLIGHT_CONFIG_MISSING)
82
+ expect(def.category).toBe("PREFLIGHT")
83
+ expect(def.defaultMessage).toBe("config not found in config folder")
84
+ })
85
+
86
+ it("falls back to a generic definition for an unrecognized code", () => {
87
+ const def = getDefinition("NOT-A-REAL-CODE")
88
+ expect(def).toBeDefined()
89
+ expect(def.defaultMessage).toBeTruthy()
90
+ expect(def.suggestedAction).toBeTruthy()
91
+ })
92
+
93
+ it("never mislabels the fallback as RUNTIME-NATIVE — category is always UNKNOWN", () => {
94
+ const def = getDefinition("NOT-A-REAL-CODE")
95
+ expect(def.category).toBe("UNKNOWN")
96
+ })
97
+ })
98
+
99
+ describe("getDocUrl()", () => {
100
+ it("builds a repo-blob URL under the code's own category for a known code", () => {
101
+ const url = getDocUrl(ERROR_CODES.AI_DISABLED)
102
+ expect(url).toBe("https://github.com/tata1mg/catalyst-core/blob/main/errors/AI/AI-001.md")
103
+ })
104
+
105
+ it("returns null for an unrecognized code rather than a broken URL", () => {
106
+ expect(getDocUrl("NOT-A-REAL-CODE")).toBeNull()
107
+ })
108
+ })
@@ -0,0 +1,118 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest"
2
+ import { mkdtempSync, rmSync, mkdirSync, writeFileSync } from "fs"
3
+ import { tmpdir } from "os"
4
+ import path from "path"
5
+ import { runStaticPreflight } from "../../src/scripts/preflight.js"
6
+ import { ERROR_CODES } from "../../src/errors/registry.js"
7
+
8
+ // runStaticPreflight(appDir) reads config/config.json + package.json from a
9
+ // real directory and returns an array of CatalystError for every failure it
10
+ // finds (empty = the app would pass). These tests build throwaway app dirs
11
+ // and assert the codes.
12
+
13
+ const VALID_CONFIG = {
14
+ NODE_SERVER_HOSTNAME: "localhost",
15
+ NODE_SERVER_PORT: 3000,
16
+ BUILD_OUTPUT_PATH: "build",
17
+ PUBLIC_STATIC_ASSET_PATH: "/assets/",
18
+ PUBLIC_STATIC_ASSET_URL: "http://localhost:3000",
19
+ CLIENT_ENV_VARIABLES: [],
20
+ }
21
+ const VALID_PKG = {
22
+ name: "fixture-app",
23
+ _moduleAliases: {
24
+ "@api": "api.js",
25
+ "@containers": "src/js/containers",
26
+ "@server": "server",
27
+ "@config": "config",
28
+ "@css": "src/static/css",
29
+ "@routes": "src/js/routes/",
30
+ },
31
+ }
32
+
33
+ let appDir: string
34
+
35
+ function writeApp({ config, pkg }: { config?: unknown; pkg?: unknown }) {
36
+ if (config !== undefined) {
37
+ mkdirSync(path.join(appDir, "config"), { recursive: true })
38
+ writeFileSync(
39
+ path.join(appDir, "config", "config.json"),
40
+ typeof config === "string" ? config : JSON.stringify(config)
41
+ )
42
+ }
43
+ if (pkg !== undefined) {
44
+ writeFileSync(
45
+ path.join(appDir, "package.json"),
46
+ typeof pkg === "string" ? pkg : JSON.stringify(pkg)
47
+ )
48
+ }
49
+ }
50
+
51
+ const codes = (dir: string) => runStaticPreflight(dir).map((e) => e.code)
52
+
53
+ beforeEach(() => {
54
+ appDir = mkdtempSync(path.join(tmpdir(), "preflight-test-"))
55
+ })
56
+ afterEach(() => {
57
+ rmSync(appDir, { recursive: true, force: true })
58
+ })
59
+
60
+ describe("runStaticPreflight", () => {
61
+ it("returns [] for a fully valid app", () => {
62
+ writeApp({ config: VALID_CONFIG, pkg: VALID_PKG })
63
+ expect(runStaticPreflight(appDir)).toEqual([])
64
+ })
65
+
66
+ it("PREFLIGHT-001 when config/config.json is absent", () => {
67
+ writeApp({ pkg: VALID_PKG })
68
+ expect(codes(appDir)).toContain(ERROR_CODES.PREFLIGHT_CONFIG_MISSING)
69
+ })
70
+
71
+ it("PREFLIGHT-001 when config/config.json is malformed JSON", () => {
72
+ writeApp({ config: "{ not json", pkg: VALID_PKG })
73
+ expect(codes(appDir)).toContain(ERROR_CODES.PREFLIGHT_CONFIG_MISSING)
74
+ })
75
+
76
+ it("PREFLIGHT-003 when a required config key is missing", () => {
77
+ const { NODE_SERVER_PORT: _omit, ...partial } = VALID_CONFIG
78
+ writeApp({ config: partial, pkg: VALID_PKG })
79
+ expect(codes(appDir)).toContain(ERROR_CODES.PREFLIGHT_CONFIG_KEY_MISSING)
80
+ })
81
+
82
+ it("PREFLIGHT-004 when package.json is absent", () => {
83
+ writeApp({ config: VALID_CONFIG })
84
+ expect(codes(appDir)).toContain(ERROR_CODES.PREFLIGHT_PACKAGE_JSON_MISSING)
85
+ })
86
+
87
+ it("PREFLIGHT-005 when package.json is malformed JSON", () => {
88
+ writeApp({ config: VALID_CONFIG, pkg: "{ nope" })
89
+ expect(codes(appDir)).toContain(ERROR_CODES.PREFLIGHT_PACKAGE_JSON_INVALID)
90
+ })
91
+
92
+ it("PREFLIGHT-006 when _moduleAliases is absent", () => {
93
+ writeApp({ config: VALID_CONFIG, pkg: { name: "x" } })
94
+ expect(codes(appDir)).toContain(ERROR_CODES.PREFLIGHT_MODULE_ALIAS_MISSING)
95
+ })
96
+
97
+ it("PREFLIGHT-009 when a required alias is missing", () => {
98
+ const { "@containers": _drop, ...aliases } = VALID_PKG._moduleAliases
99
+ writeApp({ config: VALID_CONFIG, pkg: { name: "x", _moduleAliases: aliases } })
100
+ expect(codes(appDir)).toContain(ERROR_CODES.PREFLIGHT_MODULE_ALIAS_KEY_MISSING)
101
+ })
102
+
103
+ it("collects MULTIPLE failures in one pass (config + package.json both broken)", () => {
104
+ writeApp({}) // neither file written
105
+ const found = codes(appDir)
106
+ expect(found).toContain(ERROR_CODES.PREFLIGHT_CONFIG_MISSING)
107
+ expect(found).toContain(ERROR_CODES.PREFLIGHT_PACKAGE_JSON_MISSING)
108
+ expect(found.length).toBeGreaterThanOrEqual(2)
109
+ })
110
+
111
+ it("every returned item is a CatalystError with a docUrl", () => {
112
+ writeApp({})
113
+ for (const err of runStaticPreflight(appDir)) {
114
+ expect(err.name).toBe("CatalystError")
115
+ expect(err.docUrl).toMatch(/\/errors\/PREFLIGHT\/PREFLIGHT-\d{3}\.md$/)
116
+ }
117
+ })
118
+ })
@@ -0,0 +1,97 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest"
2
+ import { ChunkExtractor } from "../../src/server/renderer/ChunkExtractor.js"
3
+
4
+ // Asset bucketing used by handler.jsx's collectAssets / _renderMarkUp
5
+ // (#348 coverage). Critical = <head>, deferred = after </body>.
6
+
7
+ let savedUrl: string | undefined
8
+ let savedPath: string | undefined
9
+ beforeEach(() => {
10
+ savedUrl = process.env.PUBLIC_STATIC_ASSET_URL
11
+ savedPath = process.env.PUBLIC_STATIC_ASSET_PATH
12
+ process.env.PUBLIC_STATIC_ASSET_URL = "https://cdn.example.com"
13
+ process.env.PUBLIC_STATIC_ASSET_PATH = "/static/"
14
+ })
15
+ afterEach(() => {
16
+ if (savedUrl === undefined) delete process.env.PUBLIC_STATIC_ASSET_URL
17
+ else process.env.PUBLIC_STATIC_ASSET_URL = savedUrl
18
+ if (savedPath === undefined) delete process.env.PUBLIC_STATIC_ASSET_PATH
19
+ else process.env.PUBLIC_STATIC_ASSET_PATH = savedPath
20
+ })
21
+
22
+ describe("ChunkExtractor", () => {
23
+ it("constructs with empty manifests and yields empty buckets", () => {
24
+ const ce = new ChunkExtractor()
25
+ expect(ce.getCriticalAssets()).toEqual({ js: [], css: [] })
26
+ expect(ce.getDeferredAssets()).toEqual({ js: [], css: [] })
27
+ expect(ce.getRenderedComponentKeys()).toEqual([])
28
+ })
29
+
30
+ it("loads essential entrypoints from assetManifest into the critical bucket as public URLs", () => {
31
+ const ce = new ChunkExtractor({
32
+ assetManifest: {
33
+ essential: {
34
+ entry: { file: "assets/entry-abc.js", css: ["assets/entry-abc.css"] },
35
+ },
36
+ },
37
+ })
38
+ const critical = ce.getCriticalAssets()
39
+ expect(critical.js).toEqual(["https://cdn.example.com/static/assets/entry-abc.js"])
40
+ // CSS stays a relative path (read from disk later, not linked)
41
+ expect(critical.css).toEqual(["assets/entry-abc.css"])
42
+ })
43
+
44
+ it("preloadRouteCss adds a matched route component's chunk to the critical bucket", () => {
45
+ const ce = new ChunkExtractor({
46
+ manifest: { "pages/Home": { file: "assets/home-xyz.js", css: ["assets/home-xyz.css"] } },
47
+ })
48
+ ce.preloadRouteCss([
49
+ { route: { component: { __cacheKey: "pages/Home" } } },
50
+ { route: {} }, // no component -> skipped, no throw
51
+ null, // -> skipped
52
+ ])
53
+ const critical = ce.getCriticalAssets()
54
+ expect(critical.js).toEqual(["https://cdn.example.com/static/assets/home-xyz.js"])
55
+ expect(critical.css).toEqual(["assets/home-xyz.css"])
56
+ })
57
+
58
+ it("addComponent puts discovered chunks in the deferred bucket and tracks the key", () => {
59
+ const ce = new ChunkExtractor({
60
+ manifest: { "widgets/Cart": { file: "assets/cart-123.js", css: ["assets/cart-123.css"] } },
61
+ })
62
+ ce.addComponent("widgets/Cart")
63
+ expect(ce.getRenderedComponentKeys()).toEqual(["widgets/Cart"])
64
+ expect(ce.getDeferredAssets().js).toEqual(["https://cdn.example.com/static/assets/cart-123.js"])
65
+ })
66
+
67
+ it("addComponent falls back to a prefix (hashed) manifest key when the raw key is absent", () => {
68
+ const ce = new ChunkExtractor({
69
+ manifest: {
70
+ "widgets/Modal.a1b2c3.js": { file: "assets/modal-a1b2c3.js", css: ["assets/modal.css"] },
71
+ },
72
+ })
73
+ // raw "widgets/Modal.a1b2c3.js" isn't a key; "widgets/Modal" +
74
+ // "." prefix-matches it.
75
+ ce.addComponent("widgets/Modal")
76
+ expect(ce.getDeferredAssets().js).toEqual([
77
+ "https://cdn.example.com/static/assets/modal-a1b2c3.js",
78
+ ])
79
+ })
80
+
81
+ it("addComponent is a no-op (but still tracks the key) when no manifest entry matches at all", () => {
82
+ const ce = new ChunkExtractor({ manifest: {} })
83
+ ce.addComponent("widgets/Unknown")
84
+ expect(ce.getRenderedComponentKeys()).toEqual(["widgets/Unknown"])
85
+ expect(ce.getDeferredAssets()).toEqual({ js: [], css: [] })
86
+ })
87
+
88
+ it("does not double-count a JS URL already tracked as critical", () => {
89
+ const ce = new ChunkExtractor({
90
+ assetManifest: { essential: { e: { file: "assets/shared.js" } } },
91
+ manifest: { "widgets/Shared": { file: "assets/shared.js" } },
92
+ })
93
+ ce.addComponent("widgets/Shared")
94
+ expect(ce.getDeferredAssets().js).toEqual([])
95
+ expect(ce.getCriticalAssets().js).toEqual(["https://cdn.example.com/static/assets/shared.js"])
96
+ })
97
+ })
@@ -0,0 +1,128 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest"
2
+ import {
3
+ generateCssLinkStrings,
4
+ generateModulePreloadLinkElements,
5
+ generateScriptElements,
6
+ generateScriptStrings,
7
+ getCachedDeferredCssPathsForRoute,
8
+ getDeferredPreloadScriptUrls,
9
+ getDeferredRouteKey,
10
+ readCssFromDisk,
11
+ registerDeferredAssetsForRoute,
12
+ } from "../../src/server/renderer/extract.js"
13
+
14
+
15
+ // Pure asset-string / deferred-route-registry helpers pulled in by
16
+ // handler.jsx and _renderMarkUp (#348 coverage). The registry lives on
17
+ // process.deferredAssetsByRoute (module-level Map) — cleared between
18
+ // tests so cases don't leak.
19
+
20
+ beforeEach(() => {
21
+ (process as any).deferredAssetsByRoute = new Map()
22
+ })
23
+ afterEach(() => {
24
+ (process as any).deferredAssetsByRoute = new Map()
25
+ })
26
+
27
+ describe("getDeferredRouteKey", () => {
28
+ it("returns the last matched route's path", () => {
29
+ const matches = [{ route: { path: "/" } }, { route: { path: "/product/:id" } }]
30
+ expect(getDeferredRouteKey({}, matches)).toBe("/product/:id")
31
+ })
32
+ it("returns null for no matches", () => {
33
+ expect(getDeferredRouteKey({}, [])).toBeNull()
34
+ expect(getDeferredRouteKey({})).toBeNull()
35
+ })
36
+ })
37
+
38
+ describe("registerDeferredAssetsForRoute / getCachedDeferredCssPathsForRoute", () => {
39
+ it("records new CSS paths once and reports only the not-yet-seen ones", () => {
40
+ const first = registerDeferredAssetsForRoute("/r", { css: ["a.css", "b.css"] } as any)
41
+ expect(first.newCssPaths).toEqual(["a.css", "b.css"])
42
+
43
+ const second = registerDeferredAssetsForRoute("/r", { css: ["b.css", "c.css"] } as any)
44
+ expect(second.newCssPaths).toEqual(["c.css"])
45
+
46
+ expect(getCachedDeferredCssPathsForRoute("/r").sort()).toEqual(["a.css", "b.css", "c.css"])
47
+ })
48
+
49
+ it("skips JS URLs for bot requests but still records CSS", () => {
50
+ registerDeferredAssetsForRoute("/r", { css: ["x.css"], js: ["x.js"] } as any, true)
51
+ expect(getCachedDeferredCssPathsForRoute("/r")).toEqual(["x.css"])
52
+ expect(getDeferredPreloadScriptUrls("/r")).toEqual([])
53
+ })
54
+
55
+ it("records JS URLs for non-bot requests", () => {
56
+ registerDeferredAssetsForRoute("/r", { js: ["a.js", "b.js"] } as any, false)
57
+ expect(getDeferredPreloadScriptUrls("/r").sort()).toEqual(["a.js", "b.js"])
58
+ })
59
+
60
+ it("no-ops for a falsy routeKey", () => {
61
+ expect(registerDeferredAssetsForRoute(null, { css: ["a.css"] } as any)).toEqual({ newCssPaths: [] })
62
+ expect(getCachedDeferredCssPathsForRoute(null)).toEqual([])
63
+ })
64
+
65
+ it("skips falsy entries in the css list", () => {
66
+ const { newCssPaths } = registerDeferredAssetsForRoute("/r", {
67
+ css: ["a.css", "", null, "b.css"],
68
+ } as any)
69
+ expect(newCssPaths).toEqual(["a.css", "b.css"])
70
+ })
71
+ })
72
+
73
+ describe("getDeferredPreloadScriptUrls", () => {
74
+ it("excludes URLs already loaded as critical scripts", () => {
75
+ registerDeferredAssetsForRoute("/r", { js: ["keep.js", "crit.js"] } as any, false)
76
+ expect(getDeferredPreloadScriptUrls("/r", ["crit.js"])).toEqual(["keep.js"])
77
+ })
78
+ it("returns [] when the route has no record", () => {
79
+ expect(getDeferredPreloadScriptUrls("/never")).toEqual([])
80
+ })
81
+ })
82
+
83
+ describe("HTML / React element generators", () => {
84
+ it("generateScriptElements dedupes and emits module <script> elements", () => {
85
+ const els = generateScriptElements(["a.js", "a.js", "b.js"])
86
+ expect(els).toHaveLength(2)
87
+ expect(els[0].props).toMatchObject({ type: "module", src: "a.js" })
88
+ })
89
+
90
+ it("generateModulePreloadLinkElements dedupes and honors the key prefix", () => {
91
+ const els = generateModulePreloadLinkElements(["a.js", "a.js"], "crit")
92
+ expect(els).toHaveLength(1)
93
+ expect(els[0].key).toBe("crit-0")
94
+ expect(els[0].props).toMatchObject({ rel: "modulepreload", href: "a.js" })
95
+ })
96
+
97
+ it("generateCssLinkStrings builds deduped <link rel=stylesheet> strings", () => {
98
+ expect(generateCssLinkStrings(["a.css", "a.css", "b.css"])).toBe(
99
+ '<link rel="stylesheet" href="a.css"><link rel="stylesheet" href="b.css">',
100
+ )
101
+ })
102
+
103
+ it("generateScriptStrings builds deduped module <script> strings", () => {
104
+ expect(generateScriptStrings(["a.js", "a.js"])).toBe(
105
+ '<script type="module" src="a.js"></script>',
106
+ )
107
+ })
108
+ })
109
+
110
+ describe("readCssFromDisk", () => {
111
+ it("returns an empty string when given no paths", () => {
112
+ expect(readCssFromDisk([], "/whatever")).toBe("")
113
+ })
114
+
115
+ it("returns an empty string (not a throw) when the CSS files do not exist on disk", () => {
116
+ const out = readCssFromDisk(["does-not-exist.css"], "/tmp/nonexistent-build-dir")
117
+ expect(out).toBe("")
118
+ })
119
+
120
+ it("skips http(s)-prefixed and duplicate asset entries", () => {
121
+ // http-prefixed -> not read from disk; duplicate -> read once.
122
+ const out = readCssFromDisk(
123
+ ["https://cdn.example.com/x.css", "dup.css", "dup.css", ""],
124
+ "/tmp/nonexistent-build-dir",
125
+ )
126
+ expect(out).toBe("")
127
+ })
128
+ })
@@ -0,0 +1,19 @@
1
+ import React from "react"
2
+
3
+ // Minimal CustomDocument for the SSR handler fixture (#348). Mirrors the
4
+ // shape a scaffolded app's server/document exports: a function taking the
5
+ // merged render props and returning the full <html> tree. Renders just
6
+ // enough (the app div via props.jsx, a marker for assertions) to let
7
+ // renderToPipeableStream produce a stream.
8
+ export default function CustomDocument(props) {
9
+ return (
10
+ <html lang={props.lang || "en"}>
11
+ <head>
12
+ <title>fixture</title>
13
+ </head>
14
+ <body>
15
+ <div data-testid="doc-root">{props.jsx}</div>
16
+ </body>
17
+ </html>
18
+ )
19
+ }