catalyst-core-internal 0.1.4 → 0.1.6

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 (43) hide show
  1. package/bin/catalyst.js +1 -8
  2. package/changelog.md +21 -0
  3. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/BridgeMessageValidator.kt +1 -1
  4. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/CustomWebview.kt +1 -12
  5. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/MainActivity.kt +3 -18
  6. package/dist/native/buildAppAndroid.js +2 -2
  7. package/dist/native/buildAppIos.js +17 -10
  8. package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +2 -13
  9. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +0 -6
  10. package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +0 -4
  11. package/mcp_v2/conversion-tasks.json +326 -0
  12. package/mcp_v2/knowledge-base.json +1068 -0
  13. package/mcp_v2/lib/helpers.js +170 -0
  14. package/mcp_v2/mcp.js +563 -0
  15. package/mcp_v2/package.json +13 -0
  16. package/mcp_v2/schema.sql +88 -0
  17. package/mcp_v2/setup.js +282 -0
  18. package/mcp_v2/tools/build.js +686 -0
  19. package/mcp_v2/tools/config.js +453 -0
  20. package/mcp_v2/tools/conversion.js +799 -0
  21. package/mcp_v2/tools/debug.js +113 -0
  22. package/mcp_v2/tools/knowledge.js +219 -0
  23. package/mcp_v2/tools/sync.js +23 -0
  24. package/mcp_v2/tools/tasks.js +945 -0
  25. package/package.json +7 -15
  26. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/CatalystPlugin.kt +0 -7
  27. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/GeneratedPluginIndex.kt +0 -6
  28. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/plugins/PluginBridge.kt +0 -253
  29. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/plugins/PluginBridgeTest.kt +0 -139
  30. package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +0 -43
  31. package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +0 -28
  32. package/dist/native/internal-plugins/device-info-plugin/manifest.json +0 -19
  33. package/dist/native/internalPluginUtils.js +0 -1
  34. package/dist/native/iosnativeWebView/Sources/Core/Plugins/CatalystPlugin.swift +0 -5
  35. package/dist/native/iosnativeWebView/Sources/Core/Plugins/GeneratedPluginIndex.swift +0 -6
  36. package/dist/native/iosnativeWebView/Sources/Core/Plugins/PluginBridge.swift +0 -364
  37. package/dist/native/iosnativeWebView/Sources/Core/WebView/WeakScriptMessageHandler.swift +0 -14
  38. package/dist/native/iosnativeWebView/iosnativeWebViewTests/PluginBridgeTests.swift +0 -160
  39. package/dist/native/plugin-bridge/PluginBridge.js +0 -1
  40. package/dist/native/pluginComposerAndroid.js +0 -9
  41. package/dist/native/pluginComposerIos.js +0 -7
  42. package/dist/scripts/plugins.js +0 -1
  43. package/license +0 -10
