catalyst-core 0.1.0-canary.9 → 0.2.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/README.md +1 -1
  2. package/bin/catalyst.js +8 -1
  3. package/changelog.md +6 -0
  4. package/dist/native/androidProject/app/build.gradle.kts +34 -8
  5. package/dist/native/androidProject/app/src/main/AndroidManifest.xml +2 -1
  6. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/BridgeMessageValidator.kt +1 -1
  7. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +270 -78
  8. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +34 -5
  9. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +149 -1
  10. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeCameraManager.kt +337 -0
  11. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/OfflineCacheService.kt +374 -0
  12. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/TransitionManager.kt +202 -0
  13. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/WebCacheManager.kt +156 -39
  14. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/BarcodeDetector.kt +123 -0
  15. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/CameraSessionManager.kt +244 -0
  16. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/HoldController.kt +70 -0
  17. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/TorchController.kt +57 -0
  18. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/VideoStreamState.kt +41 -0
  19. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/VideoStreamStateMachine.kt +40 -0
  20. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/ViewfinderMapper.kt +75 -0
  21. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/ZoomController.kt +156 -0
  22. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/CatalystPlugin.kt +7 -0
  23. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/GeneratedPluginIndex.kt +6 -0
  24. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/PluginBridge.kt +253 -0
  25. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/security/FridaDetector.kt +16 -5
  26. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +42 -5
  27. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +7 -3
  28. package/dist/native/androidProject/app/src/main/res/drawable/debug_viewfinder_border.xml +8 -0
  29. package/dist/native/androidProject/app/src/main/res/layout/activity_main.xml +37 -0
  30. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CustomWebviewTest.kt +7 -2
  31. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/plugins/PluginBridgeTest.kt +139 -0
  32. package/dist/native/androidSetup.js +1 -0
  33. package/dist/native/bridge/WebBridge.js +5 -5
  34. package/dist/native/bridge/constants/NativeInterfaces.js +9 -5
  35. package/dist/native/bridge/hooks/index.js +1 -0
  36. package/dist/native/bridge/hooks/useCamera.js +9 -0
  37. package/dist/native/bridge/hooks/useDataProtection.js +2 -0
  38. package/dist/native/bridge/hooks/useDeviceInfo.js +2 -0
  39. package/dist/native/bridge/hooks/useFilePicker.js +2 -0
  40. package/dist/native/bridge/hooks/useGoogleSignIn.js +1 -0
  41. package/dist/native/bridge/hooks/useHapticFeedback.js +1 -0
  42. package/dist/native/bridge/hooks/useIntent.js +1 -0
  43. package/dist/native/bridge/hooks/useNativeTransition.js +43 -0
  44. package/dist/native/bridge/hooks/useNetworkStatus.js +3 -0
  45. package/dist/native/bridge/hooks/useNotification.js +2 -0
  46. package/dist/native/bridge/hooks/useSafeArea.js +2 -0
  47. package/dist/native/bridge/hooks/useVideoStream.js +6 -0
  48. package/dist/native/bridge/hooks/utils.js +1 -0
  49. package/dist/native/bridge/hooks.js +1 -137
  50. package/dist/native/bridge/useBaseHook.js +12 -7
  51. package/dist/native/bridge/utils/NativeBridge.js +41 -6
  52. package/dist/native/buildAndroid/assets.js +52 -0
  53. package/dist/native/buildAndroid/build.js +19 -0
  54. package/dist/native/buildAndroid/config.js +1 -0
  55. package/dist/native/buildAndroid/index.js +1 -0
  56. package/dist/native/buildAppAndroid.js +2 -150
  57. package/dist/native/buildAppIos.js +2 -312
  58. package/dist/native/buildIos/assets.js +8 -0
  59. package/dist/native/buildIos/build.js +40 -0
  60. package/dist/native/buildIos/config.js +71 -0
  61. package/dist/native/buildIos/index.js +6 -0
  62. package/dist/native/buildIos/plugins.js +45 -0
  63. package/dist/native/internalPluginUtils.js +7 -0
  64. package/dist/native/iosnativeWebView/Package.resolved +36 -0
  65. package/dist/native/iosnativeWebView/Sources/Core/Camera/BarcodeDetector.swift +116 -0
  66. package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraPreviewView.swift +58 -0
  67. package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraSessionManager.swift +308 -0
  68. package/dist/native/iosnativeWebView/Sources/Core/Camera/HoldController.swift +63 -0
  69. package/dist/native/iosnativeWebView/Sources/Core/Camera/NativeCameraManager.swift +194 -0
  70. package/dist/native/iosnativeWebView/Sources/Core/Camera/TorchController.swift +60 -0
  71. package/dist/native/iosnativeWebView/Sources/Core/Camera/VideoStreamState.swift +44 -0
  72. package/dist/native/iosnativeWebView/Sources/Core/Camera/VideoStreamStateMachine.swift +60 -0
  73. package/dist/native/iosnativeWebView/Sources/Core/Camera/ZoomController.swift +130 -0
  74. package/dist/native/iosnativeWebView/Sources/Core/Constants/CatalystConstants.swift +11 -1
  75. package/dist/native/iosnativeWebView/Sources/Core/Constants/ConfigConstants.swift +2 -1
  76. package/dist/native/iosnativeWebView/Sources/Core/Plugins/CatalystPlugin.swift +5 -0
  77. package/dist/native/iosnativeWebView/Sources/Core/Plugins/GeneratedPluginIndex.swift +6 -0
  78. package/dist/native/iosnativeWebView/Sources/Core/Plugins/PluginBridge.swift +364 -0
  79. package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +39 -4
  80. package/dist/native/iosnativeWebView/Sources/Core/Utils/OfflineCacheService.swift +683 -0
  81. package/dist/native/iosnativeWebView/Sources/Core/Utils/OfflineURLSchemeHandler.swift +94 -0
  82. package/dist/native/iosnativeWebView/Sources/Core/Utils/ResourceURLProtocol.swift +19 -6
  83. package/dist/native/iosnativeWebView/Sources/Core/Utils/SafeAreaUtils.swift +1 -0
  84. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeCommandHandler.swift +1 -0
  85. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +4 -1
  86. package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +227 -4
  87. package/dist/native/iosnativeWebView/Sources/Core/WebView/TransitionManager.swift +169 -0
  88. package/dist/native/iosnativeWebView/Sources/Core/WebView/WeakScriptMessageHandler.swift +14 -0
  89. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +53 -8
  90. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +88 -4
  91. package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +30 -11
  92. package/dist/native/iosnativeWebView/iosnativeWebView/Info.plist +11 -17
  93. package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +8 -2
  94. package/dist/native/iosnativeWebView/iosnativeWebViewTests/BootTimingUtilityTests.swift +1 -1
  95. package/dist/native/iosnativeWebView/iosnativeWebViewTests/ConfigMappingTests.swift +4 -2
  96. package/dist/native/iosnativeWebView/iosnativeWebViewTests/PluginBridgeTests.swift +160 -0
  97. package/dist/native/plugin-bridge/PluginBridge.js +1 -0
  98. package/dist/native/pluginComposerAndroid.js +28 -0
  99. package/dist/native/pluginComposerIos.js +16 -0
  100. package/dist/native/renameAndroidProject.js +5 -5
  101. package/dist/native/setupEmulatorIos.js +6 -4
  102. package/dist/native/setupServer.js +2 -3
  103. package/dist/native/utils.js +4 -1
  104. package/dist/offline/catalyst-sw.js +1 -0
  105. package/dist/offline/registerServiceWorker.js +1 -0
  106. package/dist/otel.js +70 -2
  107. package/dist/router/ClientRouter.js +1 -1
  108. package/dist/scripts/build.js +1 -1
  109. package/dist/scripts/cleanCache.js +1 -0
  110. package/dist/scripts/devBuild.js +1 -1
  111. package/dist/scripts/devServe.js +1 -1
  112. package/dist/scripts/generateOfflineManifest.js +1 -0
  113. package/dist/scripts/loadEnvironmentVariables.js +1 -2
  114. package/dist/scripts/plugins.js +1 -0
  115. package/dist/scripts/registerAliases.js +6 -2
  116. package/dist/scripts/scriptUtils.js +2 -1
  117. package/dist/scripts/serve.js +2 -1
  118. package/dist/scripts/start.js +7 -2
  119. package/dist/server/expressServer.js +4 -8
  120. package/dist/server/renderer/document/Body.js +4 -4
  121. package/dist/server/renderer/document/Head.js +2 -2
  122. package/dist/server/renderer/extract.js +31 -27
  123. package/dist/server/renderer/handler.js +47 -24
  124. package/dist/server/renderer/index.js +1 -1
  125. package/dist/server/startServer.js +57 -7
  126. package/dist/server/utils/userAgentUtil.js +9 -3
  127. package/dist/server/utils/validator.js +1 -1
  128. package/dist/webpack/babel-plugins/remove-client.plugin.js +2 -2
  129. package/dist/webpack/babel-plugins/remove-ssr.plugin.js +2 -2
  130. package/dist/webpack/babel.config.client.js +1 -1
  131. package/dist/webpack/babel.config.ssr.js +1 -1
  132. package/dist/webpack/base.babel.js +7 -8
  133. package/dist/webpack/development.client.babel.js +7 -16
  134. package/dist/webpack/production.client.babel.js +1 -1
  135. package/dist/webpack/production.ssr.babel.js +5 -1
  136. package/mcp_v2/knowledge-base.json +206 -100
  137. package/mcp_v2/lib/helpers.js +42 -8
  138. package/mcp_v2/lib/seed.js +88 -0
  139. package/mcp_v2/mcp.js +150 -4
  140. package/mcp_v2/setup.js +30 -193
  141. package/mcp_v2/tools/build.js +1 -1
  142. package/mcp_v2/tools/config.js +16 -4
  143. package/mcp_v2/tools/conversion.js +11 -11
  144. package/mcp_v2/tools/github.js +1117 -0
  145. package/mcp_v2/tools/knowledge.js +16 -3
  146. package/mcp_v2/tools/sync.js +179 -10
  147. package/mcp_v2/tools/tasks.js +13 -4
  148. package/package.json +20 -8
