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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (184) hide show
  1. package/babel.native.config.cjs +8 -0
  2. package/bin/catalyst.js +49 -105
  3. package/changelog.md +8 -0
  4. package/dist/caching.js +1 -1
  5. package/dist/index.jsx +8 -0
  6. package/dist/logger.js +74 -3
  7. package/dist/native/androidProject/app/build.gradle.kts +1 -0
  8. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +143 -4
  9. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +26 -6
  10. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MetricsMonitor.kt +143 -17
  11. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +6 -0
  12. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/WebCacheManager.kt +26 -0
  13. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +56 -1
  14. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/KeyboardUtil.kt +48 -8
  15. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/PerfDeliveryState.kt +36 -0
  16. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/PerfEventBuffer.kt +289 -0
  17. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/ProfilerNavigationState.kt +13 -0
  18. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/ProfilerLifecycleTest.kt +51 -0
  19. package/dist/native/androidSetup.js +300 -8
  20. package/dist/native/bridge/WebBridge.js +481 -59
  21. package/dist/native/bridge/constants/NativeInterfaces.js +290 -44
  22. package/dist/native/bridge/errors.js +250 -26
  23. package/dist/native/bridge/hooks/index.js +125 -1
  24. package/dist/native/bridge/hooks/useCamera.js +347 -9
  25. package/dist/native/bridge/hooks/useDataProtection.js +200 -2
  26. package/dist/native/bridge/hooks/useDeviceInfo.js +71 -2
  27. package/dist/native/bridge/hooks/useFilePicker.js +429 -2
  28. package/dist/native/bridge/hooks/useGoogleSignIn.js +81 -1
  29. package/dist/native/bridge/hooks/useHapticFeedback.js +196 -1
  30. package/dist/native/bridge/hooks/useIntent.js +95 -1
  31. package/dist/native/bridge/hooks/useNativeTransition.js +202 -27
  32. package/dist/native/bridge/hooks/useNetworkStatus.js +84 -3
  33. package/dist/native/bridge/hooks/useNotification.js +318 -2
  34. package/dist/native/bridge/hooks/useSafeArea.js +84 -2
  35. package/dist/native/bridge/hooks/useVideoStream.js +434 -6
  36. package/dist/native/bridge/hooks/utils.js +27 -1
  37. package/dist/native/bridge/hooks.js +16 -1
  38. package/dist/native/bridge/perf/collectors/ApiCollector.js +85 -0
  39. package/dist/native/bridge/perf/collectors/CacheCollector.js +215 -0
  40. package/dist/native/bridge/perf/collectors/HookCollector.js +165 -0
  41. package/dist/native/bridge/perf/collectors/InsightsCollector.js +505 -0
  42. package/dist/native/bridge/perf/collectors/InteractionCollector.js +273 -0
  43. package/dist/native/bridge/perf/collectors/KeyboardCollector.js +124 -0
  44. package/dist/native/bridge/perf/collectors/NavigationCollector.js +294 -0
  45. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +183 -0
  46. package/dist/native/bridge/perf/collectors/RenderCollector.js +284 -0
  47. package/dist/native/bridge/perf/collectors/ScrollCollector.js +111 -0
  48. package/dist/native/bridge/perf/collectors/ThreadCollector.js +111 -0
  49. package/dist/native/bridge/perf/core/constants.js +126 -0
  50. package/dist/native/bridge/perf/core/measure.js +189 -0
  51. package/dist/native/bridge/perf/core/session.js +100 -0
  52. package/dist/native/bridge/perf/core/store.js +554 -0
  53. package/dist/native/bridge/perf/core/summary.js +174 -0
  54. package/dist/native/bridge/perf/core/traceExport.js +240 -0
  55. package/dist/native/bridge/perf/core/waterfalls.js +146 -0
  56. package/dist/native/bridge/perf/index.js +402 -0
  57. package/dist/native/bridge/safeArea.js +53 -5
  58. package/dist/native/bridge/standardizedHooks.js +595 -82
  59. package/dist/native/bridge/useBaseHook.js +274 -38
  60. package/dist/native/bridge/utils/CameraUtils.js +201 -56
  61. package/dist/native/bridge/utils/FileObjectConverter.js +129 -18
  62. package/dist/native/bridge/utils/NativeBridge.js +425 -141
  63. package/dist/native/buildAndroid/assets.js +510 -13
  64. package/dist/native/buildAndroid/build.js +296 -19
  65. package/dist/native/buildAndroid/config.js +87 -1
  66. package/dist/native/buildAndroid/index.js +374 -1
  67. package/dist/native/buildAppAndroid.js +34 -2
  68. package/dist/native/buildAppIos.js +62 -2
  69. package/dist/native/buildIos/assets.js +609 -8
  70. package/dist/native/buildIos/build.js +639 -20
  71. package/dist/native/buildIos/config.js +294 -12
  72. package/dist/native/buildIos/index.js +343 -6
  73. package/dist/native/buildIos/plugins.js +343 -16
  74. package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +43 -0
  75. package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +28 -0
  76. package/dist/native/internal-plugins/device-info-plugin/manifest.json +19 -0
  77. package/dist/native/internalPluginUtils.js +232 -7
  78. package/dist/native/iosnativeWebView/Sources/Core/Constants/CatalystConstants.swift +19 -0
  79. package/dist/native/iosnativeWebView/Sources/Core/Constants/ConfigConstants.swift +4 -0
  80. package/dist/native/iosnativeWebView/Sources/Core/Utils/BootTimingUtility.swift +5 -1
  81. package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +10 -0
  82. package/dist/native/iosnativeWebView/Sources/Core/Utils/DisplayLinkPerfMonitor.swift +89 -0
  83. package/dist/native/iosnativeWebView/Sources/Core/Utils/PerfEventBuffer.swift +373 -0
  84. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeFileHandler.swift +35 -1
  85. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +24 -11
  86. package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +9 -0
  87. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +126 -0
  88. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +75 -1
  89. package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeMessageValidatorTests.swift +13 -0
  90. package/dist/native/package.json +3 -0
  91. package/dist/native/plugin-bridge/PluginBridge.js +227 -1
  92. package/dist/native/pluginComposerAndroid.js +407 -23
  93. package/dist/native/pluginComposerIos.js +435 -11
  94. package/dist/native/renameAndroidProject.js +1094 -88
  95. package/dist/native/setupEmulatorIos.js +289 -22
  96. package/dist/native/setupServer.js +160 -19
  97. package/dist/native/terminalProgress.js +199 -11
  98. package/dist/native/utils.js +193 -15
  99. package/dist/offline/catalyst-sw.js +145 -1
  100. package/dist/offline/registerServiceWorker.js +33 -1
  101. package/dist/otel.js +375 -46
  102. package/dist/router/ClientRouter.js +14 -2
  103. package/dist/router/ServerRouter.js +9 -1
  104. package/dist/router.js +1 -1
  105. package/dist/scripts/build.js +112 -3
  106. package/dist/scripts/checkVersion.js +3 -1
  107. package/dist/scripts/cleanCache.js +30 -1
  108. package/dist/scripts/generateOfflineManifest.js +116 -1
  109. package/dist/scripts/loadEnvironmentVariables.js +32 -6
  110. package/dist/scripts/plugins.js +309 -1
  111. package/dist/scripts/preServerInit.js +13 -0
  112. package/dist/scripts/scriptUtils.js +54 -3
  113. package/dist/scripts/serve.js +51 -4
  114. package/dist/scripts/start.js +47 -10
  115. package/dist/scripts/validator.js +149 -1
  116. package/dist/sentry.cjs +127 -0
  117. package/dist/sentry.js +125 -5
  118. package/dist/server/expressServer.js +250 -5
  119. package/dist/server/manifestCache.js +36 -0
  120. package/dist/server/renderer/ChunkExtractor.js +144 -0
  121. package/dist/server/renderer/document/Body.jsx +59 -0
  122. package/dist/server/renderer/document/FastRefresh.jsx +20 -0
  123. package/dist/server/renderer/document/Head.jsx +60 -0
  124. package/dist/server/renderer/document/index.jsx +2 -0
  125. package/dist/server/renderer/extract.js +149 -31
  126. package/dist/server/renderer/handler.jsx +433 -0
  127. package/dist/server/renderer/index.js +8 -1
  128. package/dist/server/renderer/render.js +47 -0
  129. package/dist/server/utils/botDetectionMiddleware.js +29 -0
  130. package/dist/server/utils/cjsRequire.js +10 -0
  131. package/dist/server/utils/userAgentUtil.js +87 -9
  132. package/dist/server/utils/validator.js +164 -1
  133. package/dist/vite/FastRefresh.jsx +20 -0
  134. package/dist/vite/inject-cache-key-plugin.js +276 -0
  135. package/dist/vite/loadCustomViteConfig.js +22 -0
  136. package/dist/vite/manifest-categorization-plugin.js +455 -0
  137. package/dist/vite/node-loader.mjs +458 -0
  138. package/dist/vite/scssParams.js +21 -0
  139. package/dist/vite/vite.config.client.js +130 -0
  140. package/dist/vite/vite.config.js +422 -0
  141. package/dist/vite/vite.config.server.js +84 -0
  142. package/dist/web-router/components/MetaTag.jsx +34 -0
  143. package/dist/web-router/components/RouterDataProvider.jsx +332 -0
  144. package/dist/web-router/components/Split.jsx +200 -0
  145. package/dist/web-router/components/SplitInview.jsx +106 -0
  146. package/dist/web-router/components/SsrRequestContext.jsx +14 -0
  147. package/dist/web-router/context.jsx +12 -0
  148. package/dist/web-router/hooks.jsx +72 -0
  149. package/dist/web-router/types/components/MetaTag.d.ts +2 -0
  150. package/dist/web-router/types/components/RouterDataProvider.d.ts +95 -0
  151. package/dist/web-router/types/context.d.ts +14 -0
  152. package/dist/web-router/types/hooks.d.ts +1 -0
  153. package/dist/web-router/types/index.d.ts +3 -0
  154. package/dist/web-router/types/utils/metaDataUtils.d.ts +24 -0
  155. package/dist/web-router/utils/metaDataUtils.jsx +119 -0
  156. package/mcp_v2/conversion-tasks.json +35 -17
  157. package/mcp_v2/knowledge-base.json +124 -13
  158. package/mcp_v2/lib/helpers.js +10 -0
  159. package/mcp_v2/mcp.js +4 -4
  160. package/mcp_v2/package.json +1 -0
  161. package/mcp_v2/tools/build.js +116 -20
  162. package/mcp_v2/tools/conversion.js +106 -5
  163. package/mcp_v2/tools/tasks.js +18 -14
  164. package/package.json +35 -68
  165. package/babel.config.js +0 -32
  166. package/dist/index.js +0 -1
  167. package/dist/scripts/devBuild.js +0 -3
  168. package/dist/scripts/devServe.js +0 -3
  169. package/dist/scripts/loadScriptsBeforeServerStarts.js +0 -4
  170. package/dist/scripts/registerAliases.js +0 -6
  171. package/dist/server/renderer/document/Body.js +0 -11
  172. package/dist/server/renderer/document/Head.js +0 -9
  173. package/dist/server/renderer/document/index.js +0 -1
  174. package/dist/server/renderer/handler.js +0 -47
  175. package/dist/server/startServer.js +0 -57
  176. package/dist/webpack/babel-plugins/remove-client.plugin.js +0 -5
  177. package/dist/webpack/babel-plugins/remove-ssr.plugin.js +0 -5
  178. package/dist/webpack/babel.config.client.js +0 -1
  179. package/dist/webpack/babel.config.ssr.js +0 -1
  180. package/dist/webpack/base.babel.js +0 -7
  181. package/dist/webpack/development.client.babel.js +0 -10
  182. package/dist/webpack/production.client.babel.js +0 -4
  183. package/dist/webpack/production.ssr.babel.js +0 -8
  184. package/dist/webpack/scssParams.js +0 -9
