catalyst-core 0.1.0-beta.3 → 0.1.0-beta.4

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 (81) hide show
  1. package/bin/catalyst.js +8 -1
  2. package/changelog.md +6 -0
  3. package/dist/native/androidProject/app/build.gradle.kts +9 -0
  4. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/BridgeMessageValidator.kt +1 -1
  5. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +17 -1
  6. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +31 -3
  7. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +148 -0
  8. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeCameraManager.kt +337 -0
  9. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/TransitionManager.kt +202 -0
  10. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/BarcodeDetector.kt +123 -0
  11. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/CameraSessionManager.kt +244 -0
  12. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/HoldController.kt +70 -0
  13. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/TorchController.kt +57 -0
  14. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/VideoStreamState.kt +41 -0
  15. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/VideoStreamStateMachine.kt +40 -0
  16. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/ViewfinderMapper.kt +75 -0
  17. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/ZoomController.kt +156 -0
  18. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/CatalystPlugin.kt +7 -0
  19. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/GeneratedPluginIndex.kt +6 -0
  20. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/PluginBridge.kt +253 -0
  21. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +42 -5
  22. package/dist/native/androidProject/app/src/main/res/drawable/debug_viewfinder_border.xml +8 -0
  23. package/dist/native/androidProject/app/src/main/res/layout/activity_main.xml +37 -0
  24. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/plugins/PluginBridgeTest.kt +139 -0
  25. package/dist/native/bridge/WebBridge.js +5 -5
  26. package/dist/native/bridge/constants/NativeInterfaces.js +9 -5
  27. package/dist/native/bridge/hooks/index.js +1 -0
  28. package/dist/native/bridge/hooks/useCamera.js +9 -0
  29. package/dist/native/bridge/hooks/useDataProtection.js +2 -0
  30. package/dist/native/bridge/hooks/useDeviceInfo.js +2 -0
  31. package/dist/native/bridge/hooks/useFilePicker.js +2 -0
  32. package/dist/native/bridge/hooks/useGoogleSignIn.js +1 -0
  33. package/dist/native/bridge/hooks/useHapticFeedback.js +1 -0
  34. package/dist/native/bridge/hooks/useIntent.js +1 -0
  35. package/dist/native/bridge/hooks/useNativeTransition.js +43 -0
  36. package/dist/native/bridge/hooks/useNetworkStatus.js +3 -0
  37. package/dist/native/bridge/hooks/useNotification.js +2 -0
  38. package/dist/native/bridge/hooks/useSafeArea.js +2 -0
  39. package/dist/native/bridge/hooks/useVideoStream.js +6 -0
  40. package/dist/native/bridge/hooks/utils.js +1 -0
  41. package/dist/native/bridge/hooks.js +1 -137
  42. package/dist/native/bridge/useBaseHook.js +8 -3
  43. package/dist/native/bridge/utils/NativeBridge.js +36 -1
  44. package/dist/native/buildAppAndroid.js +2 -2
  45. package/dist/native/buildAppIos.js +10 -17
  46. package/dist/native/internalPluginUtils.js +1 -0
  47. package/dist/native/iosnativeWebView/Package.resolved +36 -0
  48. package/dist/native/iosnativeWebView/Sources/Core/Camera/BarcodeDetector.swift +116 -0
  49. package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraPreviewView.swift +58 -0
  50. package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraSessionManager.swift +308 -0
  51. package/dist/native/iosnativeWebView/Sources/Core/Camera/HoldController.swift +63 -0
  52. package/dist/native/iosnativeWebView/Sources/Core/Camera/NativeCameraManager.swift +194 -0
  53. package/dist/native/iosnativeWebView/Sources/Core/Camera/TorchController.swift +60 -0
  54. package/dist/native/iosnativeWebView/Sources/Core/Camera/VideoStreamState.swift +44 -0
  55. package/dist/native/iosnativeWebView/Sources/Core/Camera/VideoStreamStateMachine.swift +60 -0
  56. package/dist/native/iosnativeWebView/Sources/Core/Camera/ZoomController.swift +130 -0
  57. package/dist/native/iosnativeWebView/Sources/Core/Constants/CatalystConstants.swift +11 -1
  58. package/dist/native/iosnativeWebView/Sources/Core/Plugins/CatalystPlugin.swift +5 -0
  59. package/dist/native/iosnativeWebView/Sources/Core/Plugins/GeneratedPluginIndex.swift +6 -0
  60. package/dist/native/iosnativeWebView/Sources/Core/Plugins/PluginBridge.swift +364 -0
  61. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +4 -1
  62. package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +227 -4
  63. package/dist/native/iosnativeWebView/Sources/Core/WebView/TransitionManager.swift +169 -0
  64. package/dist/native/iosnativeWebView/Sources/Core/WebView/WeakScriptMessageHandler.swift +14 -0
  65. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +38 -7
  66. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +6 -2
  67. package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +15 -4
  68. package/dist/native/iosnativeWebView/iosnativeWebView/Info.plist +11 -17
  69. package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +8 -2
  70. package/dist/native/iosnativeWebView/iosnativeWebViewTests/PluginBridgeTests.swift +160 -0
  71. package/dist/native/plugin-bridge/PluginBridge.js +1 -0
  72. package/dist/native/pluginComposerAndroid.js +9 -0
  73. package/dist/native/pluginComposerIos.js +7 -0
  74. package/dist/otel.js +70 -2
  75. package/dist/scripts/plugins.js +1 -0
  76. package/dist/server/renderer/extract.js +3 -3
  77. package/dist/server/renderer/handler.js +15 -14
  78. package/dist/webpack/development.client.babel.js +5 -1
  79. package/dist/webpack/production.ssr.babel.js +5 -1
  80. package/mcp_v2/knowledge-base.json +1564 -1067
  81. package/package.json +1 -1