package/bin/catalyst.js CHANGED
@@ -13,8 +13,6 @@ const validCommands = [
13
13
  "serve",
14
14
  "devBuild",
15
15
  "devServe",
16
- "plugin",
17
- "plugins",
18
16
  "buildApp",
19
17
  "buildApp:ios",
20
18
  "buildApp:android",
@@ -69,14 +67,11 @@ const scriptIndex = args.findIndex(
69
67
  x === "serve" ||
70
68
  x === "devBuild" ||
71
69
  x === "devServe" ||
72
- x === "plugin" ||
73
- x === "plugins" ||
74
70
  isPlatformCommand(x, "buildApp") ||
75
71
  isPlatformCommand(x, "setupEmulator")
76
72
  )
77
73
  const script = scriptIndex === -1 ? args[0] : args[scriptIndex]
78
74
  const nodeArgs = scriptIndex > 0 ? args.slice(0, scriptIndex) : []
79
- const resolvedScript = script === "plugin" ? "plugins" : script
80
75
 
81
76
  if (validCommands.includes(script)) {
82
77
  // Handle platform-specific or combined commands
@@ -95,9 +90,7 @@ if (validCommands.includes(script)) {
95
90
  // Original commands
96
91
  const result = spawnSync(
97
92
  process.execPath,
98
- nodeArgs
99
- .concat(require.resolve("../dist/scripts/" + resolvedScript))
100
- .concat(args.slice(scriptIndex + 1)),
93
+ nodeArgs.concat(require.resolve("../dist/scripts/" + script)).concat(args.slice(scriptIndex + 1)),
101
94
  { stdio: "inherit" }
102
95
  )
103
96
  handleProcessResult(result)
package/changelog.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.0-beta.1] - 2026-04-15
4
+
5
+ - Promoted `0.1.0-canary.7` to the first proper beta release after stabilization.
6
+ - No code changes from `0.1.0-canary.7`; this release marks the same build as production-ready beta.
7
+
8
+ ## [0.1.0-canary.7] - 2026-03-31
9
+
10
+ - Introduced Catalyst MCP v2 with a new setup flow, database schema, knowledge base, and source-aware migration tooling for stronger project guidance and conversion workflows.
11
+ - Expanded MCP/framework knowledge coverage across SEO, observability, webpack, React Compiler, CLI, file conventions, and native hooks, while improving setup and verification messaging across supported MCP clients.
12
+
13
+ ## [0.1.0-canary.6] - 2026-03-09
14
+
15
+ - Hardened URL whitelisting with thread-safety improvements, broader test coverage, and related iOS build fixes to make access-control behavior more reliable.
16
+ - Improved compatibility and runtime resilience by softening bridge environment mismatch failures and preserving backward compatibility for `useDataProtection` on older native binaries.
17
+
18
+ ## [0.1.0-canary.5] - 2026-02-27
19
+
20
+ - Strengthened native app security with backup restrictions, screen-capture protection, web data clearing, and related Android/iOS test coverage.
21
+ - Improved universal app runtime behavior with safe-area inset support, edge-to-edge rendering, and notification permission override fixes.
22
+ - Expanded platform support with offline fallback handling, notification/access-control refinements, localhost HTTP allowances for local development, and file-picker/HTTPS server improvements.
23
+
3
24
  ## [0.1.0-canary.4] - 2026-02-12
4
25
 
5
26
  - Added Google Sign-In support for both Android and iOS in Catalyst, enabling a unified native authentication experience for apps built on the framework.
@@ -60,7 +60,7 @@ data class SchemaDefinition(
60
60
  object BridgeMessageValidator {
61
61
 
62
62
  private const val TAG = "BridgeMessageValidator"
63
- private const val MAX_MESSAGE_SIZE = CatalystConstants.Bridge.MAX_MESSAGE_SIZE
63
+ private const val MAX_MESSAGE_SIZE = 10 * 1024 * 1024 // 10MB (match iOS)
64
64
 
65
65
  // Valid commands — single source of truth from CatalystConstants
66
66
  private val validCommands = CatalystConstants.Bridge.VALID_COMMANDS
@@ -233,7 +233,7 @@ class CustomWebView(
233
233
  }
234
234
 
235
235
  // Additional security check: only allow whitelisted interface names
236
- val allowedInterfaces = setOf("NativeBridge", "AndroidBridge", "PluginBridge")
236
+ val allowedInterfaces = setOf("NativeBridge", "AndroidBridge")
237
237
  if (name !in allowedInterfaces) {
238
238
  Log.e(TAG, "❌ Security: Interface name '$name' is not in whitelist. Refusing to add interface.")
239
239
  return
@@ -246,17 +246,6 @@ class CustomWebView(
246
246
  }
247
247
  }
248
248
 
249
- fun removeJavascriptInterface(name: String) {
250
- try {
251
- webView.removeJavascriptInterface(name)
252
- if (BuildConfig.DEBUG) {
253
- Log.d(TAG, "🔌 Removed JavaScript interface: $name")
254
- }
255
- } catch (e: Exception) {
256
- Log.w(TAG, "⚠️ Failed to remove JavaScript interface '$name': ${e.message}")
257
- }
258
- }
259
-
260
249
  fun destroy() {
261
250
  job.cancel()
262
251
  webView.destroy()
@@ -14,7 +14,6 @@ import org.json.JSONObject
14
14
  import java.util.Properties
15
15
  import io.yourname.androidproject.databinding.ActivityMainBinding
16
16
  import io.yourname.androidproject.NativeBridge
17
- import io.yourname.androidproject.plugins.PluginBridge
18
17
  import io.yourname.androidproject.utils.BridgeUtils
19
18
  import io.yourname.androidproject.utils.KeyboardUtil
20
19
  import io.yourname.androidproject.utils.NetworkUtils
@@ -40,7 +39,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
40
39
 
41
40
  private lateinit var binding: ActivityMainBinding
42
41
  private lateinit var nativeBridge: NativeBridge
43
- private lateinit var pluginBridge: PluginBridge
44
42
  private lateinit var customWebView: CustomWebView
45
43
  lateinit var properties: Properties
46
44
  private lateinit var metricsMonitor: MetricsMonitor
@@ -330,14 +328,6 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
330
328
  Log.e(TAG, "Failed to initialize NativeBridge: ${e.message}")
331
329
  }
332
330
 
333
- // Setup isolated PluginBridge
334
- try {
335
- pluginBridge = PluginBridge(this, customWebView.getWebView(), properties)
336
- customWebView.addJavascriptInterface(pluginBridge, "PluginBridge")
337
- } catch (e: Exception) {
338
- Log.e(TAG, "Failed to initialize PluginBridge: ${e.message}")
339
- }
340
-
341
331
  setupSafeAreaHandling()
342
332
 
343
333
  // Setup back press handler (modern API)
@@ -455,16 +445,11 @@ class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() {
455
445
  if (::keyboardUtil.isInitialized) {
456
446
  keyboardUtil.cleanup()
457
447
  }
458
- if (::customWebView.isInitialized) {
459
- if (::pluginBridge.isInitialized) {
460
- customWebView.removeJavascriptInterface("PluginBridge")
461
- }
462
- if (::nativeBridge.isInitialized) {
463
- customWebView.removeJavascriptInterface("NativeBridge")
464
- }
465
- customWebView.destroy()
448
+ if (::nativeBridge.isInitialized) {
449
+ nativeBridge.cleanup()
466
450
  }
467
451
  coroutineContext.cancelChildren()
452
+ customWebView.destroy()
468
453
  super.onDestroy()
469
454
  }
470
455
 
@@ -1,4 +1,4 @@
1
- /* eslint-disable no-extra-semi */"use strict";var _renameAndroidProject=require("./renameAndroidProject.js");var _child_process=require("child_process");var _fs=_interopRequireDefault(require("fs"));var _path=_interopRequireDefault(require("path"));var _utils=require("./utils.js");var _TerminalProgress=_interopRequireDefault(require("./TerminalProgress.js"));var _pluginComposerAndroid=require("./pluginComposerAndroid.js");var _internalPluginUtils=require("./internalPluginUtils.js");// Import the AAB builder
1
+ /* eslint-disable no-extra-semi */"use strict";var _renameAndroidProject=require("./renameAndroidProject.js");var _child_process=require("child_process");var _fs=_interopRequireDefault(require("fs"));var _path=_interopRequireDefault(require("path"));var _utils=require("./utils.js");var _TerminalProgress=_interopRequireDefault(require("./TerminalProgress.js"));// Import the AAB builder
2
2
  function _interopRequireDefault(e){return e&&e.__esModule?e:{default:e};}const configPath=`${process.env.PWD}/config/config.json`;const publicPath=`${process.env.PWD}/public`;const catalystCorePath=_path.default.dirname(require.resolve("catalyst-core-internal/package.json"));const pwd=_path.default.join(catalystCorePath,"dist/native");const ANDROID_PACKAGE="io.yourname.androidproject";// Default values for AAB building
3
3
  const DEFAULT_PROJECT_PATH=`${pwd}/androidProject`;const DEFAULT_DEPLOYMENT_PATH="./deployment";const DEFAULT_OLD_PROJECT_NAME="androidProject";// Use actual project name in catalyst
4
4
  const DEFAULT_OVERWRITE_EXISTING=true;const steps={config:"Initialize Configuration",tools:"Validate Android Tools",emulator:"Check and Start Emulator",copyAssets:"Copy Build Assets",build:"Build and Install Application",aab:"Build Signed AAB"};const progressConfig={titlePaddingTop:2,titlePaddingBottom:1,stepPaddingLeft:4,stepSpacing:1,errorPaddingLeft:6,bottomMargin:2};const progress=new _TerminalProgress.default(steps,"Catalyst Android Build",progressConfig);async function initializeConfig(){const configFile=_fs.default.readFileSync(configPath,"utf8");const config=JSON.parse(configFile);const{WEBVIEW_CONFIG,BUILD_OUTPUT_PATH}=config;if(!WEBVIEW_CONFIG||Object.keys(WEBVIEW_CONFIG).length===0){throw new Error("WebView Config missing in "+configPath);}if(!WEBVIEW_CONFIG.android){throw new Error("Android config missing in WebView Config");}// Log build type information
@@ -131,7 +131,7 @@ const canInstallOnPhysical=await testPhysicalDeviceInstallation(ADB_PATH,physica
131
131
  targetDevice={type:"physical",id:physicalDevice.id,model:physicalDevice.model};progress.log(`Using physical device: ${physicalDevice.model}`,"success");}else{// Physical device failed, fallback to emulator
132
132
  progress.log("Physical device installation test failed, falling back to emulator","warning");targetDevice=await handleEmulatorSetup(ADB_PATH,EMULATOR_PATH,androidConfig);}}else{// No physical device, use emulator (current behavior)
133
133
  targetDevice=await handleEmulatorSetup(ADB_PATH,EMULATOR_PATH,androidConfig);}progress.complete("emulator");}else{progress.log("Skipping device setup for release build","info");}// Copy build assets
134
- progress.start("copyAssets");await copyBuildAssets(androidConfig,buildOptimisation);await copySplashscreenAssets();await copyOfflinePage();await copyIconAssets();await configureAppName(androidConfig);const pluginConfig=(0,_internalPluginUtils.resolvePluginConfig)(WEBVIEW_CONFIG);(0,_pluginComposerAndroid.composeAndroidPlugins)({corePluginsRoot:(0,_internalPluginUtils.resolveInternalPluginsRoot)(catalystCorePath),androidProjectPath:`${pwd}/androidProject`,pluginConfig,log:(message,status="info")=>progress.log(message,status)});await processNotifications(WEBVIEW_CONFIG);progress.log(`Build optimization: ${buildOptimisation?"Enabled":"Disabled"}`,"info");progress.complete("copyAssets");// Build based on type
134
+ progress.start("copyAssets");await copyBuildAssets(androidConfig,buildOptimisation);await copySplashscreenAssets();await copyOfflinePage();await copyIconAssets();await configureAppName(androidConfig);await processNotifications(WEBVIEW_CONFIG);progress.log(`Build optimization: ${buildOptimisation?"Enabled":"Disabled"}`,"info");progress.complete("copyAssets");// Build based on type
135
135
  let movedApkPath=null;if(buildType==="release"){// Build signed AAB for release
136
136
  progress.start("aab");await buildSignedAAB(androidConfig);progress.complete("aab");// Move APK to output directory
137
137
  movedApkPath=await moveApkToOutputPath(buildType,BUILD_OUTPUT_PATH,androidConfig.appName);}else{// Install debug app for development
@@ -1,12 +1,12 @@
1
- "use strict";const{exec,execSync,execFileSync}=require("child_process");const fs=require("fs");const path=require("path");const TerminalProgress=require("./TerminalProgress.js").default;const crypto=require("crypto");const{composeIosPlugins}=require("./pluginComposerIos.js");const{resolveInternalPluginsRoot,resolvePluginConfig}=require("./internalPluginUtils.js");const catalystCorePath=path.dirname(require.resolve("catalyst-core-internal/package.json"));const pwd=path.join(catalystCorePath,"dist/native");const{WEBVIEW_CONFIG,BUILD_OUTPUT_PATH}=require(`${process.env.PWD}/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
1
+ "use strict";const{exec,execSync}=require("child_process");const fs=require("fs");const path=require("path");const TerminalProgress=require("./TerminalProgress.js").default;const crypto=require("crypto");const catalystCorePath=path.dirname(require.resolve("catalyst-core-internal/package.json"));const pwd=path.join(catalystCorePath,"dist/native");const{WEBVIEW_CONFIG,BUILD_OUTPUT_PATH}=require(`${process.env.PWD}/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
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);// Utility function to run shell commands
5
5
  function runCommand(command,options={}){return new Promise((resolve,reject)=>{// eslint-disable-next-line security/detect-child-process
6
6
  exec(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){try{// First try to find a booted simulator of the specified model
7
7
  let command=`xcrun simctl list devices | grep "${modelName}" | grep "Booted" | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})" | head -n 1`;let uuid=execSync(command).toString().trim();if(uuid){console.log(`Found booted simulator of model ${modelName}`);return uuid;}// If no booted simulator of the specified model is found, check any booted simulator
8
- console.log(`No booted simulator of model ${modelName} found, checking for any booted simulator...`);command=`xcrun simctl list devices | grep "Booted" | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})" | head -n 1`;uuid=execSync(command).toString().trim();if(uuid){console.log("Found another booted simulator, will use it instead");return uuid;}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
9
- 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
8
+ console.log(`No booted simulator of model ${modelName} found, checking for any booted simulator...`);command=`xcrun simctl list devices | grep "Booted" | grep -E -o -i "([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})" | head -n 1`;uuid=execSync(command).toString().trim();if(uuid){console.log("Found another booted simulator, will use it instead");return uuid;}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
9
+ 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
10
10
  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
11
11
  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
12
12
  let packageContent=`// swift-tools-version: 5.9
@@ -23,7 +23,10 @@ if(isNotificationsEnabled){packageContent+=`,
23
23
  .library(name: "CatalystNotifications", targets: ["CatalystNotifications"])`;}packageContent+=`
24
24
  ],
25
25
  dependencies: [
26
- ${formatSwiftPackageEntries(packageDependencies).join(",\n")}`;packageContent+=`
26
+ .package(url: "https://github.com/kylef/JSONSchema.swift", from: "0.6.0"),
27
+ .package(url: "https://github.com/google/GoogleSignIn-iOS", from: "7.0.0")`;// Add Firebase dependency only if notifications enabled
28
+ if(isNotificationsEnabled){packageContent+=`,
29
+ .package(url: "https://github.com/firebase/firebase-ios-sdk", from: "12.3.0")`;}packageContent+=`
27
30
  ],
28
31
  targets: [
29
32
  // Core functionality (WebView, bridge, utils, constants)
@@ -31,7 +34,8 @@ ${formatSwiftPackageEntries(packageDependencies).join(",\n")}`;packageContent+=`
31
34
  .target(
32
35
  name: "CatalystCore",
33
36
  dependencies: [
34
- ${formatSwiftProductEntries(coreDependencies).join(",\n")}
37
+ .product(name: "JSONSchema", package: "JSONSchema.swift"),
38
+ .product(name: "GoogleSignIn", package: "GoogleSignIn-iOS")
35
39
  ],
36
40
  path: "Sources/Core"
37
41
  )`;// Add CatalystNotifications target only if enabled
@@ -41,7 +45,8 @@ if(isNotificationsEnabled){packageContent+=`,
41
45
  name: "CatalystNotifications",
42
46
  dependencies: [
43
47
  "CatalystCore",
44
- ${formatSwiftProductEntries(notificationsDependencies).join(",\n")}
48
+ .product(name: "FirebaseCore", package: "firebase-ios-sdk"),
49
+ .product(name: "FirebaseMessaging", package: "firebase-ios-sdk")
45
50
  ],
46
51
  path: "Sources/CatalystNotifications"
47
52
  )`;}packageContent+=`
@@ -60,7 +65,9 @@ projectContent=projectContent.replace(/(C99974342E97D56900C25611 \/\* CatalystCo
60
65
  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
61
66
  projectContent=projectContent.replace(/(packageProductDependencies = \(\s*C99974332E97D56900C25611 \/\* CatalystCore \*\/,)/,`$1\n\t\t\t\t${NOTIF_PRODUCT_ID} /* CatalystNotifications */,`);// 4. Add to XCSwiftPackageProductDependency section
62
67
  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
63
- 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();if(!fs.existsSync(projectFilePath)){return;}let projectContent=fs.readFileSync(projectFilePath,"utf8");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();if(!fs.existsSync(projectFilePath)){throw new Error(`Xcode project file not found at: ${projectFilePath}`);}let projectContent=fs.readFileSync(projectFilePath,"utf8");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
68
+ 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
69
+ 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
70
+ 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
64
71
  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
65
72
  if(key==="cachePattern"){if(typeof value==="string"){// Convert single string to array
66
73
  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
@@ -275,7 +282,7 @@ progress.log("Opening Simulator.app...");await runCommand("open -a Simulator");/
275
282
  await new Promise(resolve=>setTimeout(resolve,1000));// Activate the Simulator.app window to bring it to front
276
283
  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
277
284
  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
278
- async function buildForIOS(pluginComposition={}){const originalDir=process.cwd();try{await generatePackageSwift(pluginComposition.iosDependencies);await updateXcodeProjectPackageDependencies();// Process notification assets
285
+ async function buildForIOS(){const originalDir=process.cwd();try{await generatePackageSwift();await updateXcodeProjectPackageDependencies();// Process notification assets
279
286
  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)
280
287
  const physicalDevice=await detectPhysicalDevices();let APP_PATH;let targetInfo;if(physicalDevice){// Physical device workflow
281
288
  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)
@@ -302,4 +309,4 @@ fs.copyFileSync(foundImage.path,destinationPath);foundIcons.push({size,scale,fil
302
309
  const iconKey=`${size}-${idiom}-${scale}`;addedIcons.add(iconKey);// Remove existing entry with same size/idiom/scale
303
310
  contents.images=contents.images.filter(img=>`${img.size}-${img.idiom}-${img.scale}`!==iconKey);// Add new entry
304
311
  contents.images.push({size,idiom,scale,filename});}}if(foundIcons.length>0){// Write updated Contents.json
305
- 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();
312
+ 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();
@@ -17,8 +17,6 @@ class NativeBridge: NSObject, BridgeCommandHandlerDelegate, BridgeFileHandlerDel
17
17
  weak var webView: WKWebView?
18
18
  private weak var viewController: UIViewController?
19
19
  private weak var webViewModel: WebViewModel?
20
- private var messageHandlerProxy: WeakScriptMessageHandler?
21
- private var isRegistered = false
22
20
 
23
21
  // Protocol-based notification handler (injected at runtime)
24
22
  private var notificationHandler: NotificationHandlerProtocol = NullNotificationHandler.shared
@@ -135,16 +133,10 @@ class NativeBridge: NSObject, BridgeCommandHandlerDelegate, BridgeFileHandlerDel
135
133
 
136
134
  // Register the JavaScript interface with the WebView
137
135
  func register() {
138
- guard !isRegistered else { return }
139
136
  let registerStart = CFAbsoluteTimeGetCurrent()
140
137
 
141
- guard let userContentController = webView?.configuration.userContentController else {
142
- return
143
- }
144
- let proxy = WeakScriptMessageHandler(delegate: self)
145
- userContentController.add(proxy, name: "NativeBridge")
146
- messageHandlerProxy = proxy
147
- isRegistered = true
138
+ let userContentController = webView?.configuration.userContentController
139
+ userContentController?.add(self, name: "NativeBridge")
148
140
 
149
141
  let registerTime = (CFAbsoluteTimeGetCurrent() - registerStart) * 1000
150
142
  logWithTimestamp("✅ NativeBridge registered (took \(String(format: "%.2f", registerTime))ms)")
@@ -152,10 +144,7 @@ class NativeBridge: NSObject, BridgeCommandHandlerDelegate, BridgeFileHandlerDel
152
144
 
153
145
  // Unregister to prevent memory leaks
154
146
  func unregister() {
155
- guard isRegistered else { return }
156
147
  webView?.configuration.userContentController.removeScriptMessageHandler(forName: "NativeBridge")
157
- messageHandlerProxy = nil
158
- isRegistered = false
159
148
 
160
149
  if let listenerId = networkStatusListenerId {
161
150
  NetworkMonitor.shared.removeListener(listenerId)
@@ -121,8 +121,6 @@ public struct WebView: UIViewRepresentable, Equatable {
121
121
  // Clean up native bridge
122
122
  coordinator.nativeBridge?.unregister()
123
123
  coordinator.nativeBridge = nil
124
- coordinator.pluginBridge?.unregister()
125
- coordinator.pluginBridge = nil
126
124
  coordinator.hostingController = nil
127
125
 
128
126
  // Unregister custom URL protocol
@@ -134,7 +132,6 @@ public struct WebView: UIViewRepresentable, Equatable {
134
132
  public class Coordinator: NSObject {
135
133
  var parent: WebView
136
134
  var nativeBridge: NativeBridge?
137
- var pluginBridge: PluginBridge?
138
135
  var hostingController: UIViewController?
139
136
  var isObserverAdded = false
140
137
 
@@ -149,7 +146,6 @@ public struct WebView: UIViewRepresentable, Equatable {
149
146
 
150
147
  // Create and register the native bridge
151
148
  let bridge = NativeBridge(webView: webView, viewController: hostingController)
152
- let pluginBridge = PluginBridge(webView: webView, viewController: hostingController)
153
149
 
154
150
  // Inject WebViewModel for safe area handling
155
151
  Task { @MainActor in
@@ -160,9 +156,7 @@ public struct WebView: UIViewRepresentable, Equatable {
160
156
  bridge.setNotificationHandler(NotificationHandlerProvider.shared)
161
157
 
162
158
  bridge.register()
163
- pluginBridge.register()
164
159
  self.nativeBridge = bridge
165
- self.pluginBridge = pluginBridge
166
160
  }
167
161
 
168
162
  override public func observeValue(forKeyPath keyPath: String?,
@@ -23,7 +23,6 @@
23
23
  E9F699E02EE065B0005E972E /* NotificationHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F699DF2EE065B0005E972E /* NotificationHandlerTests.swift */; };
24
24
  E9F699E22EE06650005E972E /* FrameworkServerUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F699E12EE06650005E972E /* FrameworkServerUtilsTests.swift */; };
25
25
  E9F699E42EE0696A005E972E /* BootTimingUtilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9F699E32EE0696A005E972E /* BootTimingUtilityTests.swift */; };
26
- F2B100012F11111100AAA001 /* PluginBridgeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2B100022F11111100AAA001 /* PluginBridgeTests.swift */; };
27
26
  F1234AC22E990180008C7F58 /* localhost.p12 in Resources */ = {isa = PBXBuildFile; fileRef = F1234AC32E990180008C7F58 /* localhost.p12 */; };
28
27
  /* End PBXBuildFile section */
29
28
 
@@ -67,7 +66,6 @@
67
66
  E9F699DF2EE065B0005E972E /* NotificationHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHandlerTests.swift; sourceTree = "<group>"; };
68
67
  E9F699E12EE06650005E972E /* FrameworkServerUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FrameworkServerUtilsTests.swift; sourceTree = "<group>"; };
69
68
  E9F699E32EE0696A005E972E /* BootTimingUtilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BootTimingUtilityTests.swift; sourceTree = "<group>"; };
70
- F2B100022F11111100AAA001 /* PluginBridgeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginBridgeTests.swift; sourceTree = "<group>"; };
71
69
  F1234AC32E990180008C7F58 /* localhost.p12 */ = {isa = PBXFileReference; lastKnownFileType = file; path = localhost.p12; sourceTree = "<group>"; };
72
70
  XCCONFIG001000000001 /* Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
73
71
  XCCONFIG001000000002 /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
@@ -154,7 +152,6 @@
154
152
  E9F699DB2EE06482005E972E /* CacheManagerTests.swift */,
155
153
  E9F699D92EE063D5005E972E /* WebViewTests.swift */,
156
154
  E9F699D72EE06068005E972E /* NativeBridgeTests.swift */,
157
- F2B100022F11111100AAA001 /* PluginBridgeTests.swift */,
158
155
  E9F699D52EE05D74005E972E /* BridgeMessageValidatorTests.swift */,
159
156
  E9F699D32EE05984005E972E /* ConfigMappingTests.swift */,
160
157
  E9F699D02EDEFEF2005E972E /* URLWhitelistManagerTests.swift */,
@@ -324,7 +321,6 @@
324
321
  E9F699DC2EE06483005E972E /* CacheManagerTests.swift in Sources */,
325
322
  E9F699D42EE05984005E972E /* ConfigMappingTests.swift in Sources */,
326
323
  E9F699D82EE06068005E972E /* NativeBridgeTests.swift in Sources */,
327
- F2B100012F11111100AAA001 /* PluginBridgeTests.swift in Sources */,
328
324
  E9F699DA2EE063D5005E972E /* WebViewTests.swift in Sources */,
329
325
  E9F699E42EE0696A005E972E /* BootTimingUtilityTests.swift in Sources */,
330
326
  E9F699E22EE06650005E972E /* FrameworkServerUtilsTests.swift in Sources */,