@@ -0,0 +1,160 @@
1
+ import XCTest
2
+ import WebKit
3
+ import UIKit
4
+ @testable import CatalystCore
5
+
6
+ final class PluginBridgeTests: XCTestCase {
7
+
8
+ private var bridge: PluginBridge!
9
+ private var mockWebView: PluginMockWKWebView!
10
+ private var mockViewController: UIViewController!
11
+ private var testExpectation: XCTestExpectation!
12
+
13
+ @MainActor
14
+ override func setUp() {
15
+ super.setUp()
16
+ mockWebView = PluginMockWKWebView()
17
+ mockViewController = UIViewController()
18
+ bridge = PluginBridge(webView: mockWebView, viewController: mockViewController)
19
+ bridge.register()
20
+ }
21
+
22
+ @MainActor
23
+ override func tearDown() {
24
+ bridge.unregister()
25
+ bridge = nil
26
+ mockWebView = nil
27
+ mockViewController = nil
28
+ testExpectation = nil
29
+ super.tearDown()
30
+ }
31
+
32
+ @MainActor
33
+ func testMessageHandling_InvalidHandler_EmitsInvalidPayloadError() async {
34
+ testExpectation = expectation(description: "Invalid handler should emit bridge error")
35
+ mockWebView.onEvaluateJavaScript = { script in
36
+ if script.contains("PLUGIN_BRIDGE_ERROR") && script.contains("INVALID_PAYLOAD") {
37
+ self.testExpectation.fulfill()
38
+ }
39
+ }
40
+
41
+ let message = PluginMockScriptMessage(
42
+ name: "NativeBridge",
43
+ body: [
44
+ "pluginId": "device-info-plugin",
45
+ "command": "getDeviceInfo"
46
+ ]
47
+ )
48
+
49
+ bridge.handleMessage(message)
50
+
51
+ await fulfillment(of: [testExpectation], timeout: 2.0)
52
+ }
53
+
54
+ @MainActor
55
+ func testMessageHandling_UnknownPlugin_EmitsPluginNotFoundError() async {
56
+ testExpectation = expectation(description: "Unknown plugin should emit plugin not found error")
57
+ mockWebView.onEvaluateJavaScript = { script in
58
+ if script.contains("PLUGIN_BRIDGE_ERROR") && script.contains("PLUGIN_NOT_FOUND") {
59
+ self.testExpectation.fulfill()
60
+ }
61
+ }
62
+
63
+ let message = PluginMockScriptMessage(
64
+ name: "PluginBridge",
65
+ body: [
66
+ "pluginId": "missing-plugin",
67
+ "command": "ping",
68
+ "requestId": "req-42"
69
+ ]
70
+ )
71
+
72
+ bridge.handleMessage(message)
73
+
74
+ await fulfillment(of: [testExpectation], timeout: 2.0)
75
+ }
76
+
77
+ @MainActor
78
+ func testCallbackHandling_ArbitraryCallback_DispatchesPluginEnvelope() async {
79
+ testExpectation = expectation(description: "Arbitrary callback should dispatch plugin envelope")
80
+ mockWebView.onEvaluateJavaScript = { script in
81
+ if script.contains("PluginBridgeWeb.dispatch") &&
82
+ script.contains("sync-plugin") &&
83
+ script.contains("ON_FAILURE") &&
84
+ script.contains("req-9") &&
85
+ script.contains("syncData") {
86
+ self.testExpectation.fulfill()
87
+ }
88
+ }
89
+
90
+ let context = PluginBridgeContext(
91
+ webView: mockWebView,
92
+ viewController: mockViewController,
93
+ pluginId: "sync-plugin",
94
+ command: "syncData",
95
+ requestId: "req-9"
96
+ )
97
+
98
+ context.callback(eventName: "ON_FAILURE", data: ["reason": "network"])
99
+
100
+ await fulfillment(of: [testExpectation], timeout: 2.0)
101
+ }
102
+
103
+ @MainActor
104
+ func testCallbackHandling_ValidCallback_DispatchesPluginEnvelope() async {
105
+ testExpectation = expectation(description: "Valid callback should dispatch plugin envelope")
106
+ mockWebView.onEvaluateJavaScript = { script in
107
+ if script.contains("PluginBridgeWeb.dispatch") &&
108
+ script.contains("sync-plugin") &&
109
+ script.contains("ON_SUCCESS") &&
110
+ script.contains("req-10") {
111
+ self.testExpectation.fulfill()
112
+ }
113
+ }
114
+
115
+ let context = PluginBridgeContext(
116
+ webView: mockWebView,
117
+ viewController: mockViewController,
118
+ pluginId: "sync-plugin",
119
+ command: "syncData",
120
+ requestId: "req-10"
121
+ )
122
+
123
+ context.callback(eventName: "ON_SUCCESS", data: ["status": "ok"])
124
+
125
+ await fulfillment(of: [testExpectation], timeout: 2.0)
126
+ }
127
+ }
128
+
129
+ private final class PluginMockWKWebView: WKWebView {
130
+ var onEvaluateJavaScript: ((String) -> Void)?
131
+ var evaluatedScripts: [String] = []
132
+
133
+ override init(frame: CGRect, configuration: WKWebViewConfiguration) {
134
+ super.init(frame: frame, configuration: configuration)
135
+ }
136
+
137
+ convenience init() {
138
+ let config = WKWebViewConfiguration()
139
+ self.init(frame: .zero, configuration: config)
140
+ }
141
+
142
+ required init?(coder: NSCoder) {
143
+ fatalError("init(coder:) has not been implemented")
144
+ }
145
+
146
+ @MainActor
147
+ override func evaluateJavaScript(_ javaScriptString: String, completionHandler: ((Any?, Error?) -> Void)? = nil) {
148
+ evaluatedScripts.append(javaScriptString)
149
+ onEvaluateJavaScript?(javaScriptString)
150
+
151
+ DispatchQueue.main.async {
152
+ completionHandler?(nil, nil)
153
+ }
154
+ }
155
+ }
156
+
157
+ private struct PluginMockScriptMessage: PluginBridgeMessage {
158
+ let name: String
159
+ let body: Any
160
+ }
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;class PluginNativeBridge{constructor(){this.handlers=new Map();this.requestCount=0;this.isInitialized=false;}createRequestId=()=>{this.requestCount+=1;return`plugin_${Date.now()}_${this.requestCount}`;};normalizeRequiredString=(value,fieldName)=>{if(typeof value!=="string"||!value.trim()){throw new Error(`${fieldName} must be a non-empty string`);}return value.trim();};normalizeCommand=command=>{if(command==null){return null;}return this.normalizeRequiredString(command,"command");};getHandlerKey=({pluginId,eventName,command=null}={})=>{return JSON.stringify([this.normalizeRequiredString(pluginId,"pluginId"),this.normalizeRequiredString(eventName,"eventName"),this.normalizeCommand(command)]);};getHandlerSet=(key,create=false)=>{let handlers=this.handlers.get(key);if(!handlers&&create){handlers=new Set();this.handlers.set(key,handlers);}return handlers||null;};addHandler=({pluginId,eventName,command}={},handler)=>{if(typeof handler!=="function"){throw new Error("handler must be a function");}this.getHandlerSet(this.getHandlerKey({pluginId,eventName,command}),true).add(handler);};removeHandler=({pluginId,eventName,command}={},handler)=>{const key=this.getHandlerKey({pluginId,eventName,command});const handlers=this.getHandlerSet(key,false);if(!handlers){return false;}if(handler==null){handlers.clear();this.handlers.delete(key);return true;}const removed=handlers.delete(handler);if(handlers.size===0){this.handlers.delete(key);}return removed;};ensureInitialized=()=>{if(typeof window==="undefined"||this.isInitialized){return this;}window.PluginBridgeWeb=window.PluginBridgeWeb||{};window.PluginBridgeWeb.callback=this.dispatchCallback;window.PluginBridgeWeb.dispatch=message=>{let parsed=message;if(typeof parsed==="string"){try{parsed=JSON.parse(parsed);}catch{return false;}}if(!parsed||typeof parsed!=="object"){return false;}const payload=Object.prototype.hasOwnProperty.call(parsed,"payload")?parsed.payload:null;return this.dispatchCallback(parsed.pluginId,parsed.eventName,payload,parsed.requestId??null,parsed.command??null);};this.isInitialized=true;return this;};hasAndroidBridge=()=>typeof window!=="undefined"&&!!window.PluginBridge;hasIOSBridge=()=>typeof window!=="undefined"&&!!window.webkit?.messageHandlers?.PluginBridge;init=()=>this.ensureInitialized();assertInitialized=()=>{if(!this.isInitialized){throw new Error("PluginBridge.init() must be called before using emit() or register()");}};reportHandlerError=error=>{if(typeof queueMicrotask==="function"){queueMicrotask(()=>{throw error;});return;}setTimeout(()=>{throw error;},0);};emit=({pluginId,command,data=null}={})=>{const normalizedPluginId=this.normalizeRequiredString(pluginId,"pluginId");const normalizedCommand=this.normalizeRequiredString(command,"command");if(typeof window==="undefined"){throw new Error("PluginBridge is not available in this environment");}this.assertInitialized();const payload={pluginId:normalizedPluginId,command:normalizedCommand,data,requestId:this.createRequestId()};if(this.hasAndroidBridge()){window.PluginBridge.emit(JSON.stringify(payload));return;}if(this.hasIOSBridge()){window.webkit.messageHandlers.PluginBridge.postMessage(payload);return;}throw new Error("PluginBridge is not available in this environment");};register=({pluginId,eventName,command,handler}={})=>{this.assertInitialized();this.addHandler({pluginId,eventName,command},handler);return()=>this.removeHandler({pluginId,eventName,command},handler);};unregister=({pluginId,eventName,command,handler}={})=>{if(typeof handler!=="function"){throw new Error("handler must be a function");}return this.removeHandler({pluginId,eventName,command},handler);};dispatchCallback=(pluginId,eventName,payload,requestId=null,command=null)=>{void requestId;const normalizedPluginId=typeof pluginId==="string"&&pluginId.trim()?pluginId.trim():null;const normalizedEventName=typeof eventName==="string"&&eventName.trim()?eventName.trim():null;if(normalizedPluginId==null||normalizedEventName==null){return false;}const normalizedCommand=typeof command==="string"&&command.trim()?command.trim():null;const toCall=new Set();const collectHandlers=commandScope=>{const handlers=this.getHandlerSet(JSON.stringify([normalizedPluginId,normalizedEventName,commandScope]),false);if(!handlers){return;}handlers.forEach(handler=>{toCall.add(handler);});};if(normalizedCommand!=null){collectHandlers(normalizedCommand);}collectHandlers(null);if(toCall.size===0){return false;}const meta={pluginId:normalizedPluginId,eventName:normalizedEventName,command:normalizedCommand};toCall.forEach(handler=>{try{handler(payload,meta);}catch(error){this.reportHandlerError(error);}});return true;};}const pluginNativeBridge=new PluginNativeBridge();var _default=exports.default=pluginNativeBridge;
@@ -0,0 +1,28 @@
1
+ "use strict";const fs=require("fs");const path=require("path");const{discoverInternalPlugins}=require("./internalPluginUtils.js");const PLUGINS_PACKAGE_PARTS=["io","yourname","androidproject","plugins"];function isDir(dirPath){return fs.existsSync(dirPath)&&fs.statSync(dirPath).isDirectory();}function ensureDir(dirPath){if(!fs.existsSync(dirPath)){fs.mkdirSync(dirPath,{recursive:true});}}function sanitizeForPath(value){return value.replace(/[^a-zA-Z0-9_-]/g,"_");}function mustBeNonEmptyString(value,fieldName,sourcePath){if(typeof value!=="string"||!value.trim()){throw new Error(`Invalid '${fieldName}' in ${sourcePath}`);}return value.trim();}function asUniqueSorted(values){return[...new Set(values)].sort();}function parsePluginToggleConfig(pluginConfig){if(pluginConfig==null){return{};}if(typeof pluginConfig!=="object"||Array.isArray(pluginConfig)){throw new Error("'WEBVIEW_CONFIG.plugins' must be an object with boolean values");}const toggles={};for(const[key,value]of Object.entries(pluginConfig)){const normalizedKey=mustBeNonEmptyString(key,"plugins.<key>","WEBVIEW_CONFIG");if(typeof value!=="boolean"){throw new Error(`'WEBVIEW_CONFIG.plugins.${normalizedKey}' must be boolean`);}toggles[normalizedKey]=value;}return toggles;}function selectPluginsByConfig(plugins,pluginConfig,log){const toggles=parsePluginToggleConfig(pluginConfig);const matchedKeys=new Set();const selected=[];for(const plugin of plugins){const selectorKeys=plugin.configKey?[plugin.configKey,plugin.id]:[plugin.id];const matches=[];for(const key of selectorKeys){if(Object.prototype.hasOwnProperty.call(toggles,key)){matches.push({key,value:toggles[key]});matchedKeys.add(key);}}const uniqueValues=[...new Set(matches.map(entry=>entry.value))];if(uniqueValues.length>1){throw new Error(`Conflicting toggle values for plugin '${plugin.id}' across keys: ${matches.map(entry=>`${entry.key}=${entry.value}`).join(", ")}`);}const enabled=matches.length===0?false:matches[0].value;if(enabled){selected.push(plugin);}else{log(`Plugin disabled by config: ${plugin.id}`,"info");}}const unknownKeys=Object.keys(toggles).filter(key=>!matchedKeys.has(key));if(unknownKeys.length>0){throw new Error(`Unknown plugin toggle key(s) in WEBVIEW_CONFIG.plugins: ${unknownKeys.join(", ")}`);}return selected;}function parseDependency(dependency,pluginId){const parts=dependency.split(":");if(parts.length<3){throw new Error(`Dependency '${dependency}' in plugin '${pluginId}' must be in 'group:artifact:version' format`);}return{key:`${parts[0]}:${parts[1]}`,version:parts.slice(2).join(":")};}function validatePlugins(plugins){const pluginIds=new Set();const configKeys=new Set();const dependencies=new Map();const selectorKeys=new Map();for(const plugin of plugins){if(pluginIds.has(plugin.id)){throw new Error(`Duplicate plugin id detected: ${plugin.id}`);}pluginIds.add(plugin.id);if(plugin.configKey){if(configKeys.has(plugin.configKey)){throw new Error(`Duplicate configKey detected: ${plugin.configKey}`);}configKeys.add(plugin.configKey);}for(const[field,selector]of[["id",plugin.id],["configKey",plugin.configKey]]){if(!selector){continue;}const existing=selectorKeys.get(selector);if(existing&&existing.pluginId!==plugin.id){throw new Error(`Plugin selector collision for '${selector}': '${existing.pluginId}' (${existing.field}) conflicts with '${plugin.id}' (${field})`);}selectorKeys.set(selector,{pluginId:plugin.id,field});}if(new Set(plugin.commands).size!==plugin.commands.length){throw new Error(`Duplicate command(s) detected within plugin '${plugin.id}'`);}for(const dependency of plugin.android?.dependencies||[]){const parsed=parseDependency(dependency,plugin.id);const existing=dependencies.get(parsed.key);if(existing&&existing.version!==parsed.version){throw new Error(`Dependency version conflict for '${parsed.key}': '${existing.version}' in '${existing.pluginId}', '${parsed.version}' in '${plugin.id}'`);}if(!existing){dependencies.set(parsed.key,{version:parsed.version,pluginId:plugin.id});}}}}function selectPluginsForPlatform(plugins,platform,log){const selected=[];for(const plugin of plugins){if(plugin.platforms.includes(platform)){selected.push(plugin);continue;}log(`Plugin enabled but not supported on ${platform}: ${plugin.id}`,"info");}return selected;}function walkFiles(rootDir,predicate,results=[]){if(!isDir(rootDir)){return results;}for(const entry of fs.readdirSync(rootDir,{withFileTypes:true})){// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - entry.name comes from readdirSync within the walked root.
2
+ const fullPath=path.join(rootDir,entry.name);if(entry.isDirectory()){walkFiles(fullPath,predicate,results);continue;}if(predicate(entry.name,fullPath)){results.push(fullPath);}}return results;}function resolvePluginClassSourcePath(plugin){const className=plugin.android.className.split(".").pop();const candidateNames=new Set([`${className}.kt`,`${className}.java`]);const candidates=walkFiles(plugin.android.sourceDir,name=>candidateNames.has(name));if(candidates.length>1){throw new Error(`Declared class '${plugin.android.className}' for selected plugin '${plugin.id}' resolved to multiple source files under ${plugin.android.sourceDir}`);}return candidates[0]||null;}function validateSelectedPluginSources(plugins){for(const plugin of plugins){if(!plugin.android){throw new Error(`Android config missing for selected plugin '${plugin.id}'`);}if(!isDir(plugin.android.sourceDir)){throw new Error(`Android source directory missing for selected plugin '${plugin.id}'`);}const codeFiles=walkFiles(plugin.android.sourceDir,name=>name.endsWith(".kt")||name.endsWith(".java"));if(codeFiles.length===0){throw new Error(`No Android source files found for selected plugin '${plugin.id}'`);}if(!resolvePluginClassSourcePath(plugin)){throw new Error(`Declared class '${plugin.android.className}' for selected plugin '${plugin.id}' was not found as a .kt or .java file under ${plugin.android.sourceDir}`);}}}function copyTree(sourceDir,targetDir){if(!isDir(sourceDir)){return;}ensureDir(targetDir);for(const filePath of walkFiles(sourceDir,()=>true)){// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - filePath is produced by walking sourceDir, so the relative path remains within that tree.
3
+ const targetPath=path.join(targetDir,path.relative(sourceDir,filePath));ensureDir(path.dirname(targetPath));fs.copyFileSync(filePath,targetPath);}}function copyAndroidPluginSources(plugins,javaRoot,log){// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - The package path components are fixed constants.
4
+ const internalRoot=path.join(javaRoot,...PLUGINS_PACKAGE_PARTS,"internal");fs.rmSync(internalRoot,{recursive:true,force:true});ensureDir(internalRoot);let copiedCount=0;for(const plugin of plugins){// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - plugin.id is sanitized before being used as a path component.
5
+ const pluginOutputDir=path.join(internalRoot,sanitizeForPath(plugin.id));const codeFiles=walkFiles(plugin.android.sourceDir,name=>name.endsWith(".kt")||name.endsWith(".java"));for(const sourcePath of codeFiles){// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - sourcePath is produced by walking plugin.android.sourceDir.
6
+ const targetPath=path.join(pluginOutputDir,path.relative(plugin.android.sourceDir,sourcePath));ensureDir(path.dirname(targetPath));fs.copyFileSync(sourcePath,targetPath);copiedCount++;}}log(`Copied ${copiedCount} Android plugin source file(s)`,"info");}function copyPluginAssets(plugins,androidProjectPath,log){// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - The destination path components are fixed.
7
+ const baseAssetsDir=path.join(androidProjectPath,"app","src","main","assets","plugins");fs.rmSync(baseAssetsDir,{recursive:true,force:true});ensureDir(baseAssetsDir);for(const plugin of plugins){// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - plugin.id is sanitized before being used as a path component.
8
+ const pluginAssetsDir=path.join(baseAssetsDir,sanitizeForPath(plugin.id));// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - These are fixed asset directories beneath the discovered internal plugin directory.
9
+ copyTree(path.join(plugin.pluginDir,"assets","common"),path.join(pluginAssetsDir,"common"));// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - These are fixed asset directories beneath the discovered internal plugin directory.
10
+ copyTree(path.join(plugin.pluginDir,"assets","android"),path.join(pluginAssetsDir,"android"));}log("Plugin assets copied to app/src/main/assets/plugins","info");}function formatKotlinMap(entries,emptyLiteral="emptyMap()"){return entries.length===0?emptyLiteral:`mapOf(\n${entries.join(",\n")}\n )`;}function generatePluginRegistryFiles(plugins,javaRoot){const pluginIdToClassName={};const pluginToCommands={};for(const plugin of plugins){pluginIdToClassName[plugin.id]=plugin.android.className;pluginToCommands[plugin.id]=asUniqueSorted(plugin.commands);}const classEntries=Object.keys(pluginIdToClassName).sort().map(pluginId=>` ${JSON.stringify(pluginId)} to ${JSON.stringify(pluginIdToClassName[pluginId])}`);const commandEntries=Object.keys(pluginToCommands).sort().map(pluginId=>{const commands=pluginToCommands[pluginId];const commandSet=commands.length?`setOf(${commands.map(value=>JSON.stringify(value)).join(", ")})`:"emptySet()";return` ${JSON.stringify(pluginId)} to ${commandSet}`;});const indexContent=`package io.yourname.androidproject.plugins
11
+
12
+ object GeneratedPluginIndex {
13
+ val pluginIdToClassName: Map<String, String> = ${formatKotlinMap(classEntries)}
14
+ val pluginToCommands: Map<String, Set<String>> = ${formatKotlinMap(commandEntries)}
15
+ }
16
+ `;// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - The package path components are fixed constants.
17
+ const pluginsDir=path.join(javaRoot,...PLUGINS_PACKAGE_PARTS);ensureDir(pluginsDir);// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - The generated filename is fixed.
18
+ fs.writeFileSync(path.join(pluginsDir,"GeneratedPluginIndex.kt"),indexContent);// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - The generated filename is fixed.
19
+ fs.rmSync(path.join(pluginsDir,"GeneratedPluginMeta.kt"),{force:true});}function escapeRegexLiteral(value){return value.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");}function updateAndroidManifestPermissions(manifestPath,selectedPermissions,allKnownPluginPermissions){const uniquePermissions=asUniqueSorted(selectedPermissions);const knownPermissions=asUniqueSorted(allKnownPluginPermissions);let manifest=fs.readFileSync(manifestPath,"utf8");const beginMarker="<!-- CATALYST_PLUGIN_PERMISSIONS_START -->";const endMarker="<!-- CATALYST_PLUGIN_PERMISSIONS_END -->";const markerRegex=/[ \t]*<!-- CATALYST_PLUGIN_PERMISSIONS_START -->[\s\S]*?<!-- CATALYST_PLUGIN_PERMISSIONS_END -->\s*/g;manifest=manifest.replace(markerRegex,"");// Migration cleanup for legacy entries previously written without markers.
20
+ for(const permission of knownPermissions){const escaped=escapeRegexLiteral(permission);// nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp - The dynamic value is escaped as a regex literal above.
21
+ const legacyRegex=new RegExp(`^[ \\t]*<uses-permission\\s+android:name="${escaped}"\\s*/>\\s*\\n?`,"gm");manifest=manifest.replace(legacyRegex,"");}if(uniquePermissions.length>0){const permissionLines=uniquePermissions.map(permission=>` <uses-permission android:name="${permission}" />`).join("\n");const managedBlock=` ${beginMarker}\n${permissionLines}\n ${endMarker}\n`;manifest=manifest.replace(/<application\b/,`${managedBlock} <application`);}fs.writeFileSync(manifestPath,manifest);}function findDependenciesBlockRange(gradleText,gradlePath){const headerMatch=gradleText.match(/^dependencies\s*\{/m);if(!headerMatch||headerMatch.index==null){throw new Error(`Could not find dependencies block in ${gradlePath}`);}const openBraceIndex=gradleText.indexOf("{",headerMatch.index);if(openBraceIndex===-1){throw new Error(`Malformed dependencies block in ${gradlePath}`);}let depth=0;for(let index=openBraceIndex;index<gradleText.length;index++){const ch=gradleText[index];if(ch==="{")depth++;if(ch==="}")depth--;if(depth===0){return{openBraceIndex,blockEnd:index};}}throw new Error(`Could not find end of dependencies block in ${gradlePath}`);}function updateGradleDependencies(gradlePath,selectedDependencies,allKnownPluginDependencies){const uniqueDependencies=asUniqueSorted(selectedDependencies);const knownDependencies=asUniqueSorted(allKnownPluginDependencies);let gradle=fs.readFileSync(gradlePath,"utf8");const{openBraceIndex,blockEnd}=findDependenciesBlockRange(gradle,gradlePath);let blockBody=gradle.slice(openBraceIndex+1,blockEnd);const beginMarker="// CATALYST_PLUGIN_DEPENDENCIES_START";const endMarker="// CATALYST_PLUGIN_DEPENDENCIES_END";const markerRegex=/[ \t]*\/\/ CATALYST_PLUGIN_DEPENDENCIES_START[\s\S]*?\/\/ CATALYST_PLUGIN_DEPENDENCIES_END\s*/g;blockBody=blockBody.replace(markerRegex,"");// Migration cleanup for legacy entries previously written without markers.
22
+ for(const dependency of knownDependencies){const escaped=escapeRegexLiteral(dependency);// nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp - The dynamic value is escaped as a regex literal above.
23
+ const legacyRegex=new RegExp(`^[ \\t]*implementation\\("${escaped}"\\)\\s*\\n?`,"gm");blockBody=blockBody.replace(legacyRegex,"");}if(uniqueDependencies.length>0){const managedLines=uniqueDependencies.map(dependency=>` implementation("${dependency}")`).join("\n");blockBody=`${blockBody}\n ${beginMarker}\n${managedLines}\n ${endMarker}\n`;}gradle=`${gradle.slice(0,openBraceIndex+1)}${blockBody}${gradle.slice(blockEnd)}`;fs.writeFileSync(gradlePath,gradle);}function updateProguardKeepRules(proguardPath,selectedClassNames,allKnownPluginClassNames){const uniqueClassNames=asUniqueSorted(selectedClassNames);const knownClassNames=asUniqueSorted(allKnownPluginClassNames);let rules=fs.readFileSync(proguardPath,"utf8");const beginMarker="# CATALYST_PLUGIN_KEEP_START";const endMarker="# CATALYST_PLUGIN_KEEP_END";const markerRegex=/[ \t]*# CATALYST_PLUGIN_KEEP_START[\s\S]*?# CATALYST_PLUGIN_KEEP_END\s*/g;rules=rules.replace(markerRegex,"");for(const className of knownClassNames){const escaped=escapeRegexLiteral(className);// nosemgrep: javascript.lang.security.audit.detect-non-literal-regexp.detect-non-literal-regexp - The dynamic value is escaped as a regex literal above.
24
+ const legacyRegex=new RegExp(`^[ \\t]*-keep class ${escaped} \\{ \\*; \\}\\s*\\n?`,"gm");rules=rules.replace(legacyRegex,"");}if(uniqueClassNames.length>0){const keepLines=uniqueClassNames.map(className=>`-keep class ${className} { *; }`).join("\n");rules=`${rules.trimEnd()}\n\n${beginMarker}\n${keepLines}\n${endMarker}\n`;}fs.writeFileSync(proguardPath,rules);}function composeAndroidPlugins({corePluginsRoot,androidProjectPath,pluginConfig,log}){const discovered=discoverInternalPlugins(corePluginsRoot,log);validatePlugins(discovered);const enabled=selectPluginsByConfig(discovered,pluginConfig,log);const selected=selectPluginsForPlatform(enabled,"android",log);validateSelectedPluginSources(selected);// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - These are fixed paths within the generated Android project.
25
+ const javaRoot=path.join(androidProjectPath,"app","src","main","java");// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - These are fixed paths within the generated Android project.
26
+ const manifestPath=path.join(androidProjectPath,"app","src","main","AndroidManifest.xml");// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - These are fixed paths within the generated Android project.
27
+ const gradlePath=path.join(androidProjectPath,"app","build.gradle.kts");// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - These are fixed paths within the generated Android project.
28
+ const proguardPath=path.join(androidProjectPath,"app","proguard-rules.pro");copyAndroidPluginSources(selected,javaRoot,log);copyPluginAssets(selected,androidProjectPath,log);generatePluginRegistryFiles(selected,javaRoot);updateAndroidManifestPermissions(manifestPath,selected.flatMap(plugin=>plugin.android?.permissions||[]),discovered.flatMap(plugin=>plugin.android?.permissions||[]));updateGradleDependencies(gradlePath,selected.flatMap(plugin=>plugin.android?.dependencies||[]),discovered.flatMap(plugin=>plugin.android?.dependencies||[]));updateProguardKeepRules(proguardPath,selected.map(plugin=>plugin.android?.className).filter(Boolean),discovered.map(plugin=>plugin.android?.className).filter(Boolean));log(`Plugin composition complete (${selected.length} enabled plugin(s))`,"success");return{pluginCount:selected.length,commandCount:selected.reduce((total,plugin)=>total+plugin.commands.length,0)};}module.exports={composeAndroidPlugins};
@@ -0,0 +1,16 @@
1
+ "use strict";const fs=require("fs");const path=require("path");const{discoverInternalPlugins}=require("./internalPluginUtils.js");function isDir(dirPath){return fs.existsSync(dirPath)&&fs.statSync(dirPath).isDirectory();}function ensureDir(dirPath){if(!fs.existsSync(dirPath)){fs.mkdirSync(dirPath,{recursive:true});}}function sanitizeForPath(value){return value.replace(/[^a-zA-Z0-9_-]/g,"_");}function mustBeNonEmptyString(value,fieldName,sourcePath){if(typeof value!=="string"||!value.trim()){throw new Error(`Invalid '${fieldName}' in ${sourcePath}`);}return value.trim();}function asUniqueSorted(values){return[...new Set(values)].sort();}function isPlainObject(value){return!!value&&typeof value==="object"&&!Array.isArray(value);}function deepEqual(left,right){return JSON.stringify(left)===JSON.stringify(right);}function mergeStructuredValues(existing,incoming,fieldName){if(existing===undefined){return JSON.parse(JSON.stringify(incoming));}if(Array.isArray(existing)&&Array.isArray(incoming)){const merged=[];const seen=new Set();for(const value of[...existing,...incoming]){const key=JSON.stringify(value);if(seen.has(key)){continue;}seen.add(key);merged.push(value);}return merged;}if(isPlainObject(existing)&&isPlainObject(incoming)){const merged={...existing};for(const[key,value]of Object.entries(incoming)){merged[key]=mergeStructuredValues(merged[key],value,`${fieldName}.${key}`);}return merged;}if(deepEqual(existing,incoming)){return existing;}throw new Error(`Conflicting values for '${fieldName}' while composing selected iOS plugins`);}function dependencyKey(dependency){return dependency.url;}function requirementKey(dependency){return`${dependency.requirement.type}:${dependency.requirement.version}`;}function packageKey(dependency){return dependency.package;}function resolveManifestPath(pluginDir,relativePath,fieldName){// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - Containment and realpath checks are enforced immediately below.
2
+ const resolvedPath=path.resolve(pluginDir,relativePath);// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - pluginDir comes from the internal plugin root.
3
+ const normalizedPluginDir=fs.realpathSync(path.resolve(pluginDir));if(resolvedPath!==normalizedPluginDir&&!resolvedPath.startsWith(`${normalizedPluginDir}${path.sep}`)){throw new Error(`'${fieldName}' must stay within plugin directory: ${relativePath}`);}if(!fs.existsSync(resolvedPath)){return resolvedPath;}const realResolvedPath=fs.realpathSync(resolvedPath);if(realResolvedPath!==normalizedPluginDir&&!realResolvedPath.startsWith(`${normalizedPluginDir}${path.sep}`)){throw new Error(`'${fieldName}' resolves outside plugin directory: ${relativePath}`);}return realResolvedPath;}function parsePluginToggleConfig(pluginConfig){if(pluginConfig==null){return{};}if(typeof pluginConfig!=="object"||Array.isArray(pluginConfig)){throw new Error("'WEBVIEW_CONFIG.plugins' must be an object with boolean values");}const toggles={};for(const[key,value]of Object.entries(pluginConfig)){const normalizedKey=mustBeNonEmptyString(key,"plugins.<key>","WEBVIEW_CONFIG");if(typeof value!=="boolean"){throw new Error(`'WEBVIEW_CONFIG.plugins.${normalizedKey}' must be boolean`);}toggles[normalizedKey]=value;}return toggles;}function selectPluginsByConfig(plugins,pluginConfig,log){const toggles=parsePluginToggleConfig(pluginConfig);const matchedKeys=new Set();const enabled=[];for(const plugin of plugins){const selectorKeys=plugin.configKey?[plugin.configKey,plugin.id]:[plugin.id];const matches=[];for(const key of selectorKeys){if(Object.prototype.hasOwnProperty.call(toggles,key)){matches.push({key,value:toggles[key]});matchedKeys.add(key);}}const uniqueValues=[...new Set(matches.map(entry=>entry.value))];if(uniqueValues.length>1){throw new Error(`Conflicting toggle values for plugin '${plugin.id}' across keys: ${matches.map(entry=>`${entry.key}=${entry.value}`).join(", ")}`);}const isEnabled=matches.length===0?false:matches[0].value;if(isEnabled){enabled.push(plugin);}else{log(`Plugin disabled by config: ${plugin.id}`,"info");}}const unknownKeys=Object.keys(toggles).filter(key=>!matchedKeys.has(key));if(unknownKeys.length>0){throw new Error(`Unknown plugin toggle key(s) in WEBVIEW_CONFIG.plugins: ${unknownKeys.join(", ")}`);}return enabled;}function validatePlugins(plugins){const pluginIds=new Set();const configKeys=new Set();const selectorKeys=new Map();const dependencies=new Map();for(const plugin of plugins){if(pluginIds.has(plugin.id)){throw new Error(`Duplicate plugin id detected: ${plugin.id}`);}pluginIds.add(plugin.id);if(plugin.configKey){if(configKeys.has(plugin.configKey)){throw new Error(`Duplicate configKey detected: ${plugin.configKey}`);}configKeys.add(plugin.configKey);}for(const[field,selector]of[["id",plugin.id],["configKey",plugin.configKey]]){if(!selector){continue;}const existing=selectorKeys.get(selector);if(existing&&existing.pluginId!==plugin.id){throw new Error(`Plugin selector collision for '${selector}': '${existing.pluginId}' (${existing.field}) conflicts with '${plugin.id}' (${field})`);}selectorKeys.set(selector,{pluginId:plugin.id,field});}if(new Set(plugin.commands).size!==plugin.commands.length){throw new Error(`Duplicate command(s) detected within plugin '${plugin.id}'`);}if(Object.prototype.hasOwnProperty.call(plugin.ios?.infoPlist||{},"CFBundleURLTypes")){throw new Error(`Plugin '${plugin.id}' must use 'ios.urlSchemes' instead of 'ios.infoPlist.CFBundleURLTypes'`);}if(Object.prototype.hasOwnProperty.call(plugin.ios?.infoPlist||{},"LSApplicationQueriesSchemes")){throw new Error(`Plugin '${plugin.id}' must use 'ios.querySchemes' instead of 'ios.infoPlist.LSApplicationQueriesSchemes'`);}for(const dependency of plugin.ios?.dependencies||[]){const existing=dependencies.get(dependencyKey(dependency));if(existing&&requirementKey(existing.dependency)!==requirementKey(dependency)){throw new Error(`iOS dependency version conflict for '${dependency.url}': '${existing.dependency.requirement.type}:${existing.dependency.requirement.version}' in '${existing.pluginId}', '${dependency.requirement.type}:${dependency.requirement.version}' in '${plugin.id}'`);}if(existing&&packageKey(existing.dependency)!==packageKey(dependency)){throw new Error(`iOS dependency package identity conflict for '${dependency.url}': '${existing.dependency.package}' in '${existing.pluginId}', '${dependency.package}' in '${plugin.id}'`);}if(!existing){dependencies.set(dependencyKey(dependency),{dependency,pluginId:plugin.id});}}for(const resourcePath of plugin.ios?.resources||[]){const resolvedPath=resolveManifestPath(plugin.pluginDir,resourcePath,`ios.resources for '${plugin.id}'`);if(!fs.existsSync(resolvedPath)){throw new Error(`iOS resource path not found for plugin '${plugin.id}': ${resourcePath}`);}}}}function selectPluginsForPlatform(plugins,platform,log){const selected=[];for(const plugin of plugins){if(plugin.platforms.includes(platform)){selected.push(plugin);continue;}log(`Plugin enabled but not supported on ${platform}: ${plugin.id}`,"info");}return selected;}function walkFiles(rootDir,predicate,results=[]){if(!isDir(rootDir)){return results;}for(const entry of fs.readdirSync(rootDir,{withFileTypes:true})){// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - entry.name comes from readdirSync within the walked root.
4
+ const fullPath=path.join(rootDir,entry.name);if(entry.isDirectory()){walkFiles(fullPath,predicate,results);continue;}if(predicate(entry.name,fullPath)){results.push(fullPath);}}return results;}function resolvePluginClassSourcePath(plugin){const className=plugin.ios.className.split(".").pop();const candidateName=`${className}.swift`;const candidates=walkFiles(plugin.ios.sourceDir,name=>name===candidateName);return candidates[0]||null;}function validateSelectedPluginSources(plugins){for(const plugin of plugins){if(!plugin.ios){throw new Error(`iOS config missing for selected plugin '${plugin.id}'`);}if(!isDir(plugin.ios.sourceDir)){throw new Error(`iOS source directory missing for selected plugin '${plugin.id}'`);}const codeFiles=walkFiles(plugin.ios.sourceDir,name=>name.endsWith(".swift"));if(codeFiles.length===0){throw new Error(`No iOS source files found for selected plugin '${plugin.id}'`);}if(!resolvePluginClassSourcePath(plugin)){throw new Error(`Declared class '${plugin.ios.className}' for selected plugin '${plugin.id}' was not found under ${plugin.ios.sourceDir}`);}}}function collectIosDependencies(plugins){const dependenciesByUrl=new Map();for(const plugin of plugins){for(const dependency of plugin.ios?.dependencies||[]){const existing=dependenciesByUrl.get(dependency.url);if(!existing){dependenciesByUrl.set(dependency.url,{url:dependency.url,package:dependency.package,requirement:dependency.requirement,products:[...dependency.products]});continue;}if(requirementKey(existing)!==requirementKey(dependency)){throw new Error(`iOS dependency version conflict for '${dependency.url}' while composing selected plugins`);}if(packageKey(existing)!==packageKey(dependency)){throw new Error(`iOS dependency package identity conflict for '${dependency.url}' while composing selected plugins`);}existing.products=asUniqueSorted([...existing.products,...dependency.products]);}}return[...dependenciesByUrl.values()].sort((left,right)=>left.url.localeCompare(right.url));}function normalizeResourceRelativePath(plugin,absolutePath){const relativePath=path.relative(plugin.pluginDir,absolutePath);if(relativePath.startsWith(`ios${path.sep}resources${path.sep}`)){return relativePath.slice(`ios${path.sep}resources${path.sep}`.length);}if(relativePath.startsWith(`ios${path.sep}`)){return relativePath.slice(`ios${path.sep}`.length);}return relativePath;}function validateBundleRelativePath(pluginId,bundleRelativePath){const normalizedPath=path.posix.normalize(bundleRelativePath);const expectedPrefix=`PluginResources/${sanitizeForPath(pluginId)}`;if(path.posix.isAbsolute(normalizedPath)||normalizedPath===".."||normalizedPath.startsWith("../")){throw new Error(`Invalid bundled resource path for plugin '${pluginId}': ${bundleRelativePath}`);}if(normalizedPath!==expectedPrefix&&!normalizedPath.startsWith(`${expectedPrefix}/`)){throw new Error(`Bundled resource path escaped managed directory for plugin '${pluginId}': ${bundleRelativePath}`);}return normalizedPath;}function collectIosResources(plugins){const resources=[];for(const plugin of plugins){for(const resourcePath of plugin.ios?.resources||[]){const resolvedPath=resolveManifestPath(plugin.pluginDir,resourcePath,`ios.resources for '${plugin.id}'`);const entries=fs.statSync(resolvedPath).isDirectory()?walkFiles(resolvedPath,()=>true):[resolvedPath];for(const entryPath of entries){const normalizedRelativePath=normalizeResourceRelativePath(plugin,entryPath);const candidateBundleRelativePath=path// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - The resulting path is checked by validateBundleRelativePath below.
5
+ .join("PluginResources",sanitizeForPath(plugin.id),normalizedRelativePath).split(path.sep).join("/");const bundleRelativePath=validateBundleRelativePath(plugin.id,candidateBundleRelativePath);resources.push({pluginId:plugin.id,sourcePath:entryPath,bundleRelativePath});}}}return resources.sort((left,right)=>left.bundleRelativePath.localeCompare(right.bundleRelativePath));}function collectIosInfoPlist(plugins){let infoPlist={};for(const plugin of plugins){infoPlist=mergeStructuredValues(infoPlist,plugin.ios?.infoPlist||{},`ios.infoPlist for '${plugin.id}'`);}return infoPlist;}function collectIosEntitlements(plugins){let entitlements={};for(const plugin of plugins){entitlements=mergeStructuredValues(entitlements,plugin.ios?.entitlements||{},`ios.entitlements for '${plugin.id}'`);}return entitlements;}function collectIosUrlSchemes(plugins){const entries=[];for(const plugin of plugins){for(const entry of plugin.ios?.urlSchemes||[]){entries.push({name:entry.name||plugin.id,schemes:asUniqueSorted(entry.schemes)});}}return entries;}function collectIosQuerySchemes(plugins){return asUniqueSorted(plugins.flatMap(plugin=>plugin.ios?.querySchemes||[]));}function copyIosPluginSources(plugins,iosProjectPath,log){// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - The destination path components are fixed.
6
+ const internalRoot=path.join(iosProjectPath,"Sources","Core","Plugins","Internal");fs.rmSync(internalRoot,{recursive:true,force:true});ensureDir(internalRoot);let copiedCount=0;for(const plugin of plugins){// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - plugin.id is sanitized before being used as a path component.
7
+ const pluginOutputDir=path.join(internalRoot,sanitizeForPath(plugin.id));const codeFiles=walkFiles(plugin.ios.sourceDir,name=>name.endsWith(".swift"));for(const sourcePath of codeFiles){// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - sourcePath is produced by walking plugin.ios.sourceDir.
8
+ const targetPath=path.join(pluginOutputDir,path.relative(plugin.ios.sourceDir,sourcePath));ensureDir(path.dirname(targetPath));fs.copyFileSync(sourcePath,targetPath);copiedCount++;}}log(`Copied ${copiedCount} iOS plugin source file(s)`,"info");}function formatSwiftDictionary(entries,emptyLiteral="[:]"){return entries.length===0?emptyLiteral:`[\n${entries.join(",\n")}\n ]`;}function generatePluginRegistryFiles(plugins,iosProjectPath){const pluginFactories={};const pluginToCommands={};for(const plugin of plugins){pluginFactories[plugin.id]=plugin.ios.className;pluginToCommands[plugin.id]=asUniqueSorted(plugin.commands);}const factoryEntries=Object.keys(pluginFactories).sort().map(pluginId=>` ${JSON.stringify(pluginId)}: { ${pluginFactories[pluginId]}() }`);const commandEntries=Object.keys(pluginToCommands).sort().map(pluginId=>{const commands=pluginToCommands[pluginId];const commandSet=commands.length?`Set([${commands.map(value=>JSON.stringify(value)).join(", ")}])`:"[]";return` ${JSON.stringify(pluginId)}: ${commandSet}`;});const indexContent=`import Foundation
9
+
10
+ enum GeneratedPluginIndex {
11
+ static let pluginFactories: [String: () -> CatalystPlugin] = ${formatSwiftDictionary(factoryEntries)}
12
+ static let pluginToCommands: [String: Set<String>] = ${formatSwiftDictionary(commandEntries)}
13
+ }
14
+ `;// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - The destination path components are fixed.
15
+ const pluginsDir=path.join(iosProjectPath,"Sources","Core","Plugins");ensureDir(pluginsDir);// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - The generated filename is fixed.
16
+ fs.writeFileSync(path.join(pluginsDir,"GeneratedPluginIndex.swift"),indexContent);}function composeIosPlugins({corePluginsRoot,iosProjectPath,pluginConfig,log}){const discovered=discoverInternalPlugins(corePluginsRoot,log);validatePlugins(discovered);const enabled=selectPluginsByConfig(discovered,pluginConfig,log);const selected=selectPluginsForPlatform(enabled,"ios",log);validateSelectedPluginSources(selected);const iosDependencies=collectIosDependencies(selected);const infoPlist=collectIosInfoPlist(selected);const urlSchemes=collectIosUrlSchemes(selected);const querySchemes=collectIosQuerySchemes(selected);const entitlements=collectIosEntitlements(selected);const resources=collectIosResources(selected);copyIosPluginSources(selected,iosProjectPath,log);generatePluginRegistryFiles(selected,iosProjectPath);log(`Plugin composition complete (${selected.length} enabled iOS plugin(s))`,"success");return{pluginCount:selected.length,commandCount:selected.reduce((total,plugin)=>total+plugin.commands.length,0),iosDependencies,infoPlist,urlSchemes,querySchemes,entitlements,resources};}module.exports={composeIosPlugins};
@@ -49,23 +49,23 @@ const gradlewPath=_path.default.join(tempDir,"gradlew");if(!_fs.default.existsSy
49
49
  progress.log(`Checking copied files (informational only):`,"info");const filesToCheck=[{path:"gradlew",desc:"Gradle wrapper"},{path:"build.gradle",desc:"Root build.gradle"},{path:"build.gradle.kts",desc:"Root build.gradle.kts"},{path:"app/build.gradle",desc:"App build.gradle"},{path:"app/build.gradle.kts",desc:"App build.gradle.kts"},{path:"settings.gradle",desc:"Settings gradle"},{path:"settings.gradle.kts",desc:"Settings gradle.kts"},{path:"app/src/main/AndroidManifest.xml",desc:"Android manifest"}];filesToCheck.forEach(file=>{const fullPath=_path.default.join(tempDir,file.path);const exists=_fs.default.existsSync(fullPath);progress.log(` ${file.desc}: ${exists?"✅ Found":"⚠️ Not found"}`,exists?"info":"warning");});// List all files in temp directory for debugging
50
50
  if(_fs.default.existsSync(tempDir)){const tempFiles=_fs.default.readdirSync(tempDir);progress.log(`Temp directory contents: ${tempFiles.join(", ")}`,"info");}}catch(error){// Clean up on error
51
51
  if(_fs.default.existsSync(tempDir)){try{;(0,_utils.runCommand)(`rm -rf "${tempDir}"`);}catch(cleanupError){progress.log(`Warning: Could not cleanup temp directory: ${cleanupError.message}`,"warning");}}throw new Error(`Failed to create temporary deployment copy: ${error.message}`);}// Find and rename any subdirectories that contain the old project name within the temp copy