@@ -1,11 +1,11 @@
1
- "use strict";const{execFile,execSync}=require("child_process");const fs=require("fs");const path=require("path");const TerminalProgress=require("./TerminalProgress.js").default;const crypto=require("crypto");const{runInteractiveCommand}=require("./utils.js");const catalystCorePath=path.dirname(require.resolve("catalyst-core/package.json"));const pwd=path.join(catalystCorePath,"dist/native");const{WEBVIEW_CONFIG,BUILD_OUTPUT_PATH}=require(`${process.cwd()}/config/config.json`);// Configuration constants
2
- const iosConfig=WEBVIEW_CONFIG.ios;const isGoogleSignInEnabled=WEBVIEW_CONFIG.googleSignIn?.enabled??false;const protocol=WEBVIEW_CONFIG.useHttps?"https":"http";const ip=WEBVIEW_CONFIG.LOCAL_IP||"localhost";const port=WEBVIEW_CONFIG.port?WEBVIEW_CONFIG.useHttps?443:WEBVIEW_CONFIG.port:null;let url=port?`${protocol}://${ip}:${port}`:`${protocol}://${ip}`;const PUBLIC_PATH=`${process.env.PWD}/public`;const PROJECT_DIR=`${pwd}/iosnativeWebView`;const SCHEME_NAME="iosnativeWebView";const APP_BUNDLE_ID=iosConfig.appBundleId||"com.debug.webview";const PROJECT_NAME=path.basename(PROJECT_DIR);const IPHONE_MODEL=iosConfig.simulatorName;const GOOGLE_SERVICES_FILENAME="GoogleService-Info.plist";function generateProjectObjectId(identifier,suffix=""){return crypto.createHash("md5").update(`${identifier}:${suffix}`).digest("hex").substring(0,24).toUpperCase();}function getXcodeProjectFilePath(){return path.join(PROJECT_DIR,`${PROJECT_NAME}.xcodeproj`,"project.pbxproj");}// Define build steps for progress tracking
1
+ "use strict";const{execFile,execSync,execFileSync}=require("child_process");const fs=require("fs");const path=require("path");const TerminalProgress=require("./TerminalProgress.js").default;const crypto=require("crypto");const{runInteractiveCommand}=require("./utils.js");const{composeIosPlugins}=require("./pluginComposerIos.js");const{resolveInternalPluginsRoot,resolvePluginConfig}=require("./internalPluginUtils.js");const catalystCorePath=path.dirname(require.resolve("catalyst-core/package.json"));const pwd=path.join(catalystCorePath,"dist/native");const{WEBVIEW_CONFIG,BUILD_OUTPUT_PATH}=require(`${process.cwd()}/config/config.json`);// Configuration constants
2
+ const iosConfig=WEBVIEW_CONFIG.ios;const isGoogleSignInEnabled=WEBVIEW_CONFIG.googleSignIn?.enabled??false;const protocol=WEBVIEW_CONFIG.useHttps?"https":"http";const ip=WEBVIEW_CONFIG.LOCAL_IP||"localhost";const port=WEBVIEW_CONFIG.port?WEBVIEW_CONFIG.useHttps?443:WEBVIEW_CONFIG.port:null;let url=port?`${protocol}://${ip}:${port}`:`${protocol}://${ip}`;const PUBLIC_PATH=`${process.env.PWD}/public`;const PROJECT_DIR=`${pwd}/iosnativeWebView`;const SCHEME_NAME="iosnativeWebView";const APP_BUNDLE_ID=iosConfig.appBundleId||"com.debug.webview";const PROJECT_NAME=path.basename(PROJECT_DIR);const IPHONE_MODEL=iosConfig.simulatorName;const GOOGLE_SERVICES_FILENAME="GoogleService-Info.plist";const MANAGED_BASELINE_SUFFIX=".catalyst-base";const PLUGIN_RESOURCE_ROOT="PluginResources";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(dependency=>` .package(url: "${dependency.url}", ${formatSwiftPackageRequirement(dependency)})`);}function formatSwiftProductEntries(dependencies){return dependencies.flatMap(dependency=>dependency.products.map(product=>` .product(name: "${product}", package: "${dependency.package}")`));}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 iOS build metadata`);}function ensureManagedBaseline(filePath){const baselinePath=`${filePath}${MANAGED_BASELINE_SUFFIX}`;if(!fs.existsSync(baselinePath)){if(!fs.existsSync(filePath)){throw new Error(`Managed baseline source file not found: ${filePath}`);}fs.copyFileSync(filePath,baselinePath);}return baselinePath;}function restoreManagedFileFromBaseline(filePath){const baselinePath=ensureManagedBaseline(filePath);fs.copyFileSync(baselinePath,filePath);}function readPlistObject(filePath){const output=execFileSync("plutil",["-convert","json","-o","-",filePath],{encoding:"utf8"});return JSON.parse(output);}function writePlistObject(filePath,value){const tempPath=path.join(PROJECT_DIR,`.catalyst-${path.basename(filePath)}-${process.pid}.json`);fs.writeFileSync(tempPath,JSON.stringify(value,null,2),"utf8");try{execFileSync("plutil",["-convert","xml1","-o",filePath,tempPath]);}finally{fs.rmSync(tempPath,{force:true});}}function mergeIntoTopLevelObject(target,source,fieldName){for(const[key,value]of Object.entries(source||{})){target[key]=mergeStructuredValues(target[key],value,`${fieldName}.${key}`);}}function mergeUrlSchemes(plistObject,entries){const existing=Array.isArray(plistObject.CFBundleURLTypes)?plistObject.CFBundleURLTypes:[];const merged=existing.map(entry=>({...entry,CFBundleURLSchemes:Array.isArray(entry?.CFBundleURLSchemes)?[...new Set(entry.CFBundleURLSchemes)]:[]}));for(const entry of entries){const targetName=entry.name||null;let match=merged.find(item=>item.CFBundleURLName===targetName);if(!match){match={CFBundleURLName:targetName,CFBundleURLSchemes:[]};merged.push(match);}match.CFBundleURLSchemes=[...new Set([...match.CFBundleURLSchemes,...entry.schemes])];}if(merged.length>0){plistObject.CFBundleURLTypes=merged;}}function mergeQuerySchemes(plistObject,schemes){const existing=Array.isArray(plistObject.LSApplicationQueriesSchemes)?plistObject.LSApplicationQueriesSchemes:[];const merged=[...new Set([...existing,...schemes.filter(Boolean)])];if(merged.length>0){plistObject.LSApplicationQueriesSchemes=merged;}}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 getXcodeProjectFilePath(){return path.join(PROJECT_DIR,`${PROJECT_NAME}.xcodeproj`,"project.pbxproj");}function replaceRequired(content,pattern,replacement,errorMessage){if(!pattern.test(content)){throw new Error(errorMessage);}return content.replace(pattern,replacement);}// Define build steps for progress tracking
3
3
  const steps={config:"Generating Required Configuration for build",deviceDetection:"Detecting Physical Device",launchSimulator:"Launch iOS Simulator",clean:"Clean Build Artifacts",assets:"Process Notification Assets",build:"Build IOS Project",findApp:"Locate Built Application",install:"Install Application",launch:"Launch Application"};// Configure progress display
4
4
  const progressConfig={titlePaddingTop:2,titlePaddingBottom:1,stepPaddingLeft:4,stepSpacing:1,errorPaddingLeft:6,bottomMargin:2};const progress=new TerminalProgress(steps,"Catalyst iOS Build",progressConfig);const shellCommand=process.platform==="win32"?"cmd.exe":"sh";const shellArgs=command=>process.platform==="win32"?["/d","/s","/c",command]:["-c",command];// Utility function to run shell commands
5
5
  function runCommand(command,options={}){return new Promise((resolve,reject)=>{execFile(shellCommand,shellArgs(command),{maxBuffer:1024*1024*10,...options},(error,stdout,stderr)=>{if(error){console.error(`Command failed: ${command}`);console.error(`Error: ${error.message}`);console.error(`stderr: ${stderr}`);reject(error);return;}if(stderr){console.warn(`Warning: ${stderr}`);}resolve(stdout.trim());});});}async function getBootedSimulatorUUID(modelName){const UUID_RE=/[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}/i;try{const output=await runInteractiveCommand("xcrun",["simctl","list","devices"],{});const lines=output.split("\n");// First pass: find a booted simulator matching modelName
6
6
  for(const line of lines){if(line.includes(modelName)&&line.includes("Booted")){const m=line.match(UUID_RE);if(m){console.log(`Found booted simulator of model ${modelName}`);return m[0];}}}// Second pass: any booted simulator
7
- console.log(`No booted simulator of model ${modelName} found, checking for any booted simulator...`);for(const line of lines){if(line.includes("Booted")){const m=line.match(UUID_RE);if(m){console.log("Found another booted simulator, will use it instead");return m[0];}}}return null;}catch(error){console.log("No booted simulators found");return null;}}async function getBootedSimulatorInfo(){try{const listCommand="xcrun simctl list devices --json";const simulatorList=JSON.parse(execSync(listCommand).toString());for(const runtime in simulatorList.devices){const devices=simulatorList.devices[runtime];for(const device of devices){if(device.state==="Booted"){return{udid:device.udid,name:device.name,runtime:runtime};}}}return null;}catch(error){console.log("Failed to get booted simulator info:",error.message);return null;}}async function generatePackageSwift(){try{const isNotificationsEnabled=WEBVIEW_CONFIG.notifications?.enabled??false;progress.log(`🔧 Generating Package.swift (notifications: ${isNotificationsEnabled})`,"info");// Create hash of current config to detect changes
8
- const configHash=crypto.createHash("md5").update(JSON.stringify({notifications:isNotificationsEnabled,googleSignIn:isGoogleSignInEnabled})).digest("hex");const hashFilePath=path.join(PROJECT_DIR,".package-config-hash");const targetPath=path.join(PROJECT_DIR,"Package.swift");let shouldUpdate=true;// Check if we need to update based on config change
7
+ console.log(`No booted simulator of model ${modelName} found, checking for any booted simulator...`);for(const line of lines){if(line.includes("Booted")){const m=line.match(UUID_RE);if(m){console.log("Found another booted simulator, will use it instead");return m[0];}}}return null;}catch(error){console.log("No booted simulators found");return null;}}async function getBootedSimulatorInfo(){try{const listCommand="xcrun simctl list devices --json";const simulatorList=JSON.parse(execSync(listCommand).toString());for(const runtime in simulatorList.devices){const devices=simulatorList.devices[runtime];for(const device of devices){if(device.state==="Booted"){return{udid:device.udid,name:device.name,runtime:runtime};}}}return null;}catch(error){console.log("Failed to get booted simulator info:",error.message);return null;}}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(dependency=>mergePackageDependency(coreDependencyMap,dependency,"base core"));pluginDependencies.forEach(dependency=>mergePackageDependency(coreDependencyMap,dependency,"plugin manifests"));const coreDependencies=[...coreDependencyMap.values()].sort((left,right)=>left.url.localeCompare(right.url));const packageDependencyMap=new Map();coreDependencies.forEach(dependency=>mergePackageDependency(packageDependencyMap,dependency,"core target"));notificationsDependencies.forEach(dependency=>mergePackageDependency(packageDependencyMap,dependency,"notifications target"));const packageDependencies=[...packageDependencyMap.values()].sort((left,right)=>left.url.localeCompare(right.url));progress.log(`🔧 Generating Package.swift (notifications: ${isNotificationsEnabled})`,"info");// Create hash of current config to detect changes
8
+ 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");const targetPath=path.join(PROJECT_DIR,"Package.swift");let shouldUpdate=true;// Check if we need to update based on config change
9
9
  if(fs.existsSync(hashFilePath)){const previousHash=fs.readFileSync(hashFilePath,"utf8");if(previousHash===configHash){shouldUpdate=false;progress.log("Package.swift already up to date","info");}}// Ensure Package.swift exists even if hash matches
10
10
  if(!fs.existsSync(targetPath)){shouldUpdate=true;progress.log("Package.swift missing, will generate it now","info");}if(shouldUpdate){progress.log("Generating Package.swift dynamically","info");// Build the Package.swift content dynamically
11
11
  let packageContent=`// swift-tools-version: 5.9