@@ -1,8 +1,157 @@
1
- "use strict";const fs=require("fs");const path=require("path");const crypto=require("crypto");const{execSync}=require("child_process");const PLUGIN_RESOURCE_ROOT="PluginResources";// ─── Pure helpers ─────────────────────────────────────────────────────────────
2
- function packageRequirementKey(dependency){return`${dependency.requirement.type}:${dependency.requirement.version}`;}function mergePackageDependency(map,dependency,sourceLabel){const existing=map.get(dependency.url);if(!existing){map.set(dependency.url,{url:dependency.url,package:dependency.package,requirement:dependency.requirement,products:[...new Set(dependency.products)]});return;}if(packageRequirementKey(existing)!==packageRequirementKey(dependency)){throw new Error(`iOS package dependency conflict for '${dependency.url}' while processing ${sourceLabel}: '${existing.requirement.type}:${existing.requirement.version}' vs '${dependency.requirement.type}:${dependency.requirement.version}'`);}if(existing.package!==dependency.package){throw new Error(`iOS package identity conflict for '${dependency.url}' while processing ${sourceLabel}: '${existing.package}' vs '${dependency.package}'`);}existing.products=[...new Set([...existing.products,...dependency.products])].sort();}function formatSwiftPackageRequirement(dependency){if(dependency.requirement.type==="from")return`from: "${dependency.requirement.version}"`;if(dependency.requirement.type==="exact")return`exact: "${dependency.requirement.version}"`;throw new Error(`Unsupported iOS package requirement type: ${dependency.requirement.type}`);}function formatSwiftPackageEntries(dependencies){return dependencies.map(d=>` .package(url: "${d.url}", ${formatSwiftPackageRequirement(d)})`);}function formatSwiftProductEntries(dependencies){return dependencies.flatMap(d=>d.products.map(p=>` .product(name: "${p}", package: "${d.package}")`));}function formatPbxprojPath(value){return /[^A-Za-z0-9_./-]/.test(value)?`"${value}"`:value;}function detectPbxprojFileType(filePath){const extension=path.extname(filePath).toLowerCase();const fileTypes={".json":"text.json",".plist":"text.plist.xml",".txt":"text",".html":"text.html",".js":"sourcecode.javascript",".css":"text.css",".png":"image.png",".jpg":"image.jpeg",".jpeg":"image.jpeg",".gif":"image.gif",".svg":"image.svg",".mp3":"audio.mp3",".wav":"audio.wav",".m4a":"audio.m4a",".caf":"audio.caf",".pdf":"image.pdf"};return fileTypes[extension]||"file";}function generateProjectObjectId(identifier,suffix=""){return crypto.createHash("md5").update(`${identifier}:${suffix}`).digest("hex").substring(0,24).toUpperCase();}function replaceRequired(content,pattern,replacement,errorMessage){if(!pattern.test(content))throw new Error(errorMessage);return content.replace(pattern,replacement);}// ─── Module factory ───────────────────────────────────────────────────────────
3
- module.exports=function createPluginsPhase(ctx){const{WEBVIEW_CONFIG,PROJECT_DIR,PROJECT_NAME,SCHEME_NAME,isGoogleSignInEnabled,progress,getXcodeProjectFilePath}=ctx;async function generatePackageSwift(pluginDependencies=[]){try{const isNotificationsEnabled=WEBVIEW_CONFIG.notifications?.enabled??false;const baseCoreDependencies=[{url:"https://github.com/kylef/JSONSchema.swift",package:"JSONSchema.swift",requirement:{type:"from",version:"0.6.0"},products:["JSONSchema"]},{url:"https://github.com/google/GoogleSignIn-iOS",package:"GoogleSignIn-iOS",requirement:{type:"from",version:"7.0.0"},products:["GoogleSignIn"]}];const notificationsDependencies=isNotificationsEnabled?[{url:"https://github.com/firebase/firebase-ios-sdk",package:"firebase-ios-sdk",requirement:{type:"from",version:"12.3.0"},products:["FirebaseCore","FirebaseMessaging"]}]:[];const coreDependencyMap=new Map();baseCoreDependencies.forEach(d=>mergePackageDependency(coreDependencyMap,d,"base core"));pluginDependencies.forEach(d=>mergePackageDependency(coreDependencyMap,d,"plugin manifests"));const coreDependencies=[...coreDependencyMap.values()].sort((l,r)=>l.url.localeCompare(r.url));const packageDependencyMap=new Map();coreDependencies.forEach(d=>mergePackageDependency(packageDependencyMap,d,"core target"));notificationsDependencies.forEach(d=>mergePackageDependency(packageDependencyMap,d,"notifications target"));const packageDependencies=[...packageDependencyMap.values()].sort((l,r)=>l.url.localeCompare(r.url));progress.log(`🔧 Generating Package.swift (notifications: ${isNotificationsEnabled})`,"info");const configHash=crypto.createHash("md5").update(JSON.stringify({notifications:isNotificationsEnabled,googleSignIn:isGoogleSignInEnabled,pluginDependencies:coreDependencies})).digest("hex");const hashFilePath=path.join(PROJECT_DIR,".package-config-hash");// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
4
- const targetPath=path.join(PROJECT_DIR,"Package.swift");// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
5
- let shouldUpdate=true;try{const previousHash=fs.readFileSync(hashFilePath,"utf8");if(previousHash===configHash){shouldUpdate=false;progress.log("Package.swift already up to date","info");}}catch{/* hash file absent — proceed with update */}try{fs.accessSync(targetPath);}catch{shouldUpdate=true;progress.log("Package.swift missing, will generate it now","info");}if(shouldUpdate){progress.log("Generating Package.swift dynamically","info");let packageContent=`// swift-tools-version: 5.9
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const crypto = require("crypto");
6
+ const {
7
+ execSync
8
+ } = require("child_process");
9
+ const PLUGIN_RESOURCE_ROOT = "PluginResources";
10
+
11
+ // ─── Pure helpers ─────────────────────────────────────────────────────────────
12
+
13
+ function packageRequirementKey(dependency) {
14
+ return `${dependency.requirement.type}:${dependency.requirement.version}`;
15
+ }
16
+ function mergePackageDependency(map, dependency, sourceLabel) {
17
+ const existing = map.get(dependency.url);
18
+ if (!existing) {
19
+ map.set(dependency.url, {
20
+ url: dependency.url,
21
+ package: dependency.package,
22
+ requirement: dependency.requirement,
23
+ products: [...new Set(dependency.products)]
24
+ });
25
+ return;
26
+ }
27
+ if (packageRequirementKey(existing) !== packageRequirementKey(dependency)) {
28
+ throw new Error(`iOS package dependency conflict for '${dependency.url}' while processing ${sourceLabel}: '${existing.requirement.type}:${existing.requirement.version}' vs '${dependency.requirement.type}:${dependency.requirement.version}'`);
29
+ }
30
+ if (existing.package !== dependency.package) {
31
+ throw new Error(`iOS package identity conflict for '${dependency.url}' while processing ${sourceLabel}: '${existing.package}' vs '${dependency.package}'`);
32
+ }
33
+ existing.products = [...new Set([...existing.products, ...dependency.products])].sort();
34
+ }
35
+ function formatSwiftPackageRequirement(dependency) {
36
+ if (dependency.requirement.type === "from") return `from: "${dependency.requirement.version}"`;
37
+ if (dependency.requirement.type === "exact") return `exact: "${dependency.requirement.version}"`;
38
+ throw new Error(`Unsupported iOS package requirement type: ${dependency.requirement.type}`);
39
+ }
40
+ function formatSwiftPackageEntries(dependencies) {
41
+ return dependencies.map(d => ` .package(url: "${d.url}", ${formatSwiftPackageRequirement(d)})`);
42
+ }
43
+ function formatSwiftProductEntries(dependencies) {
44
+ return dependencies.flatMap(d => d.products.map(p => ` .product(name: "${p}", package: "${d.package}")`));
45
+ }
46
+ function formatPbxprojPath(value) {
47
+ return /[^A-Za-z0-9_./-]/.test(value) ? `"${value}"` : value;
48
+ }
49
+ function detectPbxprojFileType(filePath) {
50
+ const extension = path.extname(filePath).toLowerCase();
51
+ const fileTypes = {
52
+ ".json": "text.json",
53
+ ".plist": "text.plist.xml",
54
+ ".txt": "text",
55
+ ".html": "text.html",
56
+ ".js": "sourcecode.javascript",
57
+ ".css": "text.css",
58
+ ".png": "image.png",
59
+ ".jpg": "image.jpeg",
60
+ ".jpeg": "image.jpeg",
61
+ ".gif": "image.gif",
62
+ ".svg": "image.svg",
63
+ ".mp3": "audio.mp3",
64
+ ".wav": "audio.wav",
65
+ ".m4a": "audio.m4a",
66
+ ".caf": "audio.caf",
67
+ ".pdf": "image.pdf"
68
+ };
69
+ return fileTypes[extension] || "file";
70
+ }
71
+ function generateProjectObjectId(identifier, suffix = "") {
72
+ return crypto.createHash("md5").update(`${identifier}:${suffix}`).digest("hex").substring(0, 24).toUpperCase();
73
+ }
74
+ function replaceRequired(content, pattern, replacement, errorMessage) {
75
+ if (!pattern.test(content)) throw new Error(errorMessage);
76
+ return content.replace(pattern, replacement);
77
+ }
78
+
79
+ // ─── Module factory ───────────────────────────────────────────────────────────
80
+
81
+ module.exports = function createPluginsPhase(ctx) {
82
+ const {
83
+ WEBVIEW_CONFIG,
84
+ PROJECT_DIR,
85
+ PROJECT_NAME,
86
+ SCHEME_NAME,
87
+ isGoogleSignInEnabled,
88
+ progress,
89
+ getXcodeProjectFilePath
90
+ } = ctx;
91
+ async function generatePackageSwift(pluginDependencies = []) {
92
+ try {
93
+ const isNotificationsEnabled = WEBVIEW_CONFIG.notifications?.enabled ?? false;
94
+ const baseCoreDependencies = [{
95
+ url: "https://github.com/kylef/JSONSchema.swift",
96
+ package: "JSONSchema.swift",
97
+ requirement: {
98
+ type: "from",
99
+ version: "0.6.0"
100
+ },
101
+ products: ["JSONSchema"]
102
+ }, {
103
+ url: "https://github.com/google/GoogleSignIn-iOS",
104
+ package: "GoogleSignIn-iOS",
105
+ requirement: {
106
+ type: "from",
107
+ version: "7.0.0"
108
+ },
109
+ products: ["GoogleSignIn"]
110
+ }];
111
+ const notificationsDependencies = isNotificationsEnabled ? [{
112
+ url: "https://github.com/firebase/firebase-ios-sdk",
113
+ package: "firebase-ios-sdk",
114
+ requirement: {
115
+ type: "from",
116
+ version: "12.3.0"
117
+ },
118
+ products: ["FirebaseCore", "FirebaseMessaging"]
119
+ }] : [];
120
+ const coreDependencyMap = new Map();
121
+ baseCoreDependencies.forEach(d => mergePackageDependency(coreDependencyMap, d, "base core"));
122
+ pluginDependencies.forEach(d => mergePackageDependency(coreDependencyMap, d, "plugin manifests"));
123
+ const coreDependencies = [...coreDependencyMap.values()].sort((l, r) => l.url.localeCompare(r.url));
124
+ const packageDependencyMap = new Map();
125
+ coreDependencies.forEach(d => mergePackageDependency(packageDependencyMap, d, "core target"));
126
+ notificationsDependencies.forEach(d => mergePackageDependency(packageDependencyMap, d, "notifications target"));
127
+ const packageDependencies = [...packageDependencyMap.values()].sort((l, r) => l.url.localeCompare(r.url));
128
+ progress.log(`🔧 Generating Package.swift (notifications: ${isNotificationsEnabled})`, "info");
129
+ const configHash = crypto.createHash("md5").update(JSON.stringify({
130
+ notifications: isNotificationsEnabled,
131
+ googleSignIn: isGoogleSignInEnabled,
132
+ pluginDependencies: coreDependencies
133
+ })).digest("hex");
134
+ const hashFilePath = path.join(PROJECT_DIR, ".package-config-hash"); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
135
+ const targetPath = path.join(PROJECT_DIR, "Package.swift"); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
136
+ let shouldUpdate = true;
137
+ try {
138
+ const previousHash = fs.readFileSync(hashFilePath, "utf8");
139
+ if (previousHash === configHash) {
140
+ shouldUpdate = false;
141
+ progress.log("Package.swift already up to date", "info");
142
+ }
143
+ } catch {
144
+ /* hash file absent — proceed with update */
145
+ }
146
+ try {
147
+ fs.accessSync(targetPath);
148
+ } catch {
149
+ shouldUpdate = true;
150
+ progress.log("Package.swift missing, will generate it now", "info");
151
+ }
152
+ if (shouldUpdate) {
153
+ progress.log("Generating Package.swift dynamically", "info");
154
+ let packageContent = `// swift-tools-version: 5.9
6
155
  // Auto-generated Package.swift - DO NOT EDIT MANUALLY
7
156
  // Generated based on config: notifications.enabled = ${isNotificationsEnabled}
8
157
  import PackageDescription
@@ -11,7 +160,11 @@ let package = Package(
11
160
  name: "iosnativeWebView",
12
161
  platforms: [.iOS(.v17)],
13
162
  products: [
14
- .library(name: "CatalystCore", targets: ["CatalystCore"])`;if(isNotificationsEnabled){packageContent+=`,\n .library(name: "CatalystNotifications", targets: ["CatalystNotifications"])`;}packageContent+=`
163
+ .library(name: "CatalystCore", targets: ["CatalystCore"])`;
164
+ if (isNotificationsEnabled) {
165
+ packageContent += `,\n .library(name: "CatalystNotifications", targets: ["CatalystNotifications"])`;
166
+ }
167
+ packageContent += `
15
168
  ],
16
169
  dependencies: [
17
170
  ${formatSwiftPackageEntries(packageDependencies).join(",\n")}
@@ -25,7 +178,9 @@ ${formatSwiftPackageEntries(packageDependencies).join(",\n")}
25
178
  ${formatSwiftProductEntries(coreDependencies).join(",\n")}
26
179
  ],
27
180
  path: "Sources/Core"
28
- )`;if(isNotificationsEnabled){packageContent+=`,
181
+ )`;
182
+ if (isNotificationsEnabled) {
183
+ packageContent += `,
29
184
  // Notifications functionality (optional, includes Firebase)
30
185
  .target(
31
186
  name: "CatalystNotifications",
@@ -34,12 +189,184 @@ ${formatSwiftProductEntries(coreDependencies).join(",\n")}
34
189
  ${formatSwiftProductEntries(notificationsDependencies).join(",\n")}
35
190
  ],
36
191
  path: "Sources/CatalystNotifications"
37
- )`;}packageContent+=`\n ]\n)\n`;fs.writeFileSync(targetPath,packageContent,"utf8");progress.log(`Generated Package.swift with ${isNotificationsEnabled?"notifications enabled":"notifications disabled"}`,"success");fs.writeFileSync(hashFilePath,configHash);progress.log("Resolving package dependencies...","info");try{execSync(`cd "${PROJECT_DIR}" && rm -rf .build`,{stdio:"ignore"});// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
38
- try{fs.rmSync(path.join(PROJECT_DIR,"Package.resolved"),{force:true});// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - PROJECT_DIR is the fixed generated iOS project directory.
39
- }catch{progress.log("");}execSync(`cd "${PROJECT_DIR}" && rm -rf .swiftpm`,{stdio:"ignore"});// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
40
- const projectPath=path.join(PROJECT_DIR,`${PROJECT_NAME}.xcodeproj`);// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
41
- execSync(`cd "${PROJECT_DIR}" && xcodebuild -resolvePackageDependencies -project "${projectPath}" -scheme "${SCHEME_NAME}"`,// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - PROJECT_DIR, projectPath, and SCHEME_NAME are generated from trusted iOS project config.
42
- {stdio:"inherit"});progress.log("Package dependencies resolved successfully","success");}catch(error){if(isNotificationsEnabled){progress.log(`❌ CRITICAL: Package resolution failed. Firebase dependencies required for notifications could not be resolved.`,"error");throw new Error(`Package resolution failed: ${error.message}. This is required when notifications are enabled.`);}else{progress.log(`Warning: Package resolution may have failed: ${error.message}`,"warning");}}}progress.log("✅ Package.swift ready","success");}catch(error){progress.log(`❌ Failed to generate Package.swift: ${error.message}`,"error");throw error;}}async function updateXcodeProjectPackageDependencies(){try{const isNotificationsEnabled=WEBVIEW_CONFIG.notifications?.enabled??false;const projectFilePath=path.join(PROJECT_DIR,`${PROJECT_NAME}.xcodeproj`,"project.pbxproj");// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
43
- progress.log(`🔧 Updating Xcode package dependencies (notifications: ${isNotificationsEnabled})`,"info");let projectContent;try{projectContent=fs.readFileSync(projectFilePath,"utf8");}catch{throw new Error(`Xcode project file not found at: ${projectFilePath}`);}const NOTIF_BUILD_FILE_ID="C99974352E97D56900C25611";const NOTIF_PRODUCT_ID="C99974362E97D56900C25611";const hasNotifications=projectContent.includes("/* CatalystNotifications */");if(isNotificationsEnabled&&!hasNotifications){progress.log("Adding CatalystNotifications to Xcode project","info");projectContent=projectContent.replace(/(C99974342E97D56900C25611 \/\* CatalystCore in Frameworks \*\/ = {isa = PBXBuildFile; productRef = C99974332E97D56900C25611 \/\* CatalystCore \*\/; };)/,`$1\n\t\t${NOTIF_BUILD_FILE_ID} /* CatalystNotifications in Frameworks */ = {isa = PBXBuildFile; productRef = ${NOTIF_PRODUCT_ID} /* CatalystNotifications */; };`);projectContent=projectContent.replace(/(C99974342E97D56900C25611 \/\* CatalystCore in Frameworks \*\/,)/,`$1\n\t\t\t\t${NOTIF_BUILD_FILE_ID} /* CatalystNotifications in Frameworks */,`);projectContent=projectContent.replace(/(packageProductDependencies = \(\s*C99974332E97D56900C25611 \/\* CatalystCore \*\/,)/,`$1\n\t\t\t\t${NOTIF_PRODUCT_ID} /* CatalystNotifications */,`);projectContent=projectContent.replace(/(\/\* End XCSwiftPackageProductDependency section \*\/)/,`\t\t${NOTIF_PRODUCT_ID} /* CatalystNotifications */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = C99974322E97D56900C25611 /* XCLocalSwiftPackageReference "." */;\n\t\t\tproductName = CatalystNotifications;\n\t\t};\n$1`);fs.writeFileSync(projectFilePath,projectContent,"utf8");progress.log("✅ CatalystNotifications added to Xcode project","success");}else if(!isNotificationsEnabled&&hasNotifications){progress.log("Removing CatalystNotifications from Xcode project","info");projectContent=projectContent.replace(/\t\t[A-F0-9]+ \/\* CatalystNotifications in Frameworks \*\/ = {isa = PBXBuildFile; productRef = [A-F0-9]+ \/\* CatalystNotifications \*\/; };\n/g,"");projectContent=projectContent.replace(/\t\t\t\t[A-F0-9]+ \/\* CatalystNotifications in Frameworks \*\/,\n/g,"");projectContent=projectContent.replace(/\t\t\t\t[A-F0-9]+ \/\* CatalystNotifications \*\/,\n/g,"");projectContent=projectContent.replace(/\t\t[A-F0-9]+ \/\* CatalystNotifications \*\/ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = [A-F0-9]+ \/\* XCLocalSwiftPackageReference "." \*\/;\n\t\t\tproductName = CatalystNotifications;\n\t\t};\n/g,"");fs.writeFileSync(projectFilePath,projectContent,"utf8");progress.log("✅ CatalystNotifications removed from Xcode project","success");}else{progress.log("Package dependencies already correct","info");}}catch(error){progress.log(`❌ Failed to update package dependencies: ${error.message}`,"error");throw error;}}async function removePluginResourcesFromXcodeProject(){try{const projectFilePath=getXcodeProjectFilePath();let projectContent;try{projectContent=fs.readFileSync(projectFilePath,"utf8");}catch(err){if(err.code==="ENOENT")return;throw err;}const patterns=[/\t\t[A-F0-9]+ \/\* PluginResources\/[^*]+ \*\/ = \{isa = PBXFileReference;[^\n]*path = [^;]*PluginResources\/[^;]+; sourceTree = "<group>"; \};\n/g,/\t\t[A-F0-9]+ \/\* PluginResources\/[^*]+ in Resources \*\/ = \{isa = PBXBuildFile;[^\n]*\};\n/g,/\t\t\t\t[A-F0-9]+ \/\* PluginResources\/[^*]+ \*\/,\n/g,/\t\t\t\t[A-F0-9]+ \/\* PluginResources\/[^*]+ in Resources \*\/,\n/g];let modified=false;for(const pattern of patterns){const nextContent=projectContent.replace(pattern,"");if(nextContent!==projectContent){projectContent=nextContent;modified=true;}}if(modified){fs.writeFileSync(projectFilePath,projectContent,"utf8");progress.log("Removed managed plugin resources from Xcode project","info");}}catch(error){throw new Error(`Could not remove plugin resources from Xcode project: ${error.message}`);}}async function addPluginResourcesToXcodeProject(resources){try{if(resources.length===0)return;const projectFilePath=getXcodeProjectFilePath();let projectContent;try{projectContent=fs.readFileSync(projectFilePath,"utf8");}catch(err){if(err.code==="ENOENT")throw new Error(`Xcode project file not found at: ${projectFilePath}`);throw err;}for(const resource of resources){const label=resource.bundleRelativePath;const fileRefId=generateProjectObjectId(label,"_ref");const buildFileId=generateProjectObjectId(label,"_build");const pbxprojPath=formatPbxprojPath(label);const fileType=detectPbxprojFileType(label);const fileRefEntry=`\t\t${fileRefId} /* ${label} */ = {isa = PBXFileReference; lastKnownFileType = ${fileType}; path = ${pbxprojPath}; sourceTree = "<group>"; };`;projectContent=replaceRequired(projectContent,/(\/\* End PBXFileReference section \*\/)/,`${fileRefEntry}\n$1`,"PBXFileReference section not found while registering plugin resources");const buildFileEntry=`\t\t${buildFileId} /* ${label} in Resources */ = {isa = PBXBuildFile; fileRef = ${fileRefId} /* ${label} */; };`;projectContent=replaceRequired(projectContent,/(\/\* End PBXBuildFile section \*\/)/,`${buildFileEntry}\n$1`,"PBXBuildFile section not found while registering plugin resources");projectContent=replaceRequired(projectContent,/(\/\* iosnativeWebView \*\/ = \{[^}]*children = \([^)]*)/,`$1\n\t\t\t\t${fileRefId} /* ${label} */,`,"iosnativeWebView group not found while registering plugin resources");projectContent=replaceRequired(projectContent,/(\/\* Resources \*\/ = \{[^}]*files = \([^)]*)/,`$1\n\t\t\t\t${buildFileId} /* ${label} in Resources */,`,"Resources build phase not found while registering plugin resources");}fs.writeFileSync(projectFilePath,projectContent,"utf8");progress.log(`Registered ${resources.length} managed plugin resource(s) in Xcode project`,"success");}catch(error){throw new Error(`Could not add plugin resources to Xcode project: ${error.message}`);}}async function syncPluginResources(pluginComposition={}){try{const resources=pluginComposition.resources||[];const pluginResourceDir=path.join(PROJECT_DIR,PROJECT_NAME,PLUGIN_RESOURCE_ROOT);// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
44
- fs.rmSync(pluginResourceDir,{recursive:true,force:true});await removePluginResourcesFromXcodeProject();if(resources.length===0){progress.log("No managed plugin resources to sync","info");return;}for(const resource of resources){const normalizedBundleRelativePath=path.posix.normalize(resource.bundleRelativePath||"");const expectedPrefix=`${PLUGIN_RESOURCE_ROOT}/`;if(!normalizedBundleRelativePath.startsWith(expectedPrefix)||normalizedBundleRelativePath.includes("../")||path.posix.isAbsolute(normalizedBundleRelativePath)){throw new Error(`Invalid managed plugin resource path: ${resource.bundleRelativePath}`);}// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - The normalized path is validated above and containment is enforced immediately below.
45
- const targetPath=path.resolve(PROJECT_DIR,PROJECT_NAME,normalizedBundleRelativePath);if(targetPath!==pluginResourceDir&&!targetPath.startsWith(`${pluginResourceDir}${path.sep}`)){throw new Error(`Managed plugin resource escaped bundle directory: ${resource.bundleRelativePath}`);}fs.mkdirSync(path.dirname(targetPath),{recursive:true});fs.copyFileSync(resource.sourcePath,targetPath);}await addPluginResourcesToXcodeProject(resources);progress.log(`Synced ${resources.length} managed plugin resource(s)`,"success");}catch(error){progress.log(`❌ Failed to sync plugin resources: ${error.message}`,"error");throw error;}}return{generatePackageSwift,updateXcodeProjectPackageDependencies,syncPluginResources};};
192
+ )`;
193
+ }
194
+ packageContent += `\n ]\n)\n`;
195
+ fs.writeFileSync(targetPath, packageContent, "utf8");
196
+ progress.log(`Generated Package.swift with ${isNotificationsEnabled ? "notifications enabled" : "notifications disabled"}`, "success");
197
+ fs.writeFileSync(hashFilePath, configHash);
198
+ progress.log("Resolving package dependencies...", "info");
199
+ try {
200
+ execSync(`cd "${PROJECT_DIR}" && rm -rf .build`, {
201
+ stdio: "ignore"
202
+ }); // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
203
+ try {
204
+ fs.rmSync(path.join(PROJECT_DIR, "Package.resolved"), {
205
+ force: true
206
+ }); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - PROJECT_DIR is the fixed generated iOS project directory.
207
+ } catch {
208
+ progress.log("");
209
+ }
210
+ execSync(`cd "${PROJECT_DIR}" && rm -rf .swiftpm`, {
211
+ stdio: "ignore"
212
+ }); // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
213
+ const projectPath = path.join(PROJECT_DIR, `${PROJECT_NAME}.xcodeproj`); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
214
+ execSync(`cd "${PROJECT_DIR}" && xcodebuild -resolvePackageDependencies -project "${projectPath}" -scheme "${SCHEME_NAME}"`,
215
+ // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - PROJECT_DIR, projectPath, and SCHEME_NAME are generated from trusted iOS project config.
216
+ {
217
+ stdio: "inherit"
218
+ });
219
+ progress.log("Package dependencies resolved successfully", "success");
220
+ } catch (error) {
221
+ if (isNotificationsEnabled) {
222
+ progress.log(`❌ CRITICAL: Package resolution failed. Firebase dependencies required for notifications could not be resolved.`, "error");
223
+ throw new Error(`Package resolution failed: ${error.message}. This is required when notifications are enabled.`);
224
+ } else {
225
+ progress.log(`Warning: Package resolution may have failed: ${error.message}`, "warning");
226
+ }
227
+ }
228
+ }
229
+ progress.log("✅ Package.swift ready", "success");
230
+ } catch (error) {
231
+ progress.log(`❌ Failed to generate Package.swift: ${error.message}`, "error");
232
+ throw error;
233
+ }
234
+ }
235
+ async function updateXcodeProjectPackageDependencies() {
236
+ try {
237
+ const isNotificationsEnabled = WEBVIEW_CONFIG.notifications?.enabled ?? false;
238
+ const projectFilePath = path.join(PROJECT_DIR, `${PROJECT_NAME}.xcodeproj`, "project.pbxproj"); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
239
+
240
+ progress.log(`🔧 Updating Xcode package dependencies (notifications: ${isNotificationsEnabled})`, "info");
241
+ let projectContent;
242
+ try {
243
+ projectContent = fs.readFileSync(projectFilePath, "utf8");
244
+ } catch {
245
+ throw new Error(`Xcode project file not found at: ${projectFilePath}`);
246
+ }
247
+ const NOTIF_BUILD_FILE_ID = "C99974352E97D56900C25611";
248
+ const NOTIF_PRODUCT_ID = "C99974362E97D56900C25611";
249
+ const hasNotifications = projectContent.includes("/* CatalystNotifications */");
250
+ if (isNotificationsEnabled && !hasNotifications) {
251
+ progress.log("Adding CatalystNotifications to Xcode project", "info");
252
+ projectContent = projectContent.replace(/(C99974342E97D56900C25611 \/\* CatalystCore in Frameworks \*\/ = {isa = PBXBuildFile; productRef = C99974332E97D56900C25611 \/\* CatalystCore \*\/; };)/, `$1\n\t\t${NOTIF_BUILD_FILE_ID} /* CatalystNotifications in Frameworks */ = {isa = PBXBuildFile; productRef = ${NOTIF_PRODUCT_ID} /* CatalystNotifications */; };`);
253
+ projectContent = projectContent.replace(/(C99974342E97D56900C25611 \/\* CatalystCore in Frameworks \*\/,)/, `$1\n\t\t\t\t${NOTIF_BUILD_FILE_ID} /* CatalystNotifications in Frameworks */,`);
254
+ projectContent = projectContent.replace(/(packageProductDependencies = \(\s*C99974332E97D56900C25611 \/\* CatalystCore \*\/,)/, `$1\n\t\t\t\t${NOTIF_PRODUCT_ID} /* CatalystNotifications */,`);
255
+ projectContent = projectContent.replace(/(\/\* End XCSwiftPackageProductDependency section \*\/)/, `\t\t${NOTIF_PRODUCT_ID} /* CatalystNotifications */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = C99974322E97D56900C25611 /* XCLocalSwiftPackageReference "." */;\n\t\t\tproductName = CatalystNotifications;\n\t\t};\n$1`);
256
+ fs.writeFileSync(projectFilePath, projectContent, "utf8");
257
+ progress.log("✅ CatalystNotifications added to Xcode project", "success");
258
+ } else if (!isNotificationsEnabled && hasNotifications) {
259
+ progress.log("Removing CatalystNotifications from Xcode project", "info");
260
+ projectContent = projectContent.replace(/\t\t[A-F0-9]+ \/\* CatalystNotifications in Frameworks \*\/ = {isa = PBXBuildFile; productRef = [A-F0-9]+ \/\* CatalystNotifications \*\/; };\n/g, "");
261
+ projectContent = projectContent.replace(/\t\t\t\t[A-F0-9]+ \/\* CatalystNotifications in Frameworks \*\/,\n/g, "");
262
+ projectContent = projectContent.replace(/\t\t\t\t[A-F0-9]+ \/\* CatalystNotifications \*\/,\n/g, "");
263
+ projectContent = projectContent.replace(/\t\t[A-F0-9]+ \/\* CatalystNotifications \*\/ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = [A-F0-9]+ \/\* XCLocalSwiftPackageReference "." \*\/;\n\t\t\tproductName = CatalystNotifications;\n\t\t};\n/g, "");
264
+ fs.writeFileSync(projectFilePath, projectContent, "utf8");
265
+ progress.log("✅ CatalystNotifications removed from Xcode project", "success");
266
+ } else {
267
+ progress.log("Package dependencies already correct", "info");
268
+ }
269
+ } catch (error) {
270
+ progress.log(`❌ Failed to update package dependencies: ${error.message}`, "error");
271
+ throw error;
272
+ }
273
+ }
274
+ async function removePluginResourcesFromXcodeProject() {
275
+ try {
276
+ const projectFilePath = getXcodeProjectFilePath();
277
+ let projectContent;
278
+ try {
279
+ projectContent = fs.readFileSync(projectFilePath, "utf8");
280
+ } catch (err) {
281
+ if (err.code === "ENOENT") return;
282
+ throw err;
283
+ }
284
+ const patterns = [/\t\t[A-F0-9]+ \/\* PluginResources\/[^*]+ \*\/ = \{isa = PBXFileReference;[^\n]*path = [^;]*PluginResources\/[^;]+; sourceTree = "<group>"; \};\n/g, /\t\t[A-F0-9]+ \/\* PluginResources\/[^*]+ in Resources \*\/ = \{isa = PBXBuildFile;[^\n]*\};\n/g, /\t\t\t\t[A-F0-9]+ \/\* PluginResources\/[^*]+ \*\/,\n/g, /\t\t\t\t[A-F0-9]+ \/\* PluginResources\/[^*]+ in Resources \*\/,\n/g];
285
+ let modified = false;
286
+ for (const pattern of patterns) {
287
+ const nextContent = projectContent.replace(pattern, "");
288
+ if (nextContent !== projectContent) {
289
+ projectContent = nextContent;
290
+ modified = true;
291
+ }
292
+ }
293
+ if (modified) {
294
+ fs.writeFileSync(projectFilePath, projectContent, "utf8");
295
+ progress.log("Removed managed plugin resources from Xcode project", "info");
296
+ }
297
+ } catch (error) {
298
+ throw new Error(`Could not remove plugin resources from Xcode project: ${error.message}`);
299
+ }
300
+ }
301
+ async function addPluginResourcesToXcodeProject(resources) {
302
+ try {
303
+ if (resources.length === 0) return;
304
+ const projectFilePath = getXcodeProjectFilePath();
305
+ let projectContent;
306
+ try {
307
+ projectContent = fs.readFileSync(projectFilePath, "utf8");
308
+ } catch (err) {
309
+ if (err.code === "ENOENT") throw new Error(`Xcode project file not found at: ${projectFilePath}`);
310
+ throw err;
311
+ }
312
+ for (const resource of resources) {
313
+ const label = resource.bundleRelativePath;
314
+ const fileRefId = generateProjectObjectId(label, "_ref");
315
+ const buildFileId = generateProjectObjectId(label, "_build");
316
+ const pbxprojPath = formatPbxprojPath(label);
317
+ const fileType = detectPbxprojFileType(label);
318
+ const fileRefEntry = `\t\t${fileRefId} /* ${label} */ = {isa = PBXFileReference; lastKnownFileType = ${fileType}; path = ${pbxprojPath}; sourceTree = "<group>"; };`;
319
+ projectContent = replaceRequired(projectContent, /(\/\* End PBXFileReference section \*\/)/, `${fileRefEntry}\n$1`, "PBXFileReference section not found while registering plugin resources");
320
+ const buildFileEntry = `\t\t${buildFileId} /* ${label} in Resources */ = {isa = PBXBuildFile; fileRef = ${fileRefId} /* ${label} */; };`;
321
+ projectContent = replaceRequired(projectContent, /(\/\* End PBXBuildFile section \*\/)/, `${buildFileEntry}\n$1`, "PBXBuildFile section not found while registering plugin resources");
322
+ projectContent = replaceRequired(projectContent, /(\/\* iosnativeWebView \*\/ = \{[^}]*children = \([^)]*)/, `$1\n\t\t\t\t${fileRefId} /* ${label} */,`, "iosnativeWebView group not found while registering plugin resources");
323
+ projectContent = replaceRequired(projectContent, /(\/\* Resources \*\/ = \{[^}]*files = \([^)]*)/, `$1\n\t\t\t\t${buildFileId} /* ${label} in Resources */,`, "Resources build phase not found while registering plugin resources");
324
+ }
325
+ fs.writeFileSync(projectFilePath, projectContent, "utf8");
326
+ progress.log(`Registered ${resources.length} managed plugin resource(s) in Xcode project`, "success");
327
+ } catch (error) {
328
+ throw new Error(`Could not add plugin resources to Xcode project: ${error.message}`);
329
+ }
330
+ }
331
+ async function syncPluginResources(pluginComposition = {}) {
332
+ try {
333
+ const resources = pluginComposition.resources || [];
334
+ const pluginResourceDir = path.join(PROJECT_DIR, PROJECT_NAME, PLUGIN_RESOURCE_ROOT); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
335
+ fs.rmSync(pluginResourceDir, {
336
+ recursive: true,
337
+ force: true
338
+ });
339
+ await removePluginResourcesFromXcodeProject();
340
+ if (resources.length === 0) {
341
+ progress.log("No managed plugin resources to sync", "info");
342
+ return;
343
+ }
344
+ for (const resource of resources) {
345
+ const normalizedBundleRelativePath = path.posix.normalize(resource.bundleRelativePath || "");
346
+ const expectedPrefix = `${PLUGIN_RESOURCE_ROOT}/`;
347
+ if (!normalizedBundleRelativePath.startsWith(expectedPrefix) || normalizedBundleRelativePath.includes("../") || path.posix.isAbsolute(normalizedBundleRelativePath)) {
348
+ throw new Error(`Invalid managed plugin resource path: ${resource.bundleRelativePath}`);
349
+ }
350
+ // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - The normalized path is validated above and containment is enforced immediately below.
351
+ const targetPath = path.resolve(PROJECT_DIR, PROJECT_NAME, normalizedBundleRelativePath);
352
+ if (targetPath !== pluginResourceDir && !targetPath.startsWith(`${pluginResourceDir}${path.sep}`)) {
353
+ throw new Error(`Managed plugin resource escaped bundle directory: ${resource.bundleRelativePath}`);
354
+ }
355
+ fs.mkdirSync(path.dirname(targetPath), {
356
+ recursive: true
357
+ });
358
+ fs.copyFileSync(resource.sourcePath, targetPath);
359
+ }
360
+ await addPluginResourcesToXcodeProject(resources);
361
+ progress.log(`Synced ${resources.length} managed plugin resource(s)`, "success");
362
+ } catch (error) {
363
+ progress.log(`❌ Failed to sync plugin resources: ${error.message}`, "error");
364
+ throw error;
365
+ }
366
+ }
367
+ return {
368
+ generatePackageSwift,
369
+ updateXcodeProjectPackageDependencies,
370
+ syncPluginResources
371
+ };
372
+ };
@@ -0,0 +1,43 @@
1
+ package io.yourname.androidproject.plugins.internal.deviceinfo
2
+
3
+ import android.util.Log
4
+ import io.yourname.androidproject.plugins.CatalystPlugin
5
+ import io.yourname.androidproject.plugins.PluginBridgeContext
6
+ import io.yourname.androidproject.utils.DeviceInfoUtils
7
+ import org.json.JSONObject
8
+
9
+ class DeviceInfoPlugin : CatalystPlugin {
10
+ companion object {
11
+ private const val TAG = "DeviceInfoPlugin"
12
+ private const val COMMAND_GET_DEVICE_INFO = "getDeviceInfo"
13
+ private const val CALLBACK_SUCCESS = "onSuccess"
14
+ private const val CALLBACK_ERROR = "onError"
15
+ }
16
+
17
+ override fun handle(command: String, data: JSONObject?, bridge: PluginBridgeContext) {
18
+ if (command != COMMAND_GET_DEVICE_INFO) {
19
+ bridge.callback(
20
+ CALLBACK_ERROR,
21
+ JSONObject().apply {
22
+ put("message", "Unsupported command: $command")
23
+ put("code", "UNSUPPORTED_COMMAND")
24
+ }
25
+ )
26
+ return
27
+ }
28
+
29
+ try {
30
+ val deviceInfo = DeviceInfoUtils.getDeviceInfo(bridge.context, bridge.properties)
31
+ bridge.callback(CALLBACK_SUCCESS, deviceInfo)
32
+ } catch (error: Exception) {
33
+ Log.e(TAG, "Failed to resolve device info", error)
34
+ bridge.callback(
35
+ CALLBACK_ERROR,
36
+ JSONObject().apply {
37
+ put("message", error.message ?: "Failed to get device info")
38
+ put("code", "DEVICE_INFO_ERROR")
39
+ }
40
+ )
41
+ }
42
+ }
43
+ }
@@ -0,0 +1,28 @@
1
+ import Foundation
2
+
3
+ final class DeviceInfoPlugin: CatalystPlugin {
4
+ private let commandGetDeviceInfo = "getDeviceInfo"
5
+ private let successCallback = "onSuccess"
6
+ private let errorCallback = "onError"
7
+
8
+ func handle(command: String, data: Any?, bridge: PluginBridgeContext) {
9
+ guard command == commandGetDeviceInfo else {
10
+ bridge.callback(eventName: errorCallback, data: [
11
+ "message": "Unsupported command: \(command)",
12
+ "code": "UNSUPPORTED_COMMAND",
13
+ ])
14
+ return
15
+ }
16
+
17
+ let deviceInfo = DeviceInfoUtils.getDeviceInfo()
18
+ if let error = deviceInfo["error"] as? String {
19
+ bridge.callback(eventName: errorCallback, data: [
20
+ "message": error,
21
+ "code": "DEVICE_INFO_ERROR",
22
+ ])
23
+ return
24
+ }
25
+
26
+ bridge.callback(eventName: successCallback, data: deviceInfo)
27
+ }
28
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "id": "io.catalyst.device_info",
3
+ "configKey": "deviceInfo",
4
+ "version": "1.0.0",
5
+ "displayName": "Device Info",
6
+ "description": "Provides device metadata, screen metrics, app info, and platform-specific diagnostics.",
7
+ "category": "device",
8
+ "platforms": ["android", "ios"],
9
+ "commands": ["getDeviceInfo"],
10
+ "android": {
11
+ "className": "io.yourname.androidproject.plugins.internal.deviceinfo.DeviceInfoPlugin",
12
+ "permissions": [],
13
+ "dependencies": []
14
+ },
15
+ "ios": {
16
+ "className": "DeviceInfoPlugin",
17
+ "dependencies": []
18
+ }
19
+ }