52
- try{const findCommand=`find "${tempDir}" -type d -name "*${oldProjectName}*"`;const result=(0,_utils.runCommand)(findCommand);if(result.trim()){const dirsToRename=result.trim().split("\n").filter(dir=>dir.trim());for(const dir of dirsToRename){const newDirName=dir.replace(new RegExp(oldProjectName,"g"),newProjectName);if(dir!==newDirName){;(0,_utils.runCommand)(`mv "${dir}" "${newDirName}"`);progress.log(`Renamed subdirectory: ${_path.default.basename(dir)} → ${_path.default.basename(newDirName)}`,"success");}}}}catch(error){progress.log(`Warning: Error finding subdirectories to rename: ${error.message}`,"warning");}progress.log("Temporary deployment project created successfully","success");return tempDir;}async function finalizeAABAndCleanup(projectPaths,androidConfig,aabResult,tempDeploymentPath){const{oldProjectPath}=projectPaths;progress.log("Finalizing AAB and cleaning up temporary files...","info");try{// Create final output directory next to original project
52
+ try{const findCommand=`find "${tempDir}" -type d -name "*${oldProjectName}*"`;const result=(0,_utils.runCommand)(findCommand);if(result.trim()){const dirsToRename=result.trim().split("\n").filter(dir=>dir.trim());for(const dir of dirsToRename){const newDirName=dir.split(oldProjectName).join(newProjectName);if(dir!==newDirName){;(0,_utils.runCommand)(`mv "${dir}" "${newDirName}"`);progress.log(`Renamed subdirectory: ${_path.default.basename(dir)} → ${_path.default.basename(newDirName)}`,"success");}}}}catch(error){progress.log(`Warning: Error finding subdirectories to rename: ${error.message}`,"warning");}progress.log("Temporary deployment project created successfully","success");return tempDir;}async function finalizeAABAndCleanup(projectPaths,androidConfig,aabResult,tempDeploymentPath){const{oldProjectPath}=projectPaths;progress.log("Finalizing AAB and cleaning up temporary files...","info");try{// Create final output directory next to original project
53
53
  const outputDir=androidConfig.outputPath||_path.default.join(_path.default.dirname(oldProjectPath),"build-output");if(!_fs.default.existsSync(outputDir)){_fs.default.mkdirSync(outputDir,{recursive:true});progress.log(`Created output directory: ${outputDir}`,"success");}// Copy AAB to final location with descriptive name
54
54
  const timestamp=new Date().toISOString().replace(/[:.]/g,"-").split("T")[0];const finalAabName=`${androidConfig.newProjectName}-release-${timestamp}.aab`;const finalAabPath=_path.default.join(outputDir,finalAabName);_fs.default.copyFileSync(aabResult.outputAabPath,finalAabPath);progress.log(`AAB saved to final location: ${finalAabPath}`,"success");// Copy keystore to output directory for future use
55
55
  const keystorePath=_path.default.join(tempDeploymentPath,"app","keystore.jks");if(_fs.default.existsSync(keystorePath)){const finalKeystorePath=_path.default.join(outputDir,`${androidConfig.newProjectName}-keystore.jks`);_fs.default.copyFileSync(keystorePath,finalKeystorePath);progress.log(`Keystore copied to: ${finalKeystorePath}`,"success");}// Clean up temporary deployment directory
56
56
  await cleanupTempDeployment(tempDeploymentPath);return{...aabResult,finalAabPath,finalAabName,outputDir};}catch(error){throw new Error(`Failed to finalize AAB: ${error.message}`);}}async function cleanupTempDeployment(tempDeploymentPath){if(!tempDeploymentPath||!_fs.default.existsSync(tempDeploymentPath)){return;}try{progress.log("Cleaning up temporary deployment files...","info");(0,_utils.runCommand)(`rm -rf "${tempDeploymentPath}"`);progress.log("Temporary files cleaned up successfully","success");}catch(error){progress.log(`Warning: Could not cleanup temporary directory: ${error.message}`,"warning");}}async function updateFileContents(projectPaths,androidConfig){const{newProjectPath}=projectPaths;const{oldProjectName,newProjectName,packageName}=androidConfig;progress.log("Updating file contents and names...","info");try{// STEP 1: Update packageName in build.gradle.kts and source files
57
57
  if(packageName){const OLD_PACKAGE="io.yourname.androidproject";progress.log(`Replacing package name: ${OLD_PACKAGE} → ${packageName}`,"info");// Update build.gradle.kts
58
58
  const buildGradlePath=_path.default.join(newProjectPath,"app","build.gradle.kts");if(_fs.default.existsSync(buildGradlePath)){let content=_fs.default.readFileSync(buildGradlePath,"utf8");content=content.replace(/namespace = "io\.yourname\.androidproject"/g,`namespace = "${packageName}"`);content=content.replace(/applicationId = "io\.yourname\.androidproject"/g,`applicationId = "${packageName}"`);_fs.default.writeFileSync(buildGradlePath,content,"utf8");progress.log(`✅ Updated build.gradle.kts with package: ${packageName}`,"success");}// Update source directory structure for all source sets
59
- const sourceSets=["main","test","noFcm","withFcm"];const newPackageParts=packageName.split(".");let totalUpdatedFiles=0;for(const sourceSet of sourceSets){const oldPackagePath=_path.default.join(newProjectPath,"app","src",sourceSet,"java","io","yourname","androidproject");if(_fs.default.existsSync(oldPackagePath)){const newPackagePath=_path.default.join(newProjectPath,"app","src",sourceSet,"java",...newPackageParts);// Create new package directory
59
+ const sourceSets=["main","test","noFcm","withFcm"];const newPackageParts=packageName.split(".");let totalUpdatedFiles=0;for(const sourceSet of sourceSets){const oldPackagePath=_path.default.join(newProjectPath,"app","src",sourceSet,"java","io","yourname","androidproject");if(_fs.default.existsSync(oldPackagePath)){const newPackagePath=_path.default.join(newProjectPath,"app","src",sourceSet,"java",...newPackageParts);const packagePathChanged=_path.default.resolve(oldPackagePath)!==_path.default.resolve(newPackagePath);if(packagePathChanged){// Create new package directory
60
60
  const newPackageDir=_path.default.dirname(newPackagePath);_fs.default.mkdirSync(newPackageDir,{recursive:true})// Move source files
61
- ;(0,_utils.runCommand)(`mv "${oldPackagePath}" "${newPackagePath}"`);progress.log(`✅ Moved ${sourceSet} source files to: ${newPackagePath}`,"success");// Update package declarations in source files
61
+ ;(0,_utils.runCommand)(`mv "${oldPackagePath}" "${newPackagePath}"`);progress.log(`✅ Moved ${sourceSet} source files to: ${newPackagePath}`,"success");}else{progress.log(`Package path unchanged for ${sourceSet}; skipping source directory move`,"info");}// Update package declarations in source files
62
62
  const findCommand=`find "${newPackagePath}" -type f \\( -name "*.kt" -o -name "*.java" \\)`;const sourceFiles=(0,_utils.runCommand)(findCommand).trim().split("\n").filter(f=>f.trim());let updatedCount=0;for(const file of sourceFiles){let content=_fs.default.readFileSync(file,"utf8");if(content.includes(OLD_PACKAGE)){content=content.replace(new RegExp(OLD_PACKAGE,"g"),packageName);_fs.default.writeFileSync(file,content,"utf8");updatedCount++;}}totalUpdatedFiles+=updatedCount;progress.log(`✅ Updated package declarations in ${updatedCount} ${sourceSet} files`,"success");// Clean up old empty directories
63
63
  try{;(0,_utils.runCommand)(`find "${_path.default.join(newProjectPath,"app","src",sourceSet,"java")}" -type d -empty -delete`);}catch(err){// Ignore cleanup errors
64
64
  }}}if(totalUpdatedFiles>0){progress.log(`✅ Total package declarations updated: ${totalUpdatedFiles} files`,"success");}// Update AndroidManifest.xml
65
65
  const manifestPath=_path.default.join(newProjectPath,"app","src","main","AndroidManifest.xml");if(_fs.default.existsSync(manifestPath)){let content=_fs.default.readFileSync(manifestPath,"utf8");if(content.includes(OLD_PACKAGE)){content=content.replace(new RegExp(OLD_PACKAGE,"g"),packageName);_fs.default.writeFileSync(manifestPath,content,"utf8");progress.log(`✅ Updated AndroidManifest.xml with package: ${packageName}`,"success");}}// Update proguard-rules.pro
66
66
  const proguardPath=_path.default.join(newProjectPath,"app","proguard-rules.pro");if(_fs.default.existsSync(proguardPath)){let content=_fs.default.readFileSync(proguardPath,"utf8");if(content.includes(OLD_PACKAGE)){content=content.replace(new RegExp(OLD_PACKAGE.replace(/\./g,"\\."),"g"),packageName);_fs.default.writeFileSync(proguardPath,content,"utf8");progress.log(`✅ Updated proguard-rules.pro with package: ${packageName}`,"success");}}}// STEP 2: Find and rename files that contain the old project name
67
- const findFilesCommand=`find "${newProjectPath}" -type f -name "*${oldProjectName}*"`;const fileResult=(0,_utils.runCommand)(findFilesCommand);if(fileResult.trim()){const filesToRename=fileResult.trim().split("\n").filter(file=>file.trim());for(const file of filesToRename){const newFileName=file.replace(new RegExp(oldProjectName,"g"),newProjectName);if(file!==newFileName){;(0,_utils.runCommand)(`mv "${file}" "${newFileName}"`);progress.log(`Renamed file: ${_path.default.basename(file)} → ${_path.default.basename(newFileName)}`,"success");}}}// STEP 3: Update file contents that reference the old project name
68
- const fileTypes=["*.gradle","*.gradle.kts","*.xml","*.json","*.properties","*.java","*.kt","*.kts","*.js","*.ts","*.md"];for(const fileType of fileTypes){try{const findContentCommand=`find "${newProjectPath}" -name "${fileType}" -type f`;const files=(0,_utils.runCommand)(findContentCommand);if(files.trim()){const fileList=files.trim().split("\n").filter(file=>file.trim());for(const file of fileList){try{const content=_fs.default.readFileSync(file,"utf8");if(content.includes(oldProjectName)){const updatedContent=content.replace(new RegExp(oldProjectName,"g"),newProjectName);_fs.default.writeFileSync(file,updatedContent,"utf8");progress.log(`Updated content in: ${_path.default.relative(newProjectPath,file)}`,"info");}}catch(fileError){progress.log(`Warning: Could not update file ${file}: ${fileError.message}`,"warning");}}}}catch(typeError){// Continue with other file types if one fails
67
+ const findFilesCommand=`find "${newProjectPath}" -type f -name "*${oldProjectName}*"`;const fileResult=(0,_utils.runCommand)(findFilesCommand);if(fileResult.trim()){const filesToRename=fileResult.trim().split("\n").filter(file=>file.trim());for(const file of filesToRename){const newFileName=file.split(oldProjectName).join(newProjectName);if(file!==newFileName){;(0,_utils.runCommand)(`mv "${file}" "${newFileName}"`);progress.log(`Renamed file: ${_path.default.basename(file)} → ${_path.default.basename(newFileName)}`,"success");}}}// STEP 3: Update file contents that reference the old project name
68
+ const fileTypes=["*.gradle","*.gradle.kts","*.xml","*.json","*.properties","*.java","*.kt","*.kts","*.js","*.ts","*.md"];for(const fileType of fileTypes){try{const findContentCommand=`find "${newProjectPath}" -name "${fileType}" -type f`;const files=(0,_utils.runCommand)(findContentCommand);if(files.trim()){const fileList=files.trim().split("\n").filter(file=>file.trim());for(const file of fileList){try{const content=_fs.default.readFileSync(file,"utf8");if(content.includes(oldProjectName)){const updatedContent=content.split(oldProjectName).join(newProjectName);_fs.default.writeFileSync(file,updatedContent,"utf8");progress.log(`Updated content in: ${_path.default.relative(newProjectPath,file)}`,"info");}}catch(fileError){progress.log(`Warning: Could not update file ${file}: ${fileError.message}`,"warning");}}}}catch(typeError){// Continue with other file types if one fails
69
69
  progress.log(`Warning: Error processing ${fileType} files: ${typeError.message}`,"warning");}}progress.log("File content updates completed","success");}catch(error){throw new Error(`Error updating file contents: ${error.message}`);}}async function buildAndroidAAB(configPathOrConfig){let androidConfig;let projectPaths;let backupPath;let tempDeploymentPath;try{// Initialize configuration - handle both config path and direct config object
70
70
  progress.start("config");if(typeof configPathOrConfig==="string"){const{android}=await initializeConfig(configPathOrConfig);androidConfig=android;}else{// Direct config object passed (from buildAppAndroid.js)
71
71
  // All defaults should already be applied in buildAppAndroid.js
@@ -3,7 +3,8 @@ const existingConfigFile=_fs.default.readFileSync(configPath,"utf8");const exist
3
3
  const updatedConfig={...existingConfig,// Preserve all existing keys
4
4
  WEBVIEW_CONFIG:{...existingConfig.WEBVIEW_CONFIG,ios:newConfig.WEBVIEW_CONFIG.ios}// Update only WEBVIEW_CONFIG
5
5
  };// Save the merged config
6
- _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);}}async function setupIOSEnvironment(){try{progress.start("platform");if(process.platform!=="darwin"){progress.log("iOS Simulator is only available on macOS.");process.exit(1);}progress.complete("platform");progress.start("config");const{WEBVIEW_CONFIG}=await initializeConfig();progress.complete("config");progress.start("simulator");await configureSimulator(WEBVIEW_CONFIG);progress.complete("simulator");progress.start("launch");await launchIOSSimulator(WEBVIEW_CONFIG.ios.simulatorName);progress.complete("launch");progress.start("saveConfig");const config=await(0,_utils.validateAndCompleteConfig)("ios",configPath);progress.complete("saveConfig");progress.start("setupServer");await setupServer(configPath);progress.start("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:"ios: iOS-specific configuration",indent:1,prefix:"└─ ",color:"gray"},{text:"buildType: Build type (debug/release)",indent:2,prefix:"├─ ",color:"gray"},{text:"appBundleId: iOS application bundle identifier",indent:2,prefix:"├─ ",color:"gray"},{text:"simulatorName: Selected iOS simulator 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);}}async function displayPaginatedList(items,startIndex){const endIndex=Math.min(startIndex+ITEMS_PER_PAGE,items.length);for(let i=startIndex;i<endIndex;i++){console.log(`${i+1}. ${items[i]}`);}if(endIndex<items.length){console.log(`\nShowing ${startIndex+1}-${endIndex} of ${items.length} items`);console.log('Type "more" to see more items, or enter your selection: ');return true;}return false;}async function getRuntime(){const runtimesOutput=(0,_child_process.execSync)("xcrun simctl list runtimes -j").toString();const parsedRuntimes=JSON.parse(runtimesOutput).runtimes;const availableRuntime=parsedRuntimes.find(runtime=>runtime.isAvailable||runtime.availability==="(available)"||runtime.availability==="available");if(!availableRuntime){const runningSimulators=(0,_child_process.execSync)("xcrun simctl list devices booted").toString();if(runningSimulators.includes("(Booted)")&&parsedRuntimes.length>0){console.log(`Using runtime: ${parsedRuntimes[0].name}`);return parsedRuntimes[0];}console.error("No available iOS runtime found. Please install one through Xcode.");process.exit(1);}console.log(`Using runtime: ${availableRuntime.name}`);return availableRuntime;}async function configureSimulator(config){progress.pause();// Get the current runtime first
6
+ _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);}}async function setupIOSEnvironment(){try{progress.start("platform");if(process.platform!=="darwin"){progress.log("iOS Simulator is only available on macOS.");process.exit(1);}progress.complete("platform");progress.start("config");const{WEBVIEW_CONFIG}=await initializeConfig();progress.complete("config");progress.start("simulator");await configureSimulator(WEBVIEW_CONFIG);progress.complete("simulator");progress.start("launch");await launchIOSSimulator(WEBVIEW_CONFIG.ios.simulatorName);progress.complete("launch");progress.start("saveConfig");const config=await(0,_utils.validateAndCompleteConfig)("ios",configPath);progress.complete("saveConfig");progress.start("setupServer");await setupServer(configPath);progress.start("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:"ios: iOS-specific configuration",indent:1,prefix:"└─ ",color:"gray"},{text:"buildType: Build type (debug/release)",indent:2,prefix:"├─ ",color:"gray"},{text:"appBundleId: iOS application bundle identifier",indent:2,prefix:"├─ ",color:"gray"},{text:"simulatorName: Selected iOS simulator 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);}}async function displayPaginatedList(items,startIndex){const endIndex=Math.min(startIndex+ITEMS_PER_PAGE,items.length);for(let i=startIndex;i<endIndex;i++){console.log(`${i+1}. ${items[i]}`);}if(endIndex<items.length){console.log(`\nShowing ${startIndex+1}-${endIndex} of ${items.length} items`);console.log('Type "more" to see more items, or enter your selection: ');return true;}return false;}async function getRuntime(){const runtimesOutput=(0,_child_process.execSync)("xcrun simctl list runtimes -j").toString();const parsedRuntimes=JSON.parse(runtimesOutput).runtimes;const availableRuntime=parsedRuntimes.find(runtime=>runtime.isAvailable||runtime.availability==="(available)"||runtime.availability==="available");if(!availableRuntime){const runningSimulators=(0,_child_process.execSync)("xcrun simctl list devices booted").toString();if(runningSimulators.includes("(Booted)")&&parsedRuntimes.length>0){console.log(`Using runtime: ${parsedRuntimes[0].name}`);return parsedRuntimes[0];}console.error("No available iOS runtime found. Please install one through Xcode.");process.exit(1);}console.log(`Using runtime: ${availableRuntime.name}`);return availableRuntime;}async function configureSimulator(config){// If a simulator is already configured, skip the interactive picker
7
+ if(config.ios.simulatorName){progress.log(`Using configured simulator: ${config.ios.simulatorName}`);return;}progress.pause();// Get the current runtime first
7
8
  const runtime=await getRuntime();// Get simulators specifically for this runtime
8
9
  const simulatorsJson=JSON.parse((0,_child_process.execSync)("xcrun simctl list devices -j").toString());const runtimeSimulators=simulatorsJson.devices[runtime.identifier]||[];// Filter for available simulators only
9
10
  const availableSimulators=runtimeSimulators.filter(sim=>sim.availability==="(available)"||sim.isAvailable).map(sim=>`${sim.name} (${sim.udid})`);if(availableSimulators.length===0){progress.log(`No available simulators found for runtime: ${runtime.name}`,"error");progress.resume();process.exit(1);return;}let startIndex=0;console.log(`\nAvailable simulators for ${runtime.name} (or type 'new' to create a new one):`);// eslint-disable-next-line no-constant-condition
@@ -26,9 +27,10 @@ const simulatorName=selectedSimulator.split(" (")[0];config.ios.simulatorName=si
26
27
  // }
27
28
  // }
28
29
  async function launchIOSSimulator(simulatorName){progress.log("Launching iOS Simulator...");try{// Get all simulators including both available and booted ones
29
- const allSimulatorInfo=(0,_child_process.execSync)("xcrun simctl list devices -j").toString();const simulatorsJson=JSON.parse(allSimulatorInfo);// Search through all runtimes and their devices
30
- let foundSimulator=null;let foundSimulatorId=null;let isBooted=false;// Iterate through all runtimes and their devices
31
- Object.values(simulatorsJson.devices).forEach(devices=>{devices.forEach(device=>{if(device.name===simulatorName){foundSimulator=device;foundSimulatorId=device.udid;isBooted=device.state==="Booted";}});});if(!foundSimulator){console.log(`Configured simulator "${simulatorName}" not found.`);return;}if(!isBooted){console.log(`Booting simulator: ${simulatorName}`);(0,_utils.runCommand)(`xcrun simctl boot ${foundSimulatorId}`);}else{console.log(`Simulator ${simulatorName} is already booted`);}// Open Simulator.app and focus
30
+ const allSimulatorInfo=(0,_child_process.execSync)("xcrun simctl list devices -j").toString();const simulatorsJson=JSON.parse(allSimulatorInfo);const runtimesOutput=(0,_child_process.execSync)("xcrun simctl list runtimes -j").toString();const availableRuntimeIds=new Set(JSON.parse(runtimesOutput).runtimes.filter(r=>r.isAvailable||r.availability==="(available)"||r.availability==="available").map(r=>r.identifier));// Search through all runtimes and their devices
31
+ let foundSimulator=null;let foundSimulatorId=null;let isBooted=false;// Prefer devices under an available runtime; fall back to already-booted ones
32
+ Object.entries(simulatorsJson.devices).forEach(([runtimeId,devices])=>{const isAvailableRuntime=availableRuntimeIds.has(runtimeId);devices.forEach(device=>{if(device.name===simulatorName){const alreadyBooted=device.state==="Booted";// Take this match if: no match yet, or this runtime is available and current isn't, or device is already booted
33
+ if(!foundSimulator||isAvailableRuntime&&!foundSimulator._fromAvailableRuntime||alreadyBooted){foundSimulator={...device,_fromAvailableRuntime:isAvailableRuntime};foundSimulatorId=device.udid;isBooted=alreadyBooted;}}});});if(!foundSimulator){console.log(`Configured simulator "${simulatorName}" not found.`);return;}if(!isBooted){console.log(`Booting simulator: ${simulatorName}`);(0,_utils.runCommand)(`xcrun simctl boot ${foundSimulatorId}`);}else{console.log(`Simulator ${simulatorName} is already booted`);}// Open Simulator.app and focus
32
34
  progress.log("Opening Simulator.app...");(0,_utils.runCommand)("open -a Simulator");// Give the simulator a moment to open/focus
33
35
  await new Promise(resolve=>setTimeout(resolve,1000));// Activate the Simulator.app window to bring it to front
34
36
  (0,_utils.runCommand)("osascript -e 'tell application \"Simulator\" to activate'");console.log("iOS Simulator launched successfully.");}catch(error){console.error("Failed to launch iOS Simulator. Error:",error.message);}// process.exit(0);
@@ -1,9 +1,8 @@
1
- "use strict";const{exec,execSync}=require("child_process");const fs=require("fs");/**
1
+ "use strict";const{exec,execFile,execSync}=require("child_process");const fs=require("fs");/**
2
2
  * Get local IP address on macOS using ifconfig
3
3
  */function getLocalIPAddress(){try{const command=`ifconfig | grep "inet " | grep -v 127.0.0.1 | head -n 1 | awk '{print $2}'`;return execSync(command).toString().trim();}catch(error){console.warn("Could not get local IP, using localhost");return"localhost";}}/**
4
4
  * Check if server is running on given port
5
- */async function isServerRunning(port){return new Promise(resolve=>{const command=`lsof -i :${port}`;// eslint-disable-next-line security/detect-child-process
6
- exec(command,(error,stdout)=>{if(error){resolve(false);return;}resolve(stdout.includes("LISTEN"));});});}/**
5
+ */async function isServerRunning(port){return new Promise(resolve=>{const normalizedPort=Number(port);if(!Number.isInteger(normalizedPort)||normalizedPort<1||normalizedPort>65535){resolve(false);return;}execFile("lsof",["-i",`:${normalizedPort}`],(error,stdout)=>{if(error){resolve(false);return;}resolve(stdout.includes("LISTEN"));});});}/**
7
6
  * Start server in background using npm start
8
7
  */function startServerBackground(){console.log("Starting server in background...");const serverProcess=exec("npm start",{detached:true,stdio:"ignore"});if(serverProcess.pid){serverProcess.unref();// Allow parent to exit
9
8
  console.log("Server started in background");}}/**
@@ -1,4 +1,7 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.commandExists=commandExists;exports.promptUser=promptUser;exports.runCommand=runCommand;exports.runInteractiveCommand=runInteractiveCommand;exports.runSdkManagerCommand=runSdkManagerCommand;exports.validateAndCompleteConfig=validateAndCompleteConfig;var _child_process=require("child_process");var _readline=_interopRequireDefault(require("readline"));var _fs=_interopRequireDefault(require("fs"));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const rl=_readline.default.createInterface({input:process.stdin,output:process.stdout});function runCommand(command){try{return(0,_child_process.execSync)(command,{encoding:"utf8"});}catch(error){console.error(`Error executing command: ${command}`);console.error(`Error message: ${error.message}`);throw error;}}function commandExists(command){try{(0,_child_process.execSync)(`which ${command}`,{stdio:"ignore"});return true;}catch(error){return false;}}async function promptUser(question){return new Promise(resolve=>{rl.question(question,answer=>{resolve(answer.trim());});});}async function runInteractiveCommand(command,args,promptResponses={}){return new Promise((resolve,reject)=>{const process=(0,_child_process.spawn)(command,args,{stdio:["pipe","pipe","pipe"]});let buffer="";let outputBuffer="";process.stdout.on("data",data=>{buffer+=data.toString();outputBuffer+=data.toString();console.log(data.toString());handlePrompts(process,buffer,promptResponses);});process.stderr.on("data",data=>{buffer+=data.toString();outputBuffer+=data.toString();console.error(data.toString());handlePrompts(process,buffer,promptResponses);});process.on("close",code=>{if(code===0){resolve(outputBuffer);}else{reject(new Error(`Command failed with exit code ${code}`));}});});}function handlePrompts(process,buffer,promptResponses){for(const[prompt,response]of Object.entries(promptResponses)){if(buffer.includes(prompt)){process.stdin.write(response+"\n");return;}}}async function runSdkManagerCommand(sdkManagerPath,args){const promptResponses={"(y/N)":"y","Accept? (y/N):":"y"};return runInteractiveCommand(sdkManagerPath,args,promptResponses);}async function validateAndCompleteConfig(platform,configPath){// Read existing config
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.commandExists=commandExists;exports.promptUser=promptUser;exports.runCommand=runCommand;exports.runInteractiveCommand=runInteractiveCommand;exports.runSdkManagerCommand=runSdkManagerCommand;exports.validateAndCompleteConfig=validateAndCompleteConfig;var _child_process=require("child_process");var _readline=_interopRequireDefault(require("readline"));var _fs=_interopRequireDefault(require("fs"));function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const rl=_readline.default.createInterface({input:process.stdin,output:process.stdout});function runCommand(command){try{// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - Native setup/build uses fixed internal command strings; preserve existing shell behavior.
2
+ return(0,_child_process.execSync)(command,{encoding:"utf8"});}catch(error){console.error(`Error executing command: ${command}`);console.error(`Error message: ${error.message}`);throw error;}}function commandExists(command){try{// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - Utility checks known local tool names during native setup.
3
+ (0,_child_process.execSync)(`which ${command}`,{stdio:"ignore"});return true;}catch(error){return false;}}async function promptUser(question){return new Promise(resolve=>{rl.question(question,answer=>{resolve(answer.trim());});});}async function runInteractiveCommand(command,args,promptResponses={}){return new Promise((resolve,reject)=>{// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - Native build commands are fixed internal commands passed with argv arrays.
4
+ const process=(0,_child_process.spawn)(command,args,{stdio:["pipe","pipe","pipe"]});let buffer="";let outputBuffer="";process.stdout.on("data",data=>{buffer+=data.toString();outputBuffer+=data.toString();console.log(data.toString());handlePrompts(process,buffer,promptResponses);});process.stderr.on("data",data=>{buffer+=data.toString();outputBuffer+=data.toString();console.error(data.toString());handlePrompts(process,buffer,promptResponses);});process.on("close",code=>{if(code===0){resolve(outputBuffer);}else{reject(new Error(`Command failed with exit code ${code}`));}});});}function handlePrompts(process,buffer,promptResponses){for(const[prompt,response]of Object.entries(promptResponses)){if(buffer.includes(prompt)){process.stdin.write(response+"\n");return;}}}async function runSdkManagerCommand(sdkManagerPath,args){const promptResponses={"(y/N)":"y","Accept? (y/N):":"y"};return runInteractiveCommand(sdkManagerPath,args,promptResponses);}async function validateAndCompleteConfig(platform,configPath){// Read existing config
2
5
  let config;try{const configContent=_fs.default.readFileSync(configPath,"utf8");config=JSON.parse(configContent);}catch(error){console.error(`Error reading config file: ${error.message}`);config={};}// Initialize WEBVIEW_CONFIG if it doesn't exist
3
6
  if(!config.WEBVIEW_CONFIG){config.WEBVIEW_CONFIG={};}// Check if WEBVIEW_CONFIG already has all required fields
4
7
  const webviewConfig=config.WEBVIEW_CONFIG;const commonFields={port:"Enter port number (e.g., 3005): "};const platformConfigs={android:{buildType:"Enter Android build type (debug/release): ",sdkPath:"Enter Android SDK path: ",emulatorName:"Enter Android emulator name (e.g., Small_Phone_API_35): ",cachePattern:"Enter Cache pattern (e.g., *.css): "},ios:{buildType:"Enter iOS build type (debug/release): ",appBundleId:"Enter iOS bundle ID (e.g., com.test.test): ",simulatorName:"Enter iOS simulator name (e.g., iPhone 16 Pro): ",cachePattern:"Enter Cache pattern (e.g., *.css): "}};if(!platformConfigs[platform]){throw new Error('Invalid platform. Must be either "android" or "ios"');}// Check if all required fields are present
@@ -0,0 +1 @@
1
+ "use strict";/* eslint-disable no-restricted-globals */const MANIFEST_URL="/catalyst-offline-manifest.json";const OFFLINE_URL="/offline.html";const CACHE_PREFIX="catalyst-offline";const MANIFEST_CACHE=`${CACHE_PREFIX}-manifest`;self.addEventListener("install",event=>{event.waitUntil((async()=>{await loadManifest(true);await cacheOfflineFallback();await self.skipWaiting();})());});self.addEventListener("activate",event=>{event.waitUntil((async()=>{await cleanupOldCaches();await self.clients.claim();})());});self.addEventListener("fetch",event=>{const{request}=event;if(request.method!=="GET")return;if(request.mode==="navigate"||request.destination==="document"){event.respondWith(handleNavigation(request));return;}if(["script","style","font","image"].includes(request.destination)){event.respondWith(handleAsset(request));}});async function loadManifest(preferNetwork){const cache=await caches.open(MANIFEST_CACHE);if(preferNetwork){try{const response=await fetch(MANIFEST_URL,{cache:"no-store"});if(response.ok){await cache.put(MANIFEST_URL,response.clone());const manifest=await response.json();cleanupOldCaches(manifest);return manifest;}}catch(_){}}const cached=await cache.match(MANIFEST_URL);if(cached)return cached.json();return{buildId:"unknown",routes:[]};}async function cacheOfflineFallback(){try{const cache=await caches.open(`${CACHE_PREFIX}-fallback`);const response=await fetch(OFFLINE_URL,{cache:"no-store"});if(response.ok)await cache.put(OFFLINE_URL,response);}catch(_){}}function routeCacheName(manifest){return`${CACHE_PREFIX}-routes-${manifest.buildId}`;}function assetCacheName(manifest){return`${CACHE_PREFIX}-assets-${manifest.buildId}`;}async function cleanupOldCaches(manifest){manifest=manifest||(await loadManifest(false));if(!manifest.buildId||manifest.buildId==="unknown")return;const keep=new Set([MANIFEST_CACHE,`${CACHE_PREFIX}-fallback`,routeCacheName(manifest),assetCacheName(manifest)]);const cacheNames=await caches.keys();await Promise.all(cacheNames.filter(name=>name.startsWith(CACHE_PREFIX)&&!keep.has(name)).map(name=>caches.delete(name)));}function isOfflineRoute(url,manifest){const{pathname}=new URL(url);return(manifest.routes||[]).some(route=>{try{return new RegExp(route.regex).test(pathname);}catch(_){return false;}});}function canStore(response){return!!(response&&(response.ok||response.type==="opaque"));}async function handleNavigation(request){const manifest=await loadManifest(true);if(!isOfflineRoute(request.url,manifest))return fetch(request);const cache=await caches.open(routeCacheName(manifest));try{const response=await fetch(request);const contentType=response.headers.get("content-type")||"";if(canStore(response)&&contentType.includes("text/html")){await cache.put(request.url,response.clone());}return response;}catch(_){const cached=await cache.match(request.url);if(cached)return cached;const offlineResponse=await caches.match(OFFLINE_URL);if(offlineResponse)return offlineResponse;return fetch(OFFLINE_URL);}}async function handleAsset(request){const manifest=await loadManifest(false);const cache=await caches.open(assetCacheName(manifest));const cached=await cache.match(request);if(cached)return cached;const response=await fetch(request);if(canStore(response))await cache.put(request,response.clone());return response;}
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.registerCatalystServiceWorker=registerCatalystServiceWorker;let registered=false;function isNativeWebView(){if(typeof window==="undefined")return false;return!!(window.NativeBridge||window.webkit?.messageHandlers?.NativeBridge);}function registerCatalystServiceWorker(){if(registered)return;if(typeof window==="undefined"||typeof navigator==="undefined")return;if(process.env.NODE_ENV!=="production")return;if(isNativeWebView())return;if(!("serviceWorker"in navigator))return;registered=true;const register=()=>{navigator.serviceWorker.register("/catalyst-sw.js",{scope:"/",updateViaCache:"none"}).catch(()=>{registered=false;});};if(document.readyState==="complete"){register();}else{window.addEventListener("load",register,{once:true});}}
package/dist/otel.js CHANGED
@@ -1,4 +1,72 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _api=require("@opentelemetry/api");var _sdkNode=require("@opentelemetry/sdk-node");var _resources=require("@opentelemetry/resources");var _sdkTraceNode=require("@opentelemetry/sdk-trace-node");var _sdkMetrics=require("@opentelemetry/sdk-metrics");var _exporterTraceOtlpHttp=require("@opentelemetry/exporter-trace-otlp-http");var _exporterMetricsOtlpHttp=require("@opentelemetry/exporter-metrics-otlp-http");var _autoInstrumentationsNode=require("@opentelemetry/auto-instrumentations-node");var _semanticConventions=require("@opentelemetry/semantic-conventions");function init(config={}){const{serviceName="catalyst-server",serviceVersion="1.0.0",environment="development",traceUrl="http://localhost:4318/v1/traces",metricUrl="http://localhost:4318/v1/metrics",traceHeaders={},metricHeaders={},exportIntervalMillis=10000,exportTimeoutMillis=10000}=config;try{const otlpTraceExporter=new _exporterTraceOtlpHttp.OTLPTraceExporter({url:traceUrl,headers:traceHeaders});const otlpMetricExporter=new _exporterMetricsOtlpHttp.OTLPMetricExporter({url:metricUrl,headers:metricHeaders});const sdk=new _sdkNode.NodeSDK({resource:(0,_resources.resourceFromAttributes)({[_semanticConventions.ATTR_SERVICE_NAME]:serviceName,[_semanticConventions.ATTR_SERVICE_VERSION]:serviceVersion,[_semanticConventions.ATTR_DEPLOYMENT_ENVIRONMENT]:environment}),spanProcessor:new _sdkTraceNode.BatchSpanProcessor(otlpTraceExporter,{exportTimeoutMillis,scheduledDelayMillis:exportIntervalMillis,maxQueueSize:100,maxExportBatchSize:10}),metricReader:new _sdkMetrics.PeriodicExportingMetricReader({exporter:otlpMetricExporter,exportIntervalMillis}),instrumentations:[(0,_autoInstrumentationsNode.getNodeAutoInstrumentations)()]});sdk.start();logger.info("✅ OpenTelemetry started successfully");const meter=initializeCustomMetrics(serviceName,serviceVersion);const gracefulShutdown=signal=>{logger.info(`📡 Received ${signal}, shutting down OpenTelemetry gracefully...`);sdk.shutdown().then(()=>logger.info("✅ OpenTelemetry shutdown completed")).catch(error=>{logger.error("❌ Error terminating OpenTelemetry:",error);}).finally(()=>process.exit());};process.on("SIGTERM",()=>gracefulShutdown("SIGTERM"));process.on("SIGINT",()=>gracefulShutdown("SIGINT"));return{sdk,meter};}catch(error){logger.error("❌ Failed to initialize OpenTelemetry:",error);throw error;}}function initializeCustomMetrics(serviceName,serviceVersion){let customMetrics={};const meter=_api.metrics.getMeter(serviceName,serviceVersion);// CPU usage gauge
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;exports.withObservability=withObservability;exports.withSyncObservability=withSyncObservability;function _interopRequireWildcard(e,t){if("function"==typeof WeakMap)var r=new WeakMap(),n=new WeakMap();return(_interopRequireWildcard=function(e,t){if(!t&&e&&e.__esModule)return e;var o,i,f={__proto__:null,default:e};if(null===e||"object"!=typeof e&&"function"!=typeof e)return f;if(o=t?n:r){if(o.has(e))return o.get(e);o.set(e,f);}for(const t in e)"default"!==t&&{}.hasOwnProperty.call(e,t)&&((i=(o=Object.defineProperty)&&Object.getOwnPropertyDescriptor(e,t))&&(i.get||i.set)?o(f,t,i):f[t]=e[t]);return f;})(e,t);}// OpenTelemetry is an OPT-IN feature. Nothing from "@opentelemetry/*" is
2
+ // imported at module-eval time on purpose: the packages are declared as
3
+ // (optional) peerDependencies and may not be installed in the consuming app.
4
+ //
5
+ // All OTel packages are loaded lazily via dynamic import() inside init(), and
6
+ // only when OTEL_ENABLE === "true". Consequences:
7
+ // - Apps that keep OTEL disabled don't need the packages installed.
8
+ // - Bundlers don't pull the packages into the static import graph, so
9
+ // `import "catalyst-core/otel"` no longer forces them to resolve
10
+ // "@opentelemetry/exporter-trace-otlp-http" & friends at build time.
11
+ // (Pair this with marking "@opentelemetry/*" external in the bundler so the
12
+ // dynamic import() chunk isn't resolved at build time either — see the SSR
13
+ // webpack configs under src/webpack.)
14
+ // Set once by init() (when enabled) to the loaded "@opentelemetry/api" module.
15
+ // The wrappers below read it lazily so they never need a static OTel import.
16
+ let _otelApi=null;const isOtelEnabled=()=>process.env.OTEL_ENABLE==="true";async function init(config={}){if(!isOtelEnabled()){return{sdk:null,meter:null};}const{serviceName="catalyst-server",serviceVersion="1.0.0",environment="development",traceUrl="http://localhost:4317",metricUrl="http://localhost:4317",traceProtocol="grpc",// "grpc" or "http"
17
+ metricProtocol,// "grpc" or "http" - if not provided, metrics will be disabled
18
+ traceHeaders={},metricHeaders={},batchProcessorConfig={},exportIntervalMillis=10000,instrumentations,samplingRate=1.0,grpcCredentials}=config;try{// Lazy-load every OpenTelemetry package. These dynamic imports run only
19
+ // when OTEL_ENABLE=true, so the packages are required at runtime solely
20
+ // for opted-in deployments.
21
+ const[otelApi,{NodeSDK},{resourceFromAttributes},{BatchSpanProcessor,TraceIdRatioBasedSampler,ParentBasedSampler},{PeriodicExportingMetricReader},{OTLPTraceExporter},{OTLPMetricExporter},{getNodeAutoInstrumentations},{OTLPTraceExporter:OTLPTraceExporterHTTP},{OTLPMetricExporter:OTLPMetricExporterHTTP},{ATTR_SERVICE_NAME,ATTR_SERVICE_VERSION,ATTR_DEPLOYMENT_ENVIRONMENT}]=await Promise.all([Promise.resolve().then(()=>_interopRequireWildcard(require("@opentelemetry/api"))),Promise.resolve().then(()=>_interopRequireWildcard(require("@opentelemetry/sdk-node"))),Promise.resolve().then(()=>_interopRequireWildcard(require("@opentelemetry/resources"))),Promise.resolve().then(()=>_interopRequireWildcard(require("@opentelemetry/sdk-trace-node"))),Promise.resolve().then(()=>_interopRequireWildcard(require("@opentelemetry/sdk-metrics"))),Promise.resolve().then(()=>_interopRequireWildcard(require("@opentelemetry/exporter-trace-otlp-grpc"))),Promise.resolve().then(()=>_interopRequireWildcard(require("@opentelemetry/exporter-metrics-otlp-grpc"))),Promise.resolve().then(()=>_interopRequireWildcard(require("@opentelemetry/auto-instrumentations-node"))),Promise.resolve().then(()=>_interopRequireWildcard(require("@opentelemetry/exporter-trace-otlp-http"))),Promise.resolve().then(()=>_interopRequireWildcard(require("@opentelemetry/exporter-metrics-otlp-http"))),Promise.resolve().then(()=>_interopRequireWildcard(require("@opentelemetry/semantic-conventions")))]);// Enables the withObservability / withSyncObservability wrappers.
22
+ _otelApi=otelApi;const traceExporters={OTLPTraceExporter,OTLPTraceExporterHTTP};const metricExporters={OTLPMetricExporter,OTLPMetricExporterHTTP};const otlpTraceExporter=createTraceExporter(traceExporters,traceProtocol,traceUrl,traceHeaders,grpcCredentials);// Create metric exporter only if metricProtocol is specified
23
+ let metricReader=null;if(metricProtocol){const otlpMetricExporter=createMetricExporter(metricExporters,metricProtocol,metricUrl,metricHeaders,grpcCredentials);metricReader=new PeriodicExportingMetricReader({exporter:otlpMetricExporter,exportIntervalMillis});}const sampler=new ParentBasedSampler({root:new TraceIdRatioBasedSampler(samplingRate)});const sdkConfig={resource:resourceFromAttributes({[ATTR_SERVICE_NAME]:serviceName,[ATTR_SERVICE_VERSION]:serviceVersion,[ATTR_DEPLOYMENT_ENVIRONMENT]:environment}),spanProcessor:new BatchSpanProcessor(otlpTraceExporter,batchProcessorConfig),instrumentations:instrumentations??[getNodeAutoInstrumentations()],sampler};// Add metric reader only if metrics are enabled
24
+ if(metricReader){sdkConfig.metricReader=metricReader;}const sdk=new NodeSDK(sdkConfig);sdk.start();logger.info("✅ OpenTelemetry started successfully");// Initialize custom metrics only if metrics are enabled
25
+ let meter=null;if(metricProtocol){meter=initializeCustomMetrics(otelApi,serviceName,serviceVersion);}const gracefulShutdown=signal=>{logger.info(`📡 Received ${signal}, shutting down OpenTelemetry gracefully...`);sdk.shutdown().then(()=>logger.info("✅ OpenTelemetry shutdown completed")).catch(error=>{logger.error("❌ Error terminating OpenTelemetry:",error);}).finally(()=>process.exit());};process.on("SIGTERM",()=>gracefulShutdown("SIGTERM"));process.on("SIGINT",()=>gracefulShutdown("SIGINT"));return{sdk,meter};}catch(error){logger.error("❌ Failed to initialize OpenTelemetry:",error);throw error;}}/**
26
+ * Creates a trace exporter based on the specified protocol
27
+ * @param {object} exporters - Lazily-loaded exporter constructors { OTLPTraceExporter, OTLPTraceExporterHTTP }
28
+ * @param {string} protocol - "grpc" or "http"
29
+ * @param {string} url - Exporter endpoint URL
30
+ * @param {object} headers - Headers to include in requests
31
+ * @param {Function} [grpcCredentials] - gRPC Credentials (optional)
32
+ * @returns {object} Configured trace exporter
33
+ */function createTraceExporter(exporters,protocol,url,headers={},grpcCredentials){const{OTLPTraceExporter,OTLPTraceExporterHTTP}=exporters;if(protocol.toLowerCase()==="http"){logger.info(`📡 Creating HTTP trace exporter for URL: ${url}`);return new OTLPTraceExporterHTTP({url:url,headers:headers});}else if(protocol.toLowerCase()==="grpc"){logger.info(`📡 Creating gRPC trace exporter for URL: ${url}`);return new OTLPTraceExporter({url:url,headers:headers,credentials:grpcCredentials});}else{throw new Error(`❌ Unsupported trace protocol: ${protocol}. Supported protocols are "grpc" and "http"`);}}/**
34
+ * Creates a metric exporter based on the specified protocol
35
+ * @param {object} exporters - Lazily-loaded exporter constructors { OTLPMetricExporter, OTLPMetricExporterHTTP }
36
+ * @param {string} protocol - "grpc" or "http"
37
+ * @param {string} url - Exporter endpoint URL
38
+ * @param {object} headers - Headers to include in requests
39
+ * @param {Function} [grpcCredentials] - gRPC Credentials (optional)
40
+ * @returns {object} Configured metric exporter
41
+ */function createMetricExporter(exporters,protocol,url,headers={},grpcCredentials){const{OTLPMetricExporter,OTLPMetricExporterHTTP}=exporters;if(protocol.toLowerCase()==="http"){logger.info(`📊 Creating HTTP metric exporter for URL: ${url}`);return new OTLPMetricExporterHTTP({url:url,headers:headers});}else if(protocol.toLowerCase()==="grpc"){logger.info(`📊 Creating gRPC metric exporter for URL: ${url}`);return new OTLPMetricExporter({url:url,headers:headers,credentials:grpcCredentials});}else{throw new Error(`❌ Unsupported metric protocol: ${protocol}. Supported protocols are "grpc" and "http"`);}}/**
42
+ * @param {object} otelApi - The loaded "@opentelemetry/api" module
43
+ */function initializeCustomMetrics(otelApi,serviceName,serviceVersion){let customMetrics={};const meter=otelApi.metrics.getMeter(serviceName,serviceVersion);// CPU usage gauge
2
44
  customMetrics.cpuUsage=meter.createObservableGauge("process_cpu_usage_percent",{description:"Current CPU usage percentage"});// Memory usage gauges
3
45
  customMetrics.memoryUsage=meter.createObservableGauge("process_memory_usage_bytes",{description:"Current memory usage in bytes",unit:"bytes"});customMetrics.memoryHeapUsed=meter.createObservableGauge("process_memory_heap_used_bytes",{description:"Current heap memory used in bytes",unit:"bytes"});customMetrics.memoryHeapTotal=meter.createObservableGauge("process_memory_heap_total_bytes",{description:"Current heap memory total in bytes",unit:"bytes"});let lastCpuUsage=process.cpuUsage();let lastMeasureTime=process.hrtime.bigint();customMetrics.cpuUsage.addCallback(result=>{const currentCpuUsage=process.cpuUsage(lastCpuUsage);const currentTime=process.hrtime.bigint();const timeDiff=Number(currentTime-lastMeasureTime)/1000000;// Convert to milliseconds
4
- const cpuPercent=(currentCpuUsage.user+currentCpuUsage.system)/1000/timeDiff*100;result.observe(cpuPercent);lastCpuUsage=process.cpuUsage();lastMeasureTime=currentTime;});customMetrics.memoryUsage.addCallback(result=>{const memUsage=process.memoryUsage();result.observe(memUsage.rss,{type:"rss"});result.observe(memUsage.external,{type:"external"});result.observe(memUsage.arrayBuffers,{type:"arrayBuffers"});});customMetrics.memoryHeapUsed.addCallback(result=>{const memUsage=process.memoryUsage();result.observe(memUsage.heapUsed);});customMetrics.memoryHeapTotal.addCallback(result=>{const memUsage=process.memoryUsage();result.observe(memUsage.heapTotal);});return meter;}var _default=exports.default={init};
46
+ const cpuPercent=(currentCpuUsage.user+currentCpuUsage.system)/1000/timeDiff*100;result.observe(cpuPercent);lastCpuUsage=process.cpuUsage();lastMeasureTime=currentTime;});customMetrics.memoryUsage.addCallback(result=>{const memUsage=process.memoryUsage();result.observe(memUsage.rss,{type:"rss"});result.observe(memUsage.external,{type:"external"});result.observe(memUsage.arrayBuffers,{type:"arrayBuffers"});});customMetrics.memoryHeapUsed.addCallback(result=>{const memUsage=process.memoryUsage();result.observe(memUsage.heapUsed);});customMetrics.memoryHeapTotal.addCallback(result=>{const memUsage=process.memoryUsage();result.observe(memUsage.heapTotal);});return meter;}/**
47
+ * Wraps a synchronous function and measures total execution time.
48
+ * Creates a single OpenTelemetry span per function call.
49
+ * Use this instead of withObservability when the wrapped function is synchronous,
50
+ * so the return type and call sites remain unchanged.
51
+ *
52
+ * When OTEL is disabled the original function is returned unchanged, so there is
53
+ * zero overhead and no dependency on any @opentelemetry/* package.
54
+ *
55
+ * @param {string} serviceName - The name of the service
56
+ * @param {Function} fn - The synchronous function to wrap
57
+ * @param {string} name - Span name (optional)
58
+ * @returns {Function} Wrapped function (still synchronous)
59
+ */function withSyncObservability(serviceName,fn,name){if(!isOtelEnabled())return fn;const spanName=name||fn.name||"anonymousFunction";return function(...args){// init() may not have populated the API yet — run untraced rather than throw.
60
+ const api=_otelApi;if(!api)return fn(...args);const tracer=api.trace.getTracer(serviceName);return tracer.startActiveSpan(spanName,span=>{try{return fn(...args);}catch(err){span.recordException(err);span.setStatus({code:2,message:err.message});throw err;}finally{span.end();}});};}/**
61
+ * Wraps a function (sync or async) and measures total execution time.
62
+ * Creates a single OpenTelemetry span per function call.
63
+ *
64
+ * When OTEL is disabled the original function is returned unchanged, so there is
65
+ * zero overhead and no dependency on any @opentelemetry/* package.
66
+ *
67
+ * @param {string} serviceName - The name of the service
68
+ * @param {Function} fn - The function to wrap
69
+ * @param {string} name - Span name (optional)
70
+ * @returns {Function} Wrapped function
71
+ */function withObservability(serviceName,fn,name){if(!isOtelEnabled())return fn;const spanName=name||fn.name||"anonymousFunction";return async function(...args){// init() may not have populated the API yet — run untraced rather than throw.
72
+ const api=_otelApi;if(!api)return fn(...args);const tracer=api.trace.getTracer(serviceName);return tracer.startActiveSpan(spanName,async span=>{try{const result=await fn(...args);return result;}catch(err){span.recordException(err);span.setStatus({code:2,message:err.message});throw err;}finally{span.end();}});};}var _default=exports.default={init,withObservability,withSyncObservability};
@@ -1,3 +1,3 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _router=require("@tata1mg/router");const{preparedRoutes}=require(`${process.env.src_path}/src/js/routes/utils.js`);/**
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.default=void 0;var _router=require("@tata1mg/router");var _registerServiceWorker=require("../offline/registerServiceWorker.js");const{preparedRoutes}=require(`${process.env.src_path}/src/js/routes/utils.js`);(0,_registerServiceWorker.registerCatalystServiceWorker)();/**
2
2
  * @returns returns browsers routers (client routes)
3
3
  */const clientRouter=({store,routerInitialState})=>(0,_router.createBrowserRouter)(preparedRoutes({store,routerInitialState}));var _default=exports.default=clientRouter;
@@ -1,3 +1,3 @@
1
1
  "use strict";const path=require("path");const{green,cyan,yellow}=require("picocolors");const{name}=require(`${process.cwd()}/package.json`);const{BUILD_OUTPUT_PATH}=require(`${process.cwd()}/config/config.json`);const{arrayToObject,printBundleInformation,runBuildCommands}=require("./scriptUtils.js");/**
2
2
  * @description - creates a production build of the application.
3
- */function build(){const isWindows=process.platform==="win32";const commandLineArguments=process.argv.slice(2);const argumentsObject=arrayToObject(commandLineArguments);const dirname=path.resolve(__dirname,"../../");const commands=["node ./dist/scripts/checkVersion",`${isWindows?"rd -r -fo":"rm -rf"} ${process.cwd()}/${BUILD_OUTPUT_PATH} & node ./dist/scripts/loadScriptsBeforeServerStarts.js`,`cross-env APPLICATION=${name||"catalyst_app"} webpack --config ./dist/webpack/production.client.babel.js --progress`,`cross-env APPLICATION=${name||"catalyst_app"} SSR=true webpack --config ./dist/webpack/production.ssr.babel.js`,`cross-env APPLICATION=${name||"catalyst_app"} npx babel ./dist/server --out-dir ${process.cwd()}/${BUILD_OUTPUT_PATH} --extensions .js,.ts,.jsx,.tsx --ignore '**/*.test.js,./dist/server/renderer/handler.js' --quiet`,`cross-env APPLICATION=${name||"catalyst_app"} npx babel ${process.cwd()}/server --out-dir ${process.cwd()}/${BUILD_OUTPUT_PATH} --extensions .js,.ts,.jsx,.tsx --quiet`];console.log("Creating an optimized production build...");runBuildCommands({commands,cwd:dirname,env:{...process.env,src_path:process.cwd(),BUILD_OUTPUT_PATH:BUILD_OUTPUT_PATH,NODE_ENV:"production",IS_DEV_COMMAND:false,...argumentsObject}});console.log(green("Compiled successfully."));console.log("\nFile sizes after gzip:\n");printBundleInformation();console.log(`\nThe ${cyan(BUILD_OUTPUT_PATH)} folder is ready to be deployed.`);console.log("You may serve it with a serve command:");console.log(cyan("\n npm run serve"));console.log("\nFind out more about deployment here:");console.log(yellow("\n https://catalyst.1mg.com/public_docs/content/Deployment%20and%20Production/deployment\n"));}build();
3
+ */function build(){const isWindows=process.platform==="win32";const commandLineArguments=process.argv.slice(2);const argumentsObject=arrayToObject(commandLineArguments);const dirname=path.resolve(__dirname,"../../");const commands=["node ./dist/scripts/checkVersion",`${isWindows?"rd -r -fo":"rm -rf"} ${process.cwd()}/${BUILD_OUTPUT_PATH} & node ./dist/scripts/loadScriptsBeforeServerStarts.js`,`cross-env APPLICATION=${name||"catalyst_app"} webpack --config ./dist/webpack/production.client.babel.js --progress`,`cross-env APPLICATION=${name||"catalyst_app"} SSR=true webpack --config ./dist/webpack/production.ssr.babel.js`,`cross-env APPLICATION=${name||"catalyst_app"} node ./dist/scripts/generateOfflineManifest.js`,`cross-env APPLICATION=${name||"catalyst_app"} npx babel ./dist/server --out-dir ${process.cwd()}/${BUILD_OUTPUT_PATH} --extensions .js,.ts,.jsx,.tsx --ignore '**/*.test.js,./dist/server/renderer/handler.js' --quiet`,`cross-env APPLICATION=${name||"catalyst_app"} npx babel ${process.cwd()}/server --out-dir ${process.cwd()}/${BUILD_OUTPUT_PATH} --extensions .js,.ts,.jsx,.tsx --quiet`];console.log("Creating an optimized production build...");runBuildCommands({commands,cwd:dirname,env:{...process.env,src_path:process.cwd(),BUILD_OUTPUT_PATH:BUILD_OUTPUT_PATH,NODE_ENV:"production",IS_DEV_COMMAND:false,...argumentsObject}});console.log(green("Compiled successfully."));console.log("\nFile sizes after gzip:\n");printBundleInformation();console.log(`\nThe ${cyan(BUILD_OUTPUT_PATH)} folder is ready to be deployed.`);console.log("You may serve it with a serve command:");console.log(cyan("\n npm run serve"));console.log("\nFind out more about deployment here:");console.log(yellow("\n https://catalyst.1mg.com/public_docs/content/Deployment%20and%20Production/deployment\n"));}build();
@@ -0,0 +1 @@
1
+ "use strict";const fs=require("fs");const path=require("path");function cleanCache(){const catalystCacheDir=path.join(process.cwd(),"node_modules/catalyst-core");const webpackCacheDir=path.join(catalystCacheDir,".cache/webpack");const loadableStats=path.join(catalystCacheDir,"loadable-stats.json");let cleaned=false;if(fs.existsSync(webpackCacheDir)){fs.rmSync(webpackCacheDir,{recursive:true,force:true});console.log("[Catalyst] Removed webpack filesystem cache.");cleaned=true;}if(fs.existsSync(loadableStats)){fs.rmSync(loadableStats);console.log("[Catalyst] Removed loadable-stats.json.");cleaned=true;}if(!cleaned){console.log("[Catalyst] Nothing to clean — cache is already empty.");}else{console.log("[Catalyst] Clean complete. Next dev start will be a full rebuild.");}}cleanCache();
@@ -1,3 +1,3 @@
1
1
  "use strict";const path=require("path");const{green,cyan,yellow}=require("picocolors");const{name}=require(`${process.cwd()}/package.json`);const{BUILD_OUTPUT_PATH}=require(`${process.cwd()}/config/config.json`);const{arrayToObject,printBundleInformation,runBuildCommands}=require("./scriptUtils.js");/**
2
2
  * @description - creates a production build of the application.
3
- */function devBuild(){const isWindows=process.platform==="win32";const commandLineArguments=process.argv.slice(2);const argumentsObject=arrayToObject(commandLineArguments);const dirname=path.resolve(__dirname,"../../");const commands=["node ./dist/scripts/checkVersion",`${isWindows?"rd -r -fo":"rm -rf"} ${process.cwd()}/${BUILD_OUTPUT_PATH} & node ./dist/scripts/loadScriptsBeforeServerStarts.js`,`cross-env APPLICATION=${name||"catalyst_app"} webpack --config ./dist/webpack/production.client.babel.js --progress`,`cross-env APPLICATION=${name||"catalyst_app"} SSR=true webpack --config ./dist/webpack/production.ssr.babel.js`,`cross-env APPLICATION=${name||"catalyst_app"} npx babel ./dist/server --out-dir ${process.cwd()}/${BUILD_OUTPUT_PATH} --ignore '**/*.test.js,./dist/server/renderer/handler.js' --quiet`,`cross-env APPLICATION=${name||"catalyst_app"} npx babel ${process.cwd()}/server --out-dir ${process.cwd()}/${BUILD_OUTPUT_PATH} --quiet`];console.log("Creating an optimized local build...");runBuildCommands({commands,cwd:dirname,env:{...process.env,src_path:process.cwd(),BUILD_OUTPUT_PATH:BUILD_OUTPUT_PATH,NODE_ENV:"production",IS_DEV_COMMAND:true,...argumentsObject}});console.log(green("Compiled successfully."));console.log("\nFile sizes after gzip:\n");printBundleInformation();console.log(`\nThe ${cyan(BUILD_OUTPUT_PATH)} folder is ready to be deployed.`);console.log("You may serve it with a serve command:");console.log(cyan("\n npm run devServe"));console.log("\nFind out more about deployment here:");console.log(yellow("\n https://catalyst.1mg.com/public_docs/content/Deployment%20and%20Production/deployment\n"));}devBuild();
3
+ */function devBuild(){const isWindows=process.platform==="win32";const commandLineArguments=process.argv.slice(2);const argumentsObject=arrayToObject(commandLineArguments);const dirname=path.resolve(__dirname,"../../");const commands=["node ./dist/scripts/checkVersion",`${isWindows?"rd -r -fo":"rm -rf"} ${process.cwd()}/${BUILD_OUTPUT_PATH} & node ./dist/scripts/loadScriptsBeforeServerStarts.js`,`cross-env APPLICATION=${name||"catalyst_app"} webpack --config ./dist/webpack/production.client.babel.js --progress`,`cross-env APPLICATION=${name||"catalyst_app"} SSR=true webpack --config ./dist/webpack/production.ssr.babel.js`,`cross-env APPLICATION=${name||"catalyst_app"} node ./dist/scripts/generateOfflineManifest.js`,`cross-env APPLICATION=${name||"catalyst_app"} npx babel ./dist/server --out-dir ${process.cwd()}/${BUILD_OUTPUT_PATH} --ignore '**/*.test.js,./dist/server/renderer/handler.js' --quiet`,`cross-env APPLICATION=${name||"catalyst_app"} npx babel ${process.cwd()}/server --out-dir ${process.cwd()}/${BUILD_OUTPUT_PATH} --quiet`];console.log("Creating an optimized local build...");runBuildCommands({commands,cwd:dirname,env:{...process.env,src_path:process.cwd(),BUILD_OUTPUT_PATH:BUILD_OUTPUT_PATH,NODE_ENV:"production",IS_DEV_COMMAND:true,...argumentsObject}});console.log(green("Compiled successfully."));console.log("\nFile sizes after gzip:\n");printBundleInformation();console.log(`\nThe ${cyan(BUILD_OUTPUT_PATH)} folder is ready to be deployed.`);console.log("You may serve it with a serve command:");console.log(cyan("\n npm run devServe"));console.log("\nFind out more about deployment here:");console.log(yellow("\n https://catalyst.1mg.com/public_docs/content/Deployment%20and%20Production/deployment\n"));}devBuild();
@@ -1,3 +1,3 @@
1
1
  "use strict";const path=require("path");const{spawnSync}=require("child_process");const{arrayToObject}=require("./scriptUtils");const{name}=require(`${process.cwd()}/package.json`);const{BUILD_OUTPUT_PATH}=require(`${process.cwd()}/config/config.json`);/**
2
2
  * @description - Serves production build of the application.
3
- */function devServe(){const commandLineArguments=process.argv.slice(2);const argumentsObject=arrayToObject(commandLineArguments);const dirname=path.resolve(__dirname,"../../");const command=`cross-env APPLICATION=${name||"catalyst_app"} node -r ./dist/scripts/loadScriptsBeforeServerStarts.js ${process.cwd()}/${BUILD_OUTPUT_PATH}/startServer.js`;spawnSync(command,[],{cwd:dirname,stdio:"inherit",shell:true,env:{...process.env,src_path:process.cwd(),BUILD_OUTPUT_PATH:BUILD_OUTPUT_PATH,NODE_ENV:"production",IS_DEV_COMMAND:true,...argumentsObject}});}devServe();
3
+ */function devServe(){const commandLineArguments=process.argv.slice(2);const argumentsObject=arrayToObject(commandLineArguments);const dirname=path.resolve(__dirname,"../../");spawnSync("node",["-r","./dist/scripts/loadScriptsBeforeServerStarts.js",path.join(process.cwd(),BUILD_OUTPUT_PATH,"startServer.js")],{cwd:dirname,stdio:"inherit",env:{...process.env,src_path:process.cwd(),BUILD_OUTPUT_PATH:BUILD_OUTPUT_PATH,NODE_ENV:"production",IS_DEV_COMMAND:true,APPLICATION:name||"catalyst_app",...argumentsObject}});}devServe();