@@ -22,10 +22,7 @@ if(isNotificationsEnabled){packageContent+=`,
22
22
  .library(name: "CatalystNotifications", targets: ["CatalystNotifications"])`;}packageContent+=`
23
23
  ],
24
24
  dependencies: [
25
- .package(url: "https://github.com/kylef/JSONSchema.swift", from: "0.6.0"),
26
- .package(url: "https://github.com/google/GoogleSignIn-iOS", from: "7.0.0")`;// Add Firebase dependency only if notifications enabled
27
- if(isNotificationsEnabled){packageContent+=`,
28
- .package(url: "https://github.com/firebase/firebase-ios-sdk", from: "12.3.0")`;}packageContent+=`
25
+ ${formatSwiftPackageEntries(packageDependencies).join(",\n")}`;packageContent+=`
29
26
  ],
30
27
  targets: [
31
28
  // Core functionality (WebView, bridge, utils, constants)
@@ -33,8 +30,7 @@ if(isNotificationsEnabled){packageContent+=`,
33
30
  .target(
34
31
  name: "CatalystCore",
35
32
  dependencies: [
36
- .product(name: "JSONSchema", package: "JSONSchema.swift"),
37
- .product(name: "GoogleSignIn", package: "GoogleSignIn-iOS")
33
+ ${formatSwiftProductEntries(coreDependencies).join(",\n")}
38
34
  ],
39
35
  path: "Sources/Core"
40
36
  )`;// Add CatalystNotifications target only if enabled
@@ -44,8 +40,7 @@ if(isNotificationsEnabled){packageContent+=`,
44
40
  name: "CatalystNotifications",
45
41
  dependencies: [
46
42
  "CatalystCore",
47
- .product(name: "FirebaseCore", package: "firebase-ios-sdk"),
48
- .product(name: "FirebaseMessaging", package: "firebase-ios-sdk")
43
+ ${formatSwiftProductEntries(notificationsDependencies).join(",\n")}
49
44
  ],
50
45
  path: "Sources/CatalystNotifications"
51
46
  )`;}packageContent+=`
@@ -64,9 +59,7 @@ projectContent=projectContent.replace(/(C99974342E97D56900C25611 \/\* CatalystCo
64
59
  projectContent=projectContent.replace(/(C99974342E97D56900C25611 \/\* CatalystCore in Frameworks \*\/,)/,`$1\n\t\t\t\t${NOTIF_BUILD_FILE_ID} /* CatalystNotifications in Frameworks */,`);// 3. Add to packageProductDependencies array
65
60
  projectContent=projectContent.replace(/(packageProductDependencies = \(\s*C99974332E97D56900C25611 \/\* CatalystCore \*\/,)/,`$1\n\t\t\t\t${NOTIF_PRODUCT_ID} /* CatalystNotifications */,`);// 4. Add to XCSwiftPackageProductDependency section
66
61
  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");// Remove all CatalystNotifications entries
67
- 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 updateInfoPlist(){try{const infoPlistPath=path.join(PROJECT_DIR,PROJECT_NAME,"Info.plist");const infoReleasePlistPath=path.join(PROJECT_DIR,PROJECT_NAME,"Info-Release.plist");const googleServicesPlistPath=path.join(PROJECT_DIR,PROJECT_NAME,GOOGLE_SERVICES_FILENAME);const googleClientId=WEBVIEW_CONFIG.googleSignIn?.clientId||WEBVIEW_CONFIG.googleSignIn?.webClientId||"";const iosClientId=WEBVIEW_CONFIG.googleSignIn?.iosClientId||"";const googleServicesContent=fs.existsSync(googleServicesPlistPath)?fs.readFileSync(googleServicesPlistPath,"utf8"):null;const reversedClientIdFromServices=googleServicesContent?(googleServicesContent.match(/<key>REVERSED_CLIENT_ID<\/key>\s*<string>([^<]+)<\/string>/)||[])[1]:null;const clientIdFromServices=googleServicesContent?(googleServicesContent.match(/<key>CLIENT_ID<\/key>\s*<string>([^<]+)<\/string>/)||[])[1]:null;const computeReversed=value=>{if(!value)return"";return value.split(".").reverse().join(".");};const resolvedClientIdForScheme=iosClientId||googleClientId||clientIdFromServices||"";const resolvedReversedClientId=reversedClientIdFromServices||computeReversed(resolvedClientIdForScheme);if(isGoogleSignInEnabled&&!resolvedReversedClientId){progress.fail("config","Google Sign-In enabled but no valid clientId found");process.exit(1);}const plistTargets=[infoPlistPath,infoReleasePlistPath];const findMatchingArrayCloseTag=(content,arrayStartIndex)=>{if(arrayStartIndex<0)return-1;const tokenRegex=/<array>|<\/array>/g;tokenRegex.lastIndex=arrayStartIndex;let depth=0;let match;while((match=tokenRegex.exec(content))!==null){if(match[0]==="<array>"){depth+=1;}else{depth-=1;if(depth===0){return match.index;}}}return-1;};const ensureGoogleUrlScheme=plistPath=>{if(!isGoogleSignInEnabled||!resolvedReversedClientId){return;}if(!fs.existsSync(plistPath)){return;}let plistContent=fs.readFileSync(plistPath,"utf8");if(plistContent.includes(resolvedReversedClientId)){return;}const urlTypeEntry=`\n\t<key>CFBundleURLTypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>CFBundleURLName</key>\n\t\t\t<string>googleSignIn</string>\n\t\t\t<key>CFBundleURLSchemes</key>\n\t\t\t<array>\n\t\t\t\t<string>${resolvedReversedClientId}</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n`;const urlTypeDictEntry=`\n\t\t<dict>\n\t\t\t<key>CFBundleURLName</key>\n\t\t\t<string>googleSignIn</string>\n\t\t\t<key>CFBundleURLSchemes</key>\n\t\t\t<array>\n\t\t\t\t<string>${resolvedReversedClientId}</string>\n\t\t\t</array>\n\t\t</dict>\n`;if(plistContent.includes("<key>CFBundleURLTypes</key>")){const urlTypesKeyIndex=plistContent.indexOf("<key>CFBundleURLTypes</key>");const urlTypesArrayStart=plistContent.indexOf("<array>",urlTypesKeyIndex);const urlTypesArrayClose=findMatchingArrayCloseTag(plistContent,urlTypesArrayStart);if(urlTypesArrayStart>=0&&urlTypesArrayClose>=0){plistContent=plistContent.slice(0,urlTypesArrayClose)+urlTypeDictEntry+plistContent.slice(urlTypesArrayClose);}else{const insertPoint=plistContent.lastIndexOf("</dict>");plistContent=plistContent.slice(0,insertPoint)+urlTypeEntry+plistContent.slice(insertPoint);progress.log(`CFBundleURLTypes block could not be parsed in ${path.basename(plistPath)}; appended block instead`,"warning");}}else{const insertPoint=plistContent.lastIndexOf("</dict>");plistContent=plistContent.slice(0,insertPoint)+urlTypeEntry+plistContent.slice(insertPoint);}fs.writeFileSync(plistPath,plistContent,"utf8");progress.log(`Added Google Sign-In URL scheme to ${path.basename(plistPath)}`,"info");};const ensureLSApplicationQueriesSchemes=plistPath=>{if(!isGoogleSignInEnabled||!resolvedReversedClientId){return;}if(!fs.existsSync(plistPath)){return;}let plistContent=fs.readFileSync(plistPath,"utf8");const matches=plistContent.match(/<key>LSApplicationQueriesSchemes<\/key>\s*<array>([\s\S]*?)<\/array>/);const existingSchemes=new Set();if(matches&&matches[1]){const schemeRegex=/<string>([^<]+)<\/string>/g;let match;while((match=schemeRegex.exec(matches[1]))!==null){existingSchemes.add(match[1]);}}const targetSchemes=["google",resolvedReversedClientId];targetSchemes.forEach(scheme=>{if(scheme&&!existingSchemes.has(scheme)){existingSchemes.add(scheme);}});const schemesArrayContent=Array.from(existingSchemes).map(s=>`\n\t\t<string>${s}</string>`).join("");const newLSBlock=`\n\t<key>LSApplicationQueriesSchemes</key>\n\t<array>${schemesArrayContent}\n\t</array>\n`;if(matches){plistContent=plistContent.replace(/<key>LSApplicationQueriesSchemes<\/key>\s*<array>[\s\S]*?<\/array>/,newLSBlock.trimEnd());}else{const insertPoint=plistContent.lastIndexOf("</dict>");plistContent=plistContent.slice(0,insertPoint)+newLSBlock+plistContent.slice(insertPoint);}fs.writeFileSync(plistPath,plistContent,"utf8");progress.log(`Ensured LSApplicationQueriesSchemes for Google in ${path.basename(plistPath)}`,"info");};const ensureDisplayName=plistPath=>{if(!fs.existsSync(plistPath))return;let plistContent=fs.readFileSync(plistPath,"utf8");// Add CFBundleDisplayName if it doesn't exist
68
- if(!plistContent.includes("CFBundleDisplayName")){const insertPoint=plistContent.lastIndexOf("</dict>");const newEntry=`\t<key>CFBundleDisplayName</key>\n\t<string>${iosConfig.appName||"Catalyst Application"}</string>\n`;plistContent=plistContent.slice(0,insertPoint)+newEntry+plistContent.slice(insertPoint);fs.writeFileSync(plistPath,plistContent,"utf8");}else{// Update existing CFBundleDisplayName with new appName
69
- const displayNameRegex=/(<key>CFBundleDisplayName<\/key>\s*<string>)([^<]*)(<\/string>)/;if(displayNameRegex.test(plistContent)){plistContent=plistContent.replace(displayNameRegex,`$1${iosConfig.appName||"Catalyst Application"}$3`);fs.writeFileSync(plistPath,plistContent,"utf8");}}};plistTargets.forEach(plistPath=>{ensureDisplayName(plistPath);ensureGoogleUrlScheme(plistPath);ensureLSApplicationQueriesSchemes(plistPath);});}catch(err){progress.fail("config",err);process.exit(1);}}// Function to convert JSON value to Swift property
62
+ 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 updateInfoPlist(pluginComposition={}){try{const infoPlistPath=path.join(PROJECT_DIR,PROJECT_NAME,"Info.plist");const infoReleasePlistPath=path.join(PROJECT_DIR,PROJECT_NAME,"Info-Release.plist");const googleServicesPlistPath=path.join(PROJECT_DIR,PROJECT_NAME,GOOGLE_SERVICES_FILENAME);const googleClientId=WEBVIEW_CONFIG.googleSignIn?.clientId||WEBVIEW_CONFIG.googleSignIn?.webClientId||"";const iosClientId=WEBVIEW_CONFIG.googleSignIn?.iosClientId||"";const googleServicesContent=fs.existsSync(googleServicesPlistPath)?fs.readFileSync(googleServicesPlistPath,"utf8"):null;const reversedClientIdFromServices=googleServicesContent?(googleServicesContent.match(/<key>REVERSED_CLIENT_ID<\/key>\s*<string>([^<]+)<\/string>/)||[])[1]:null;const clientIdFromServices=googleServicesContent?(googleServicesContent.match(/<key>CLIENT_ID<\/key>\s*<string>([^<]+)<\/string>/)||[])[1]:null;const computeReversed=value=>{if(!value)return"";return value.split(".").reverse().join(".");};const resolvedClientIdForScheme=iosClientId||googleClientId||clientIdFromServices||"";const resolvedReversedClientId=reversedClientIdFromServices||computeReversed(resolvedClientIdForScheme);if(isGoogleSignInEnabled&&!resolvedReversedClientId){progress.fail("config","Google Sign-In enabled but no valid clientId found");process.exit(1);}const plistTargets=[infoPlistPath,infoReleasePlistPath];const pluginUrlSchemes=pluginComposition.urlSchemes||[];const pluginQuerySchemes=pluginComposition.querySchemes||[];const pluginInfoPlist=pluginComposition.infoPlist||{};plistTargets.forEach(plistPath=>{if(!fs.existsSync(plistPath)){return;}restoreManagedFileFromBaseline(plistPath);const plistObject=readPlistObject(plistPath);plistObject.CFBundleDisplayName=iosConfig.appName||"Catalyst Application";mergeIntoTopLevelObject(plistObject,pluginInfoPlist,"ios.infoPlist");if(isGoogleSignInEnabled&&resolvedReversedClientId){mergeUrlSchemes(plistObject,[{name:"googleSignIn",schemes:[resolvedReversedClientId]}]);mergeQuerySchemes(plistObject,["google",resolvedReversedClientId]);}mergeUrlSchemes(plistObject,pluginUrlSchemes);mergeQuerySchemes(plistObject,pluginQuerySchemes);writePlistObject(plistPath,plistObject);});}catch(err){progress.fail("config",err);process.exit(1);}}async function updateEntitlements(pluginComposition={}){try{const entitlementsPath=path.join(PROJECT_DIR,PROJECT_NAME,`${PROJECT_NAME}.entitlements`);if(!fs.existsSync(entitlementsPath)){return;}restoreManagedFileFromBaseline(entitlementsPath);const entitlementsObject=readPlistObject(entitlementsPath);mergeIntoTopLevelObject(entitlementsObject,pluginComposition.entitlements||{},"ios.entitlements");writePlistObject(entitlementsPath,entitlementsObject);}catch(error){progress.fail("config",error);process.exit(1);}}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");const groupPattern=/(\/\* iosnativeWebView \*\/ = \{[^}]*children = \([^)]*)/;projectContent=replaceRequired(projectContent,groupPattern,`$1\n\t\t\t\t${fileRefId} /* ${label} */,`,"iosnativeWebView group not found while registering plugin resources");const resourcesPattern=/(\/\* Resources \*\/ = \{[^}]*files = \([^)]*)/;projectContent=replaceRequired(projectContent,resourcesPattern,`$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);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}`);}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;}}// Function to convert JSON value to Swift property
70
63
  function generateSwiftProperty(key,value,indent=" "){if(value===null||value===undefined){return`${indent}public static let ${key}: String? = nil`;}// Special handling for cachePattern - always convert to array
71
64
  if(key==="cachePattern"){if(typeof value==="string"){// Convert single string to array
72
65
  return`${indent}public static let ${key}: [String] = ["${value}"]`;}else if(Array.isArray(value)){const arrayValues=value.map(v=>`"${v}"`).join(", ");return`${indent}public static let ${key}: [String] = [${arrayValues}]`;}}if(typeof value==="string"){return`${indent}public static let ${key} = "${value}"`;}if(typeof value==="number"){return Number.isInteger(value)?`${indent}public static let ${key} = ${value}`:`${indent}public static let ${key} = ${value}`;}if(typeof value==="boolean"){return`${indent}public static let ${key} = ${value}`;}if(Array.isArray(value)){if(value.length===0){return`${indent}public static let ${key}: [String] = []`;}// Determine array type from first element
@@ -281,7 +274,7 @@ progress.log("Opening Simulator.app...");await runCommand("open -a Simulator");/
281
274
  await new Promise(resolve=>setTimeout(resolve,1000));// Activate the Simulator.app window to bring it to front
282
275
  await runCommand("osascript -e 'tell application \"Simulator\" to activate'");progress.log("iOS Simulator launched successfully.","success");progress.complete("launchSimulator");}catch(error){progress.fail("launchSimulator",error.message);// Show detailed troubleshooting info
283
276
  progress.printTreeContent("Simulator Troubleshooting",["iOS Simulator failed to launch. Common solutions:",{text:"Delete and recreate the simulator in Xcode",indent:1,prefix:"├─ ",color:"yellow"},{text:"Reset simulator content: Device > Erase All Content and Settings",indent:1,prefix:"├─ ",color:"yellow"},{text:"Check available simulators: xcrun simctl list devices",indent:1,prefix:"├─ ",color:"yellow"},{text:"Restart Xcode and Simulator app",indent:1,prefix:"└─ ",color:"yellow"},"","Error Details:",{text:`Simulator: ${simulatorName}`,indent:1,prefix:"├─ ",color:"gray"},{text:`Error: ${error.message}`,indent:1,prefix:"└─ ",color:"red"}]);console.error("Failed to launch iOS Simulator. Error:",error.message);process.exit(1);}}// Separate build function with proper device routing
284
- async function buildForIOS(){const originalDir=process.cwd();try{await generatePackageSwift();await updateXcodeProjectPackageDependencies();// Process notification assets
277
+ async function buildForIOS(pluginComposition={}){const originalDir=process.cwd();try{await generatePackageSwift(pluginComposition.iosDependencies);await updateXcodeProjectPackageDependencies();// Process notification assets
285
278
  progress.start("assets");await processNotificationAssets(WEBVIEW_CONFIG);await copyOfflinePage();progress.complete("assets");await generateXCConfig();await copySplashscreenAssets();await copyAppIcon();progress.log("Changing directory to: "+PROJECT_DIR,"info");process.chdir(PROJECT_DIR);// Force physical device detection (bypass shouldUsePhysicalDevice check)
286
279
  const physicalDevice=await detectPhysicalDevices();let APP_PATH;let targetInfo;if(physicalDevice){// Physical device workflow
287
280
  progress.log("🔥 Building for physical device workflow","success");targetInfo={type:"physical",name:physicalDevice.name,udid:physicalDevice.udid};await cleanBuildArtifacts();progress.start("build");try{await buildProjectForPhysicalDevice(SCHEME_NAME,APP_BUNDLE_ID,path.join(process.env.HOME,"Library/Developer/Xcode/DerivedData"),PROJECT_NAME,physicalDevice);progress.complete("build");}catch(error){progress.fail("build",error.message);progress.printTreeContent("Physical Device Build Failed",["Build failed. Please check:",{text:"Code signing certificates are properly installed",indent:1,prefix:"├─ ",color:"yellow"},{text:"Provisioning profile matches your bundle ID",indent:1,prefix:"├─ ",color:"yellow"},{text:"Device is connected and trusted",indent:1,prefix:"└─ ",color:"yellow"}]);throw error;}progress.start("findApp");try{APP_PATH=await findPhysicalDeviceAppPath();progress.log("Found app at: "+APP_PATH,"success");progress.complete("findApp");}catch(error){progress.fail("findApp",error.message);throw error;}await installAndLaunchOnPhysicalDevice(APP_PATH,physicalDevice);}else{// Simulator workflow (with moveAppToBuildOutput improvement)
@@ -308,4 +301,4 @@ fs.copyFileSync(foundImage.path,destinationPath);foundIcons.push({size,scale,fil
308
301
  const iconKey=`${size}-${idiom}-${scale}`;addedIcons.add(iconKey);// Remove existing entry with same size/idiom/scale
309
302
  contents.images=contents.images.filter(img=>`${img.size}-${img.idiom}-${img.scale}`!==iconKey);// Add new entry
310
303
  contents.images.push({size,idiom,scale,filename});}}if(foundIcons.length>0){// Write updated Contents.json
311
- fs.writeFileSync(contentsPath,JSON.stringify(contents,null,2));progress.log(`Updated AppIcon.appiconset with ${foundIcons.length} icon(s):`,"success");foundIcons.forEach(icon=>{progress.log(` • ${icon.size} @${icon.scale} (${icon.idiom})`,"info");});}else{progress.log("No app icon files found in public folder","info");progress.log("Expected naming pattern: icon-{size}-{scale}.{ext}","info");progress.log("Example: icon-20x20-2x.png, icon-60x60-3x.png, icon-1024x1024-1x.png","info");}}catch(error){progress.log(`Warning: Error copying app icons: ${error.message}`,"warning");}}async function main(){try{progress.log("Starting build process...","info");await generateConfigConstants();await updateInfoPlist();await buildForIOS();}catch(error){progress.log("Build failed: "+error.message,"error");process.exit(1);}process.exit(0);}main();
304
+ fs.writeFileSync(contentsPath,JSON.stringify(contents,null,2));progress.log(`Updated AppIcon.appiconset with ${foundIcons.length} icon(s):`,"success");foundIcons.forEach(icon=>{progress.log(` • ${icon.size} @${icon.scale} (${icon.idiom})`,"info");});}else{progress.log("No app icon files found in public folder","info");progress.log("Expected naming pattern: icon-{size}-{scale}.{ext}","info");progress.log("Example: icon-20x20-2x.png, icon-60x60-3x.png, icon-1024x1024-1x.png","info");}}catch(error){progress.log(`Warning: Error copying app icons: ${error.message}`,"warning");}}async function main(){try{progress.log("Starting build process...","info");const pluginConfig=resolvePluginConfig(WEBVIEW_CONFIG);const pluginComposition=composeIosPlugins({corePluginsRoot:resolveInternalPluginsRoot(catalystCorePath),iosProjectPath:PROJECT_DIR,pluginConfig,log:(message,status="info")=>progress.log(message,status)});await generateConfigConstants();await updateInfoPlist(pluginComposition);await updateEntitlements(pluginComposition);await syncPluginResources(pluginComposition);await buildForIOS(pluginComposition);}catch(error){progress.log("Build failed: "+error.message,"error");process.exit(1);}process.exit(0);}main();
@@ -0,0 +1 @@
1
+ "use strict";const fs=require("fs");const path=require("path");function isDir(dirPath){return fs.existsSync(dirPath)&&fs.statSync(dirPath).isDirectory();}function mustBeNonEmptyString(value,fieldName,sourcePath){if(typeof value!=="string"||!value.trim()){throw new Error(`Invalid '${fieldName}' in ${sourcePath}`);}return value.trim();}function readStringArray(value,fieldName,sourcePath,{required=false,nonEmpty=false}={}){if(!Array.isArray(value)){if(required){throw new Error(`'${fieldName}' is required and must be an array in ${sourcePath}`);}return[];}const result=value.map(entry=>mustBeNonEmptyString(entry,`${fieldName}[]`,sourcePath));if(nonEmpty&&result.length===0){throw new Error(`'${fieldName}' is required and must be non-empty in ${sourcePath}`);}return result;}function readPlainObject(value,fieldName,sourcePath){if(value==null){return{};}if(!value||typeof value!=="object"||Array.isArray(value)){throw new Error(`'${fieldName}' must be an object in ${sourcePath}`);}return value;}function cloneJsonValue(value,fieldName,sourcePath){try{return JSON.parse(JSON.stringify(value));}catch(error){throw new Error(`'${fieldName}' must be JSON-serializable in ${sourcePath}`);}}function readIosUrlSchemes(value,fieldName,sourcePath){if(value==null){return[];}if(!Array.isArray(value)){throw new Error(`'${fieldName}' must be an array in ${sourcePath}`);}return value.map((entry,index)=>{const entryField=`${fieldName}[${index}]`;if(!entry||typeof entry!=="object"||Array.isArray(entry)){throw new Error(`'${entryField}' must be an object in ${sourcePath}`);}return{name:entry.name==null?null:mustBeNonEmptyString(entry.name,`${entryField}.name`,sourcePath),schemes:readStringArray(entry.schemes,`${entryField}.schemes`,sourcePath,{required:true,nonEmpty:true})};});}function readIosDependencies(value,fieldName,sourcePath){if(value==null){return[];}if(!Array.isArray(value)){throw new Error(`'${fieldName}' must be an array in ${sourcePath}`);}return value.map((entry,index)=>{const entryField=`${fieldName}[${index}]`;if(!entry||typeof entry!=="object"||Array.isArray(entry)){throw new Error(`'${entryField}' must be an object in ${sourcePath}`);}const url=mustBeNonEmptyString(entry.url,`${entryField}.url`,sourcePath);const packageIdentity=entry.package==null?derivePackageIdentityFromUrl(url,`${entryField}.url`,sourcePath):mustBeNonEmptyString(entry.package,`${entryField}.package`,sourcePath);const products=readStringArray(entry.products,`${entryField}.products`,sourcePath,{required:true,nonEmpty:true});if(new Set(products).size!==products.length){throw new Error(`Duplicate product(s) found in '${entryField}.products' in ${sourcePath}`);}const hasFrom=entry.from!=null;const hasExact=entry.exact!=null;if(hasFrom===hasExact){throw new Error(`'${entryField}' must define exactly one version requirement: 'from' or 'exact' in ${sourcePath}`);}return{url,package:packageIdentity,products,requirement:hasFrom?{type:"from",version:mustBeNonEmptyString(entry.from,`${entryField}.from`,sourcePath)}:{type:"exact",version:mustBeNonEmptyString(entry.exact,`${entryField}.exact`,sourcePath)}};});}function derivePackageIdentityFromUrl(url,fieldName,sourcePath){const sanitizedUrl=url.replace(/\/+$/,"");const packageIdentity=sanitizedUrl.split("/").pop()?.replace(/\.git$/,"");if(!packageIdentity){throw new Error(`Unable to derive package identity from '${fieldName}' in ${sourcePath}`);}return packageIdentity;}function parsePluginManifest(pluginDir){const manifestPath=path.join(pluginDir,"manifest.json");if(!fs.existsSync(manifestPath)){return null;}let manifestContent;try{manifestContent=fs.readFileSync(manifestPath,"utf8");}catch(error){throw new Error(`Failed to read plugin manifest at ${manifestPath}: ${error.message}`);}let manifest;try{manifest=JSON.parse(manifestContent);}catch(error){throw new Error(`Invalid JSON in plugin manifest ${manifestPath}: ${error.message}`);}const id=mustBeNonEmptyString(manifest.id,"id",manifestPath);const configKey=mustBeNonEmptyString(manifest.configKey,"configKey",manifestPath);const platforms=readStringArray(manifest.platforms,"platforms",manifestPath,{required:true,nonEmpty:true});const androidConfig=platforms.includes("android")?manifest.android:null;const iosConfig=platforms.includes("ios")?manifest.ios:null;if(platforms.includes("android")&&(!androidConfig||typeof androidConfig!=="object")){throw new Error(`'android' config is required for plugin '${id}' in ${manifestPath}`);}if(platforms.includes("ios")&&(!iosConfig||typeof iosConfig!=="object")){throw new Error(`'ios' config is required for plugin '${id}' in ${manifestPath}`);}return{pluginDir,manifestPath,id,configKey,version:mustBeNonEmptyString(manifest.version,"version",manifestPath),displayName:mustBeNonEmptyString(manifest.displayName,"displayName",manifestPath),description:mustBeNonEmptyString(manifest.description,"description",manifestPath),category:mustBeNonEmptyString(manifest.category,"category",manifestPath),platforms,commands:readStringArray(manifest.commands,"commands",manifestPath,{required:true,nonEmpty:true}),android:androidConfig?{sourceDir:path.join(pluginDir,"android"),permissions:readStringArray(androidConfig.permissions,"android.permissions",manifestPath),dependencies:readStringArray(androidConfig.dependencies,"android.dependencies",manifestPath),className:mustBeNonEmptyString(androidConfig.className,"android.className",manifestPath)}:null,ios:iosConfig?{sourceDir:path.join(pluginDir,"ios"),dependencies:readIosDependencies(iosConfig.dependencies,"ios.dependencies",manifestPath),className:mustBeNonEmptyString(iosConfig.className,"ios.className",manifestPath),infoPlist:cloneJsonValue(readPlainObject(iosConfig.infoPlist,"ios.infoPlist",manifestPath),"ios.infoPlist",manifestPath),urlSchemes:readIosUrlSchemes(iosConfig.urlSchemes,"ios.urlSchemes",manifestPath),querySchemes:readStringArray(iosConfig.querySchemes,"ios.querySchemes",manifestPath),entitlements:cloneJsonValue(readPlainObject(iosConfig.entitlements,"ios.entitlements",manifestPath),"ios.entitlements",manifestPath),resources:readStringArray(iosConfig.resources,"ios.resources",manifestPath)}:null};}function discoverInternalPlugins(corePluginsRoot,log=()=>{}){if(!corePluginsRoot||!isDir(corePluginsRoot)){log(`No internal plugin directory found at ${corePluginsRoot||"<empty>"}`,"info");return[];}const plugins=[];const entries=fs.readdirSync(corePluginsRoot,{withFileTypes:true}).sort((left,right)=>left.name.localeCompare(right.name));for(const entry of entries){if(!entry.isDirectory()){continue;}const pluginDir=path.join(corePluginsRoot,entry.name);const parsed=parsePluginManifest(pluginDir);if(parsed){plugins.push(parsed);}}log(`Discovered ${plugins.length} internal plugin manifest(s)`,"info");return plugins;}function resolveInternalPluginsRoot(packageRoot){const distPluginsPath=path.join(packageRoot,"dist","native","internal-plugins");const srcPluginsPath=path.join(packageRoot,"src","native","internal-plugins");return fs.existsSync(distPluginsPath)?distPluginsPath:srcPluginsPath;}function resolvePluginConfig(WEBVIEW_CONFIG){const pluginConfig={};if(WEBVIEW_CONFIG.plugins!=null){if(typeof WEBVIEW_CONFIG.plugins!=="object"||Array.isArray(WEBVIEW_CONFIG.plugins)){throw new Error("'WEBVIEW_CONFIG.plugins' must be an object with boolean values");}for(const[key,value]of Object.entries(WEBVIEW_CONFIG.plugins)){if(typeof value!=="boolean"){throw new Error(`'WEBVIEW_CONFIG.plugins.${key}' must be boolean`);}pluginConfig[key]=value;}}return pluginConfig;}module.exports={discoverInternalPlugins,parsePluginManifest,resolvePluginConfig,resolveInternalPluginsRoot};
@@ -1,5 +1,41 @@
1
1
  {
2
2
  "pins" : [
3
+ {
4
+ "identity" : "appauth-ios",
5
+ "kind" : "remoteSourceControl",
6
+ "location" : "https://github.com/openid/AppAuth-iOS.git",
7
+ "state" : {
8
+ "revision" : "2781038865a80e2c425a1da12cc1327bcd56501f",
9
+ "version" : "1.7.6"
10
+ }
11
+ },
12
+ {
13
+ "identity" : "googlesignin-ios",
14
+ "kind" : "remoteSourceControl",
15
+ "location" : "https://github.com/google/GoogleSignIn-iOS",
16
+ "state" : {
17
+ "revision" : "a7965d134c5d3567026c523e0a8a583f73b62b0d",
18
+ "version" : "7.1.0"
19
+ }
20
+ },
21
+ {
22
+ "identity" : "gtm-session-fetcher",
23
+ "kind" : "remoteSourceControl",
24
+ "location" : "https://github.com/google/gtm-session-fetcher.git",
25
+ "state" : {
26
+ "revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b",
27
+ "version" : "3.5.0"
28
+ }
29
+ },
30
+ {
31
+ "identity" : "gtmappauth",
32
+ "kind" : "remoteSourceControl",
33
+ "location" : "https://github.com/google/GTMAppAuth.git",
34
+ "state" : {
35
+ "revision" : "5d7d66f647400952b1758b230e019b07c0b4b22a",
36
+ "version" : "4.1.1"
37
+ }
38
+ },
3
39
  {
4
40
  "identity" : "jsonschema.swift",
5
41
  "kind" : "remoteSourceControl",
@@ -0,0 +1,116 @@
1
+ import Foundation
2
+ import AVFoundation
3
+ import os
4
+
5
+ private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.app", category: "BarcodeDetector")
6
+
7
+ /// Wraps AVCaptureMetadataOutput for barcode/QR detection.
8
+ /// Hardware-accelerated — lower CPU than Vision framework.
9
+ /// Fires onDetected with the raw string value and the bounding box in
10
+ /// AVCaptureVideoPreviewLayer coordinates (normalized metadata object rect).
11
+ class BarcodeDetector: NSObject {
12
+
13
+ /// Set by NativeCameraManager after all components are wired up.
14
+ var detectionHandler: ((String, AVMetadataObject.ObjectType, CGRect) -> Void)?
15
+
16
+ /// When true, frames still flow through the pipeline but results are not forwarded.
17
+ var suppressResults = false
18
+
19
+ private var metadataOutput: AVCaptureMetadataOutput?
20
+ private weak var previewLayer: AVCaptureVideoPreviewLayer?
21
+ private var activeFormats: [AVMetadataObject.ObjectType] = []
22
+
23
+ // MARK: - Setup
24
+
25
+ /// Attach metadata output to a running session. Call after session is configured but before startRunning.
26
+ func attach(to session: AVCaptureSession,
27
+ previewLayer: AVCaptureVideoPreviewLayer,
28
+ scanFormat: String) {
29
+ self.previewLayer = previewLayer
30
+ self.activeFormats = resolveFormats(scanFormat)
31
+
32
+ let output = AVCaptureMetadataOutput()
33
+ guard session.canAddOutput(output) else {
34
+ logger.error("Cannot add AVCaptureMetadataOutput to session")
35
+ return
36
+ }
37
+ session.addOutput(output)
38
+ output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
39
+
40
+ // Set types AFTER adding to session
41
+ let supported = output.availableMetadataObjectTypes
42
+ let filtered = activeFormats.filter { supported.contains($0) }
43
+ let typesToSet = filtered.isEmpty
44
+ ? (supported.contains(.qr) ? [AVMetadataObject.ObjectType.qr] : Array(supported.prefix(1)))
45
+ : filtered
46
+ output.metadataObjectTypes = typesToSet
47
+
48
+ self.metadataOutput = output
49
+ logger.debug("BarcodeDetector attached — formats: \(filtered.map { $0.rawValue })")
50
+ }
51
+
52
+ func detach(from session: AVCaptureSession) {
53
+ if let output = metadataOutput {
54
+ session.removeOutput(output)
55
+ }
56
+ metadataOutput = nil
57
+ previewLayer = nil
58
+ logger.debug("BarcodeDetector detached")
59
+ }
60
+
61
+ // MARK: - Format resolution
62
+
63
+ private func resolveFormats(_ scan: String) -> [AVMetadataObject.ObjectType] {
64
+ switch scan {
65
+ case "qr":
66
+ return [.qr]
67
+ case "barcode":
68
+ return [.ean13, .ean8, .code128, .code39, .upce, .pdf417, .interleaved2of5]
69
+ default: // "all"
70
+ return [.qr, .ean13, .ean8, .code128, .code39, .upce, .pdf417, .interleaved2of5, .dataMatrix, .aztec]
71
+ }
72
+ }
73
+
74
+ // MARK: - Format name helper (mirrors Android BarcodeDetector.formatName)
75
+
76
+ static func formatName(_ type: AVMetadataObject.ObjectType) -> String {
77
+ switch type {
78
+ case .qr: return "QR"
79
+ case .ean13: return "EAN_13"
80
+ case .ean8: return "EAN_8"
81
+ case .code128: return "CODE_128"
82
+ case .code39: return "CODE_39"
83
+ case .upce: return "UPC_E"
84
+ case .pdf417: return "PDF417"
85
+ case .dataMatrix: return "DATA_MATRIX"
86
+ case .aztec: return "AZTEC"
87
+ case .interleaved2of5: return "ITF"
88
+ default: return "UNKNOWN"
89
+ }
90
+ }
91
+ }
92
+
93
+ // MARK: - AVCaptureMetadataOutputObjectsDelegate
94
+
95
+ extension BarcodeDetector: AVCaptureMetadataOutputObjectsDelegate {
96
+ func metadataOutput(_ output: AVCaptureMetadataOutput,
97
+ didOutput metadataObjects: [AVMetadataObject],
98
+ from connection: AVCaptureConnection) {
99
+ guard !suppressResults else { return }
100
+
101
+ for obj in metadataObjects {
102
+ guard let machineReadable = obj as? AVMetadataMachineReadableCodeObject,
103
+ let value = machineReadable.stringValue else { continue }
104
+
105
+ // Transform bounding box from metadata coordinates into preview layer bounds
106
+ let bounds: CGRect
107
+ if let transformed = previewLayer?.transformedMetadataObject(for: machineReadable) {
108
+ bounds = transformed.bounds
109
+ } else {
110
+ bounds = .zero
111
+ }
112
+
113
+ detectionHandler?(value, machineReadable.type, bounds)
114
+ }
115
+ }
116
+ }
@@ -0,0 +1,58 @@
1
+ import SwiftUI
2
+ import UIKit
3
+ import AVFoundation
4
+
5
+ /// UIViewRepresentable that hosts the camera preview layer.
6
+ /// Placed at ZStack index 0 (behind WebViewContainer).
7
+ /// The preview layer is managed by CameraSessionManager — this view just
8
+ /// provides the UIView container that CameraSessionManager embeds into.
9
+ public struct CameraPreviewView: UIViewRepresentable {
10
+
11
+ public let cameraManager: NativeCameraManager
12
+
13
+ public init(cameraManager: NativeCameraManager) {
14
+ self.cameraManager = cameraManager
15
+ }
16
+
17
+ public func makeUIView(context: Context) -> UIView {
18
+ let view = UIView()
19
+ view.backgroundColor = .clear
20
+ cameraManager.setPreviewView(view)
21
+
22
+ // Pinch gesture for zoom
23
+ let pinch = UIPinchGestureRecognizer(target: context.coordinator,
24
+ action: #selector(Coordinator.handlePinch(_:)))
25
+ view.addGestureRecognizer(pinch)
26
+ return view
27
+ }
28
+
29
+ public func updateUIView(_ uiView: UIView, context: Context) {
30
+ // updateUIView is always called on the main thread — update frame directly.
31
+ uiView.layer.sublayers?
32
+ .compactMap { $0 as? AVCaptureVideoPreviewLayer }
33
+ .forEach { $0.frame = uiView.bounds }
34
+ }
35
+
36
+ public func makeCoordinator() -> Coordinator {
37
+ Coordinator(cameraManager: cameraManager)
38
+ }
39
+
40
+ public class Coordinator: NSObject {
41
+ private let cameraManager: NativeCameraManager
42
+
43
+ public init(cameraManager: NativeCameraManager) {
44
+ self.cameraManager = cameraManager
45
+ }
46
+
47
+ @objc func handlePinch(_ recognizer: UIPinchGestureRecognizer) {
48
+ switch recognizer.state {
49
+ case .began:
50
+ cameraManager.handlePinchBegan()
51
+ case .changed:
52
+ cameraManager.handlePinchChanged(scale: recognizer.scale)
53
+ default:
54
+ break
55
+ }
56
+ }
57
+ }
58
+ }