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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/babel.native.config.cjs +8 -0
  2. package/bin/catalyst.js +49 -105
  3. package/changelog.md +8 -0
  4. package/dist/caching.js +1 -1
  5. package/dist/globals.d.ts +7 -0
  6. package/dist/index.jsx +8 -0
  7. package/dist/logger.js +74 -3
  8. package/dist/native/androidProject/app/build.gradle.kts +45 -4
  9. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +86 -0
  10. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/BridgeUtils.kt +8 -0
  11. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +173 -13
  12. package/dist/native/androidProject/settings.gradle.kts +10 -0
  13. package/dist/native/androidSetup.js +300 -8
  14. package/dist/native/bridge/WebBridge.js +481 -59
  15. package/dist/native/bridge/constants/NativeInterfaces.js +304 -45
  16. package/dist/native/bridge/errors.js +250 -26
  17. package/dist/native/bridge/hooks/index.js +132 -1
  18. package/dist/native/bridge/hooks/useAI.js +94 -0
  19. package/dist/native/bridge/hooks/useCamera.js +347 -9
  20. package/dist/native/bridge/hooks/useDataProtection.js +200 -2
  21. package/dist/native/bridge/hooks/useDeviceInfo.js +71 -2
  22. package/dist/native/bridge/hooks/useFilePicker.js +429 -2
  23. package/dist/native/bridge/hooks/useGoogleSignIn.js +81 -1
  24. package/dist/native/bridge/hooks/useHapticFeedback.js +196 -1
  25. package/dist/native/bridge/hooks/useIntent.js +95 -1
  26. package/dist/native/bridge/hooks/useNativeTransition.js +202 -27
  27. package/dist/native/bridge/hooks/useNetworkStatus.js +84 -3
  28. package/dist/native/bridge/hooks/useNotification.js +318 -2
  29. package/dist/native/bridge/hooks/useSafeArea.js +84 -2
  30. package/dist/native/bridge/hooks/useVideoStream.js +434 -6
  31. package/dist/native/bridge/hooks/utils.js +27 -1
  32. package/dist/native/bridge/hooks.js +16 -1
  33. package/dist/native/bridge/perf/collectors/ApiCollector.js +66 -3
  34. package/dist/native/bridge/perf/collectors/CacheCollector.js +186 -11
  35. package/dist/native/bridge/perf/collectors/HookCollector.js +141 -19
  36. package/dist/native/bridge/perf/collectors/InsightsCollector.js +481 -23
  37. package/dist/native/bridge/perf/collectors/InteractionCollector.js +245 -10
  38. package/dist/native/bridge/perf/collectors/KeyboardCollector.js +107 -8
  39. package/dist/native/bridge/perf/collectors/NavigationCollector.js +273 -32
  40. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +178 -2
  41. package/dist/native/bridge/perf/collectors/RenderCollector.js +258 -14
  42. package/dist/native/bridge/perf/collectors/ScrollCollector.js +95 -9
  43. package/dist/native/bridge/perf/collectors/ThreadCollector.js +90 -6
  44. package/dist/native/bridge/perf/core/constants.js +103 -20
  45. package/dist/native/bridge/perf/core/measure.js +179 -30
  46. package/dist/native/bridge/perf/core/session.js +89 -23
  47. package/dist/native/bridge/perf/core/store.js +554 -1
  48. package/dist/native/bridge/perf/core/summary.js +174 -1
  49. package/dist/native/bridge/perf/core/traceExport.js +240 -1
  50. package/dist/native/bridge/perf/core/waterfalls.js +146 -1
  51. package/dist/native/bridge/perf/index.js +359 -38
  52. package/dist/native/bridge/safeArea.js +53 -5
  53. package/dist/native/bridge/standardizedHooks.js +595 -82
  54. package/dist/native/bridge/useBaseHook.js +274 -38
  55. package/dist/native/bridge/utils/CameraUtils.js +201 -56
  56. package/dist/native/bridge/utils/FileObjectConverter.js +129 -18
  57. package/dist/native/bridge/utils/NativeBridge.js +425 -141
  58. package/dist/native/buildAndroid/assets.js +510 -13
  59. package/dist/native/buildAndroid/build.js +296 -19
  60. package/dist/native/buildAndroid/config.js +87 -1
  61. package/dist/native/buildAndroid/index.js +406 -1
  62. package/dist/native/buildAppAndroid.js +34 -2
  63. package/dist/native/buildAppIos.js +62 -2
  64. package/dist/native/buildIos/assets.js +609 -8
  65. package/dist/native/buildIos/build.js +639 -20
  66. package/dist/native/buildIos/config.js +294 -12
  67. package/dist/native/buildIos/index.js +343 -6
  68. package/dist/native/buildIos/plugins.js +343 -16
  69. package/dist/native/internal-plugins/device-info-plugin/android/DeviceInfoPlugin.kt +43 -0
  70. package/dist/native/internal-plugins/device-info-plugin/ios/DeviceInfoPlugin.swift +28 -0
  71. package/dist/native/internal-plugins/device-info-plugin/manifest.json +19 -0
  72. package/dist/native/internalPluginUtils.js +232 -7
  73. package/dist/native/package.json +3 -0
  74. package/dist/native/plugin-bridge/PluginBridge.js +227 -1
  75. package/dist/native/pluginComposerAndroid.js +407 -23
  76. package/dist/native/pluginComposerIos.js +435 -11
  77. package/dist/native/renameAndroidProject.js +1094 -88
  78. package/dist/native/setupEmulatorIos.js +289 -22
  79. package/dist/native/setupServer.js +160 -19
  80. package/dist/native/terminalProgress.js +199 -11
  81. package/dist/native/utils.js +193 -15
  82. package/dist/offline/catalyst-sw.js +145 -1
  83. package/dist/offline/registerServiceWorker.js +33 -1
  84. package/dist/otel.js +375 -46
  85. package/dist/router/ClientRouter.js +14 -2
  86. package/dist/router/ServerRouter.js +9 -1
  87. package/dist/router.js +1 -1
  88. package/dist/scripts/build.js +112 -3
  89. package/dist/scripts/checkVersion.js +3 -1
  90. package/dist/scripts/cleanCache.js +30 -1
  91. package/dist/scripts/generateOfflineManifest.js +116 -1
  92. package/dist/scripts/loadEnvironmentVariables.js +32 -6
  93. package/dist/scripts/plugins.js +309 -1
  94. package/dist/scripts/preServerInit.js +13 -0
  95. package/dist/scripts/scriptUtils.js +54 -3
  96. package/dist/scripts/serve.js +51 -4
  97. package/dist/scripts/start.js +47 -10
  98. package/dist/scripts/validator.js +149 -1
  99. package/dist/sentry.cjs +127 -0
  100. package/dist/sentry.js +125 -5
  101. package/dist/server/expressServer.js +296 -5
  102. package/dist/server/manifestCache.js +36 -0
  103. package/dist/server/renderer/ChunkExtractor.js +144 -0
  104. package/dist/server/renderer/document/Body.jsx +59 -0
  105. package/dist/server/renderer/document/FastRefresh.jsx +20 -0
  106. package/dist/server/renderer/document/Head.jsx +60 -0
  107. package/dist/server/renderer/document/index.jsx +2 -0
  108. package/dist/server/renderer/extract.js +149 -31
  109. package/dist/server/renderer/handler.jsx +450 -0
  110. package/dist/server/renderer/index.js +8 -1
  111. package/dist/server/renderer/render.js +47 -0
  112. package/dist/server/utils/botDetectionMiddleware.js +29 -0
  113. package/dist/server/utils/cjsRequire.js +10 -0
  114. package/dist/server/utils/userAgentUtil.js +87 -9
  115. package/dist/server/utils/validator.js +164 -1
  116. package/dist/vite/FastRefresh.jsx +20 -0
  117. package/dist/vite/inject-cache-key-plugin.js +276 -0
  118. package/dist/vite/loadCustomViteConfig.js +22 -0
  119. package/dist/vite/manifest-categorization-plugin.js +455 -0
  120. package/dist/vite/node-loader.mjs +458 -0
  121. package/dist/vite/scssParams.js +21 -0
  122. package/dist/vite/vite.config.client.js +130 -0
  123. package/dist/vite/vite.config.js +498 -0
  124. package/dist/vite/vite.config.server.js +84 -0
  125. package/dist/web-router/components/MetaTag.jsx +34 -0
  126. package/dist/web-router/components/RouterDataProvider.jsx +332 -0
  127. package/dist/web-router/components/Split.jsx +200 -0
  128. package/dist/web-router/components/SplitInview.jsx +106 -0
  129. package/dist/web-router/components/SsrRequestContext.jsx +14 -0
  130. package/dist/web-router/context.jsx +12 -0
  131. package/dist/web-router/hooks.jsx +72 -0
  132. package/dist/web-router/types/components/MetaTag.d.ts +2 -0
  133. package/dist/web-router/types/components/RouterDataProvider.d.ts +95 -0
  134. package/dist/web-router/types/context.d.ts +14 -0
  135. package/dist/web-router/types/hooks.d.ts +1 -0
  136. package/dist/web-router/types/index.d.ts +3 -0
  137. package/dist/web-router/types/utils/metaDataUtils.d.ts +24 -0
  138. package/dist/web-router/utils/metaDataUtils.jsx +119 -0
  139. package/mcp_v2/conversion-tasks.json +35 -17
  140. package/mcp_v2/knowledge-base.json +176 -17
  141. package/mcp_v2/lib/helpers.js +10 -0
  142. package/mcp_v2/mcp.js +40 -8
  143. package/mcp_v2/package.json +1 -0
  144. package/mcp_v2/tools/build.js +161 -37
  145. package/mcp_v2/tools/conversion.js +106 -5
  146. package/mcp_v2/tools/tasks.js +18 -14
  147. package/package.json +35 -68
  148. package/babel.config.js +0 -32
  149. package/dist/index.js +0 -1
  150. package/dist/scripts/devBuild.js +0 -3
  151. package/dist/scripts/devServe.js +0 -3
  152. package/dist/scripts/loadScriptsBeforeServerStarts.js +0 -4
  153. package/dist/scripts/registerAliases.js +0 -6
  154. package/dist/server/renderer/document/Body.js +0 -11
  155. package/dist/server/renderer/document/Head.js +0 -9
  156. package/dist/server/renderer/document/index.js +0 -1
  157. package/dist/server/renderer/handler.js +0 -47
  158. package/dist/server/startServer.js +0 -57
  159. package/dist/webpack/babel-plugins/remove-client.plugin.js +0 -5
  160. package/dist/webpack/babel-plugins/remove-ssr.plugin.js +0 -5
  161. package/dist/webpack/babel.config.client.js +0 -1
  162. package/dist/webpack/babel.config.ssr.js +0 -1
  163. package/dist/webpack/base.babel.js +0 -7
  164. package/dist/webpack/development.client.babel.js +0 -10
  165. package/dist/webpack/production.client.babel.js +0 -4
  166. package/dist/webpack/production.ssr.babel.js +0 -8
  167. package/dist/webpack/scssParams.js +0 -9
@@ -1,8 +1,102 @@
1
- "use strict";const fs=require("fs");const os=require("os");const path=require("path");const{execSync}=require("child_process");const{runInteractiveCommand}=require("../utils.js");module.exports=function createBuildPhase(ctx){const{WEBVIEW_CONFIG,PROJECT_NAME,SCHEME_NAME,APP_BUNDLE_ID,IPHONE_MODEL,url,progress,runCommand}=ctx;// ─── Simulator helpers ────────────────────────────────────────────────────
2
- 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");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];}}}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 simulatorList=JSON.parse(execSync("xcrun simctl list devices --json").toString());for(const runtime in simulatorList.devices){for(const device of simulatorList.devices[runtime]){if(device.state==="Booted")return{udid:device.udid,name:device.name,runtime};}}return null;}catch(error){console.log("Failed to get booted simulator info:",error.message);return null;}}// ─── Clean ────────────────────────────────────────────────────────────────
3
- async function cleanBuildArtifacts(){progress.start("clean");try{const xcuserdataPath=path.join(`${PROJECT_NAME}.xcodeproj`,`project.xcworkspace`,"xcuserdata");// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
4
- if(fs.existsSync(xcuserdataPath))await runCommand(`rm -rf "${xcuserdataPath}"`);const derivedDataPath=path.join(os.homedir(),"Library/Developer/Xcode/DerivedData");await runCommand(`rm -rf "${derivedDataPath}/${PROJECT_NAME}-*"`);try{await runCommand(`xcodebuild clean -scheme "${SCHEME_NAME}" -sdk iphonesimulator -configuration Debug`);}catch(error){progress.log("Warning: Clean command returned non-zero exit code","warning");}progress.complete("clean");}catch(error){progress.fail("clean",error.message);throw error;}}// ─── Build commands ───────────────────────────────────────────────────────
5
- function makeBuildCommand(scheme,sdk,bundleId,derivedDataPath,projectName,udid,action){return`xcodebuild \
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const os = require("os");
5
+ const path = require("path");
6
+ const {
7
+ execSync
8
+ } = require("child_process");
9
+ const {
10
+ runInteractiveCommand
11
+ } = require("../utils.js");
12
+ module.exports = function createBuildPhase(ctx) {
13
+ const {
14
+ WEBVIEW_CONFIG,
15
+ PROJECT_NAME,
16
+ SCHEME_NAME,
17
+ APP_BUNDLE_ID,
18
+ IPHONE_MODEL,
19
+ url,
20
+ progress,
21
+ runCommand
22
+ } = ctx;
23
+
24
+ // ─── Simulator helpers ────────────────────────────────────────────────────
25
+
26
+ async function getBootedSimulatorUUID(modelName) {
27
+ const UUID_RE = /[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}/i;
28
+ try {
29
+ const output = await runInteractiveCommand("xcrun", ["simctl", "list", "devices"], {});
30
+ const lines = output.split("\n");
31
+ for (const line of lines) {
32
+ if (line.includes(modelName) && line.includes("Booted")) {
33
+ const m = line.match(UUID_RE);
34
+ if (m) {
35
+ console.log(`Found booted simulator of model ${modelName}`);
36
+ return m[0];
37
+ }
38
+ }
39
+ }
40
+ console.log(`No booted simulator of model ${modelName} found, checking for any booted simulator...`);
41
+ for (const line of lines) {
42
+ if (line.includes("Booted")) {
43
+ const m = line.match(UUID_RE);
44
+ if (m) {
45
+ console.log("Found another booted simulator, will use it instead");
46
+ return m[0];
47
+ }
48
+ }
49
+ }
50
+ return null;
51
+ } catch (error) {
52
+ console.log("No booted simulators found");
53
+ return null;
54
+ }
55
+ }
56
+ async function getBootedSimulatorInfo() {
57
+ try {
58
+ const simulatorList = JSON.parse(execSync("xcrun simctl list devices --json").toString());
59
+ for (const runtime in simulatorList.devices) {
60
+ for (const device of simulatorList.devices[runtime]) {
61
+ if (device.state === "Booted") return {
62
+ udid: device.udid,
63
+ name: device.name,
64
+ runtime
65
+ };
66
+ }
67
+ }
68
+ return null;
69
+ } catch (error) {
70
+ console.log("Failed to get booted simulator info:", error.message);
71
+ return null;
72
+ }
73
+ }
74
+
75
+ // ─── Clean ────────────────────────────────────────────────────────────────
76
+
77
+ async function cleanBuildArtifacts() {
78
+ progress.start("clean");
79
+ try {
80
+ const xcuserdataPath = path.join(`${PROJECT_NAME}.xcodeproj`, `project.xcworkspace`, "xcuserdata"); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
81
+ if (fs.existsSync(xcuserdataPath)) await runCommand(`rm -rf "${xcuserdataPath}"`);
82
+ const derivedDataPath = path.join(os.homedir(), "Library/Developer/Xcode/DerivedData");
83
+ await runCommand(`rm -rf "${derivedDataPath}/${PROJECT_NAME}-*"`);
84
+ try {
85
+ await runCommand(`xcodebuild clean -scheme "${SCHEME_NAME}" -sdk iphonesimulator -configuration Debug`);
86
+ } catch (error) {
87
+ progress.log("Warning: Clean command returned non-zero exit code", "warning");
88
+ }
89
+ progress.complete("clean");
90
+ } catch (error) {
91
+ progress.fail("clean", error.message);
92
+ throw error;
93
+ }
94
+ }
95
+
96
+ // ─── Build commands ───────────────────────────────────────────────────────
97
+
98
+ function makeBuildCommand(scheme, sdk, bundleId, derivedDataPath, projectName, udid, action) {
99
+ return `xcodebuild \
6
100
  -scheme "${scheme}" \
7
101
  -sdk ${sdk} \
8
102
  -configuration Debug \
@@ -17,24 +111,549 @@ function makeBuildCommand(scheme,sdk,bundleId,derivedDataPath,projectName,udid,a
17
111
  CONFIGURATION_BUILD_DIR="${derivedDataPath}/${projectName}-Build/Build/Products/Debug-iphonesimulator" \
18
112
  OS_ACTIVITY_MODE=debug \
19
113
  SWIFT_DEBUG_LOG=1 \
20
- ${action}`;}async function buildProject(scheme,sdk,destination,bundleId,derivedDataPath,projectName){const bootedInfo=await getBootedSimulatorInfo();if(!bootedInfo)throw new Error("No booted simulator found");console.log(`Building with destination: platform=iOS Simulator,id=${bootedInfo.udid}`);const isNotificationsEnabled=WEBVIEW_CONFIG.notifications?.enabled??false;progress.log(isNotificationsEnabled?"Building with notifications enabled (CatalystNotifications module included)":"Building without notifications (Firebase excluded)","info");return runCommand(makeBuildCommand(scheme,sdk,bundleId,derivedDataPath,projectName,bootedInfo.udid,"build"),{maxBuffer:1024*1024*10});}async function buildProjectForTesting(scheme,sdk,destination,bundleId,derivedDataPath,projectName){const bootedInfo=await getBootedSimulatorInfo();if(!bootedInfo)throw new Error("No booted simulator found");console.log(`Building for testing with destination: platform=iOS Simulator,id=${bootedInfo.udid}`);const isNotificationsEnabled=WEBVIEW_CONFIG.notifications?.enabled??false;progress.log(isNotificationsEnabled?"Building for testing with notifications enabled":"Building for testing without notifications","info");return runCommand(makeBuildCommand(scheme,sdk,bundleId,derivedDataPath,projectName,bootedInfo.udid,"build-for-testing"),{maxBuffer:1024*1024*10});}async function buildXcodeProject(){progress.start("build");try{const derivedDataPath=path.join(os.homedir(),"Library/Developer/Xcode/DerivedData");try{progress.log("Building for specified simulator...","info");await buildProject(SCHEME_NAME,"iphonesimulator",`platform=iOS Simulator,name=${IPHONE_MODEL}`,APP_BUNDLE_ID,derivedDataPath,PROJECT_NAME);}catch(buildError){progress.log("Initial build failed, attempting fallback...","warning");const bootedUUID=await getBootedSimulatorUUID(IPHONE_MODEL);if(bootedUUID){progress.log(`Found booted simulator, retrying build...`,"info");await buildProject(SCHEME_NAME,"iphonesimulator",`platform=iOS Simulator,id=${bootedUUID}`,APP_BUNDLE_ID,derivedDataPath,PROJECT_NAME);}else{throw buildError;}}progress.complete("build");}catch(error){progress.fail("build",error.message);progress.printTreeContent("Troubleshooting Guide",["Build failed. Please try the following steps:",{text:'Run "npm run setupEmulator:ios" to reconfigure iOS settings',indent:1,prefix:"├─ ",color:"yellow"},{text:"Check if Xcode is properly installed and updated",indent:1,prefix:"├─ ",color:"yellow"},{text:"Verify selected simulator exists",indent:1,prefix:"└─ ",color:"yellow"},"\nVerify Configuration:",{text:`Selected Simulator: ${IPHONE_MODEL}`,indent:1,prefix:"├─ ",color:"gray"},{text:`Server URL: ${url}`,indent:1,prefix:"└─ ",color:"gray"}]);throw error;}}// ─── Find / install / launch ──────────────────────────────────────────────
21
- async function findAppPath(){progress.start("findApp");try{const DERIVED_DATA_DIR=path.join(os.homedir(),"Library/Developer/Xcode/DerivedData");let APP_PATH="";try{APP_PATH=execSync(`find "${DERIVED_DATA_DIR}" -path "*${PROJECT_NAME}-*" -prune -not -path "*/Index.noindex*" -path "*/Build/Products/Debug-iphonesimulator/${PROJECT_NAME}.app" -type d | head -n 1`// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - DERIVED_DATA_DIR and PROJECT_NAME are internal iOS build paths.
22
- ).toString().trim();}catch(error){progress.log("Primary app path search failed, trying fallback...","warning");}if(!APP_PATH){try{APP_PATH=execSync(`find "${DERIVED_DATA_DIR}" -path "*${PROJECT_NAME}-*" -name "${PROJECT_NAME}.app" -type d -not -path "*/Index.noindex*" | head -n 1`// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - DERIVED_DATA_DIR and PROJECT_NAME are internal iOS build paths.
23
- ).toString().trim();}catch(error){throw new Error("Could not locate built application");}}if(!APP_PATH)throw new Error("No .app file found");progress.complete("findApp");return APP_PATH;}catch(error){progress.fail("findApp",error.message);throw error;}}async function moveAppToBuildOutput(APP_PATH){const{BUILD_OUTPUT_PATH,iosConfig}=ctx;try{const buildType=iosConfig.buildType||"debug";const appName=iosConfig.appName||"app";const currentDate=new Date().toLocaleDateString("en-GB").replace(/\//g,"-");const currentTime=new Date().toLocaleTimeString("en-US",{hour12:true,hour:"2-digit",minute:"2-digit",second:"2-digit"}).replace(/:/g,"-");const destinationDir=path.join(process.cwd(),BUILD_OUTPUT_PATH,// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - BUILD_OUTPUT_PATH is trusted application config.
24
- "native","ios",currentDate,buildType// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - buildType is "debug" or "release" from config.
25
- );const destinationPath=path.join(destinationDir,`${appName}-${currentTime}.app`);// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
26
- if(!fs.existsSync(destinationDir))fs.mkdirSync(destinationDir,{recursive:true});await runCommand(`cp -R "${APP_PATH}" "${destinationPath}"`);return destinationPath;}catch(error){console.error("Error moving app to build output:",error.message);throw error;}}async function uninstallExistingApp(){console.log("Uninstalling the app if it exists...");await runCommand(`xcrun simctl uninstall booted "${APP_BUNDLE_ID}"`);}async function installApp(APP_PATH){console.log("Installing the app...");try{await runCommand(`xcrun simctl install booted "${APP_PATH}"`);}catch(error){console.error("Error installing the app:",error);throw error;}}async function waitForInstallation(){console.log("Waiting for the app to be fully installed...");for(let i=0;i<30;i++){try{execSync(`xcrun simctl get_app_container booted "${APP_BUNDLE_ID}"`);// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - APP_BUNDLE_ID is generated from trusted native config.
27
- console.log("App installed successfully.");break;}catch(error){if(i===29)throw new Error("Timeout: App installation took too long.");await new Promise(resolve=>setTimeout(resolve,1000));}}}async function handleLaunchError(error){console.error("Error launching the app:",error);console.log("Checking app container...");try{await runCommand(`xcrun simctl get_app_container booted "${APP_BUNDLE_ID}"`);}catch{console.log("App container not found");}throw error;}async function verifyAppLaunch(){console.log("Waiting for the app to launch...");for(let i=0;i<10;i++){try{const launchResult=execSync(`xcrun simctl launch booted "${APP_BUNDLE_ID}"`).toString();// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
28
- if(launchResult.includes("already launched")){console.log("App launched successfully.");break;}}catch(error){if(i===9)console.log("Warning: App launch might have failed or taken too long.");}await new Promise(resolve=>setTimeout(resolve,1000));}}async function launchAndVerifyApp(){console.log("Launching the app...");try{await runCommand(`xcrun simctl launch booted "${APP_BUNDLE_ID}"`);await verifyAppLaunch();}catch(error){await handleLaunchError(error);}}async function focusSimulator(){console.log("Focusing on Simulator...");await runCommand(`osascript -e 'tell application "Simulator" to activate'`);}async function installAndLaunchApp(APP_PATH){progress.start("install");try{await uninstallExistingApp();await installApp(APP_PATH);await waitForInstallation();progress.complete("install");progress.start("launch");await launchAndVerifyApp();await focusSimulator();progress.complete("launch");}catch(error){const currentStep=progress.currentStep.id;progress.fail(currentStep,error.message);throw error;}}// ─── Physical device ──────────────────────────────────────────────────────
29
- async function detectPhysicalDevices(){progress.start("deviceDetection");const{iosConfig}=ctx;try{progress.log("Scanning for connected physical devices...","info");let physicalDevices=[];const configuredUDID=iosConfig.deviceUDID;if(configuredUDID){progress.log(`Using configured device UDID: ${configuredUDID}`,"info");try{const instrumentsOutput=execSync("instruments -s devices").toString();if(instrumentsOutput.includes(configuredUDID)){const deviceLine=instrumentsOutput.split("\n").find(line=>line.includes(configuredUDID));if(deviceLine){const nameMatch=deviceLine.match(/^(.+?)\s+\(/);const versionMatch=deviceLine.match(/\((\d+\.\d+(?:\.\d+)?)\)/);const deviceName=nameMatch?nameMatch[1].trim():"Physical Device";const deviceVersion=versionMatch?versionMatch[1]:"Unknown";progress.log(`✅ Found configured physical device: ${deviceName} (${deviceVersion})`,"success");physicalDevices.push({name:deviceName,version:deviceVersion,udid:configuredUDID,type:"physical"});progress.complete("deviceDetection");return physicalDevices[0];}}else{progress.log(`⚠️ Configured device UDID not found in connected devices`,"warning");progress.log("Falling back to auto-detection...","info");}}catch(error){progress.log(`Error verifying configured device: ${error.message}`,"warning");progress.log("Falling back to auto-detection...","info");}}else{progress.log("No device UDID configured, using auto-detection","info");}const tryInstruments=()=>{try{const out=execSync("instruments -s devices").toString();return out.split("\n").filter(line=>{const m=line.match(/^(.+?)\s+\((\d+\.\d+(?:\.\d+)?)\)\s+\[([A-F0-9-]{36})\](?:\s+\(Simulator\))?$/);return m&&!line.includes("(Simulator)");}).map(line=>{const[,name,version,udid]=line.match(/^(.+?)\s+\((\d+\.\d+(?:\.\d+)?)\)\s+\[([A-F0-9-]{36})\]/);return{name:name.trim(),version,udid,type:"physical"};});}catch{return[];}};physicalDevices=tryInstruments();if(physicalDevices.length>0)progress.log(`Found ${physicalDevices.length} physical device(s) via instruments`,"success");if(physicalDevices.length===0){try{const xcodebuildOutput=execSync(`xcodebuild -scheme "${SCHEME_NAME}" -showdestinations`// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - SCHEME_NAME is generated from trusted iOS project config.
30
- ).toString();progress.log("Scanning xcodebuild destinations for physical devices...","info");for(const line of xcodebuildOutput.split("\n")){const m=line.match(/\{\s*platform:iOS,\s*arch:(\w+),\s*id:([A-F0-9-]+),\s*name:(.+?)\s*\}/);if(m){const[,arch,udid,name]=m;progress.log(`Found device candidate: ${name.trim()} - ${udid}`,"info");if(!udid.includes("placeholder")&&udid.length>20){progress.log(`✅ Valid physical device: ${name.trim()}`,"success");physicalDevices.push({name:name.trim(),version:"Unknown",udid,arch,type:"physical"});}else{progress.log(`❌ Skipping placeholder: ${name.trim()}`,"warning");}}}}catch(error){progress.log("xcodebuild destinations failed","warning");progress.log(`Error: ${error.message}`,"error");}}if(physicalDevices.length===0)physicalDevices=tryInstruments();if(physicalDevices.length===0){try{for(const line of execSync("xcrun devicectl list devices").toString().split("\n")){if(line.includes("Connected")&&!line.includes("Simulator")){const udidMatch=line.match(/([A-F0-9-]{36})/);const nameMatch=line.match(/^(.+?)\s+\(/);if(udidMatch&&nameMatch)physicalDevices.push({name:nameMatch[1].trim(),version:"Unknown",udid:udidMatch[1],type:"physical"});}}}catch{progress.log("devicectl command not available or failed","warning");}}if(physicalDevices.length>0){progress.log(`Found ${physicalDevices.length} physical device(s):`,"success");physicalDevices.forEach(d=>progress.log(` 📱 ${d.name} (${d.version||"Unknown iOS"}) - ${d.udid}`,"info"));progress.complete("deviceDetection");return physicalDevices[0];}else{progress.log("No physical devices detected","warning");progress.complete("deviceDetection");return null;}}catch(error){progress.fail("deviceDetection",error.message);return null;}}async function buildProjectForPhysicalDevice(scheme,bundleId,derivedDataPath,projectName,device){progress.log(`Building for physical device: ${device.name}`,"info");const projectPath=`${process.cwd()}/${projectName}.xcodeproj`;if(!fs.existsSync(projectPath))throw new Error(`Xcode project not found at: ${projectPath}. Current directory: ${process.cwd()}`);const isNotificationsEnabled=WEBVIEW_CONFIG.notifications?.enabled??false;progress.log(isNotificationsEnabled?"Building with notifications enabled":"Building without notifications","info");const buildCommand=`xcodebuild \
114
+ ${action}`;
115
+ }
116
+ async function buildProject(scheme, sdk, destination, bundleId, derivedDataPath, projectName) {
117
+ const bootedInfo = await getBootedSimulatorInfo();
118
+ if (!bootedInfo) throw new Error("No booted simulator found");
119
+ console.log(`Building with destination: platform=iOS Simulator,id=${bootedInfo.udid}`);
120
+ const isNotificationsEnabled = WEBVIEW_CONFIG.notifications?.enabled ?? false;
121
+ progress.log(isNotificationsEnabled ? "Building with notifications enabled (CatalystNotifications module included)" : "Building without notifications (Firebase excluded)", "info");
122
+ return runCommand(makeBuildCommand(scheme, sdk, bundleId, derivedDataPath, projectName, bootedInfo.udid, "build"), {
123
+ maxBuffer: 1024 * 1024 * 10
124
+ });
125
+ }
126
+ async function buildProjectForTesting(scheme, sdk, destination, bundleId, derivedDataPath, projectName) {
127
+ const bootedInfo = await getBootedSimulatorInfo();
128
+ if (!bootedInfo) throw new Error("No booted simulator found");
129
+ console.log(`Building for testing with destination: platform=iOS Simulator,id=${bootedInfo.udid}`);
130
+ const isNotificationsEnabled = WEBVIEW_CONFIG.notifications?.enabled ?? false;
131
+ progress.log(isNotificationsEnabled ? "Building for testing with notifications enabled" : "Building for testing without notifications", "info");
132
+ return runCommand(makeBuildCommand(scheme, sdk, bundleId, derivedDataPath, projectName, bootedInfo.udid, "build-for-testing"), {
133
+ maxBuffer: 1024 * 1024 * 10
134
+ });
135
+ }
136
+ async function buildXcodeProject() {
137
+ progress.start("build");
138
+ try {
139
+ const derivedDataPath = path.join(os.homedir(), "Library/Developer/Xcode/DerivedData");
140
+ try {
141
+ progress.log("Building for specified simulator...", "info");
142
+ await buildProject(SCHEME_NAME, "iphonesimulator", `platform=iOS Simulator,name=${IPHONE_MODEL}`, APP_BUNDLE_ID, derivedDataPath, PROJECT_NAME);
143
+ } catch (buildError) {
144
+ progress.log("Initial build failed, attempting fallback...", "warning");
145
+ const bootedUUID = await getBootedSimulatorUUID(IPHONE_MODEL);
146
+ if (bootedUUID) {
147
+ progress.log(`Found booted simulator, retrying build...`, "info");
148
+ await buildProject(SCHEME_NAME, "iphonesimulator", `platform=iOS Simulator,id=${bootedUUID}`, APP_BUNDLE_ID, derivedDataPath, PROJECT_NAME);
149
+ } else {
150
+ throw buildError;
151
+ }
152
+ }
153
+ progress.complete("build");
154
+ } catch (error) {
155
+ progress.fail("build", error.message);
156
+ progress.printTreeContent("Troubleshooting Guide", ["Build failed. Please try the following steps:", {
157
+ text: 'Run "npm run setupEmulator:ios" to reconfigure iOS settings',
158
+ indent: 1,
159
+ prefix: "├─ ",
160
+ color: "yellow"
161
+ }, {
162
+ text: "Check if Xcode is properly installed and updated",
163
+ indent: 1,
164
+ prefix: "├─ ",
165
+ color: "yellow"
166
+ }, {
167
+ text: "Verify selected simulator exists",
168
+ indent: 1,
169
+ prefix: "└─ ",
170
+ color: "yellow"
171
+ }, "\nVerify Configuration:", {
172
+ text: `Selected Simulator: ${IPHONE_MODEL}`,
173
+ indent: 1,
174
+ prefix: "├─ ",
175
+ color: "gray"
176
+ }, {
177
+ text: `Server URL: ${url}`,
178
+ indent: 1,
179
+ prefix: "└─ ",
180
+ color: "gray"
181
+ }]);
182
+ throw error;
183
+ }
184
+ }
185
+
186
+ // ─── Find / install / launch ──────────────────────────────────────────────
187
+
188
+ async function findAppPath() {
189
+ progress.start("findApp");
190
+ try {
191
+ const DERIVED_DATA_DIR = path.join(os.homedir(), "Library/Developer/Xcode/DerivedData");
192
+ let APP_PATH = "";
193
+ try {
194
+ APP_PATH = execSync(`find "${DERIVED_DATA_DIR}" -path "*${PROJECT_NAME}-*" -prune -not -path "*/Index.noindex*" -path "*/Build/Products/Debug-iphonesimulator/${PROJECT_NAME}.app" -type d | head -n 1` // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - DERIVED_DATA_DIR and PROJECT_NAME are internal iOS build paths.
195
+ ).toString().trim();
196
+ } catch (error) {
197
+ progress.log("Primary app path search failed, trying fallback...", "warning");
198
+ }
199
+ if (!APP_PATH) {
200
+ try {
201
+ APP_PATH = execSync(`find "${DERIVED_DATA_DIR}" -path "*${PROJECT_NAME}-*" -name "${PROJECT_NAME}.app" -type d -not -path "*/Index.noindex*" | head -n 1` // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - DERIVED_DATA_DIR and PROJECT_NAME are internal iOS build paths.
202
+ ).toString().trim();
203
+ } catch (error) {
204
+ throw new Error("Could not locate built application");
205
+ }
206
+ }
207
+ if (!APP_PATH) throw new Error("No .app file found");
208
+ progress.complete("findApp");
209
+ return APP_PATH;
210
+ } catch (error) {
211
+ progress.fail("findApp", error.message);
212
+ throw error;
213
+ }
214
+ }
215
+ async function moveAppToBuildOutput(APP_PATH) {
216
+ const {
217
+ BUILD_OUTPUT_PATH,
218
+ iosConfig
219
+ } = ctx;
220
+ try {
221
+ const buildType = iosConfig.buildType || "debug";
222
+ const appName = iosConfig.appName || "app";
223
+ const currentDate = new Date().toLocaleDateString("en-GB").replace(/\//g, "-");
224
+ const currentTime = new Date().toLocaleTimeString("en-US", {
225
+ hour12: true,
226
+ hour: "2-digit",
227
+ minute: "2-digit",
228
+ second: "2-digit"
229
+ }).replace(/:/g, "-");
230
+ const destinationDir = path.join(process.cwd(), BUILD_OUTPUT_PATH,
231
+ // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - BUILD_OUTPUT_PATH is trusted application config.
232
+ "native", "ios", currentDate, buildType // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - buildType is "debug" or "release" from config.
233
+ );
234
+ const destinationPath = path.join(destinationDir, `${appName}-${currentTime}.app`); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal
235
+ if (!fs.existsSync(destinationDir)) fs.mkdirSync(destinationDir, {
236
+ recursive: true
237
+ });
238
+ await runCommand(`cp -R "${APP_PATH}" "${destinationPath}"`);
239
+ return destinationPath;
240
+ } catch (error) {
241
+ console.error("Error moving app to build output:", error.message);
242
+ throw error;
243
+ }
244
+ }
245
+ async function uninstallExistingApp() {
246
+ console.log("Uninstalling the app if it exists...");
247
+ await runCommand(`xcrun simctl uninstall booted "${APP_BUNDLE_ID}"`);
248
+ }
249
+ async function installApp(APP_PATH) {
250
+ console.log("Installing the app...");
251
+ try {
252
+ await runCommand(`xcrun simctl install booted "${APP_PATH}"`);
253
+ } catch (error) {
254
+ console.error("Error installing the app:", error);
255
+ throw error;
256
+ }
257
+ }
258
+ async function waitForInstallation() {
259
+ console.log("Waiting for the app to be fully installed...");
260
+ for (let i = 0; i < 30; i++) {
261
+ try {
262
+ execSync(`xcrun simctl get_app_container booted "${APP_BUNDLE_ID}"`); // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - APP_BUNDLE_ID is generated from trusted native config.
263
+ console.log("App installed successfully.");
264
+ break;
265
+ } catch (error) {
266
+ if (i === 29) throw new Error("Timeout: App installation took too long.");
267
+ await new Promise(resolve => setTimeout(resolve, 1000));
268
+ }
269
+ }
270
+ }
271
+ async function handleLaunchError(error) {
272
+ console.error("Error launching the app:", error);
273
+ console.log("Checking app container...");
274
+ try {
275
+ await runCommand(`xcrun simctl get_app_container booted "${APP_BUNDLE_ID}"`);
276
+ } catch {
277
+ console.log("App container not found");
278
+ }
279
+ throw error;
280
+ }
281
+ async function verifyAppLaunch() {
282
+ console.log("Waiting for the app to launch...");
283
+ for (let i = 0; i < 10; i++) {
284
+ try {
285
+ const launchResult = execSync(`xcrun simctl launch booted "${APP_BUNDLE_ID}"`).toString(); // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process
286
+ if (launchResult.includes("already launched")) {
287
+ console.log("App launched successfully.");
288
+ break;
289
+ }
290
+ } catch (error) {
291
+ if (i === 9) console.log("Warning: App launch might have failed or taken too long.");
292
+ }
293
+ await new Promise(resolve => setTimeout(resolve, 1000));
294
+ }
295
+ }
296
+ async function launchAndVerifyApp() {
297
+ console.log("Launching the app...");
298
+ try {
299
+ await runCommand(`xcrun simctl launch booted "${APP_BUNDLE_ID}"`);
300
+ await verifyAppLaunch();
301
+ } catch (error) {
302
+ await handleLaunchError(error);
303
+ }
304
+ }
305
+ async function focusSimulator() {
306
+ console.log("Focusing on Simulator...");
307
+ await runCommand(`osascript -e 'tell application "Simulator" to activate'`);
308
+ }
309
+ async function installAndLaunchApp(APP_PATH) {
310
+ progress.start("install");
311
+ try {
312
+ await uninstallExistingApp();
313
+ await installApp(APP_PATH);
314
+ await waitForInstallation();
315
+ progress.complete("install");
316
+ progress.start("launch");
317
+ await launchAndVerifyApp();
318
+ await focusSimulator();
319
+ progress.complete("launch");
320
+ } catch (error) {
321
+ const currentStep = progress.currentStep.id;
322
+ progress.fail(currentStep, error.message);
323
+ throw error;
324
+ }
325
+ }
326
+
327
+ // ─── Physical device ──────────────────────────────────────────────────────
328
+
329
+ async function detectPhysicalDevices() {
330
+ progress.start("deviceDetection");
331
+ const {
332
+ iosConfig
333
+ } = ctx;
334
+ try {
335
+ progress.log("Scanning for connected physical devices...", "info");
336
+ let physicalDevices = [];
337
+ const configuredUDID = iosConfig.deviceUDID;
338
+ if (configuredUDID) {
339
+ progress.log(`Using configured device UDID: ${configuredUDID}`, "info");
340
+ try {
341
+ const instrumentsOutput = execSync("instruments -s devices").toString();
342
+ if (instrumentsOutput.includes(configuredUDID)) {
343
+ const deviceLine = instrumentsOutput.split("\n").find(line => line.includes(configuredUDID));
344
+ if (deviceLine) {
345
+ const nameMatch = deviceLine.match(/^(.+?)\s+\(/);
346
+ const versionMatch = deviceLine.match(/\((\d+\.\d+(?:\.\d+)?)\)/);
347
+ const deviceName = nameMatch ? nameMatch[1].trim() : "Physical Device";
348
+ const deviceVersion = versionMatch ? versionMatch[1] : "Unknown";
349
+ progress.log(`✅ Found configured physical device: ${deviceName} (${deviceVersion})`, "success");
350
+ physicalDevices.push({
351
+ name: deviceName,
352
+ version: deviceVersion,
353
+ udid: configuredUDID,
354
+ type: "physical"
355
+ });
356
+ progress.complete("deviceDetection");
357
+ return physicalDevices[0];
358
+ }
359
+ } else {
360
+ progress.log(`⚠️ Configured device UDID not found in connected devices`, "warning");
361
+ progress.log("Falling back to auto-detection...", "info");
362
+ }
363
+ } catch (error) {
364
+ progress.log(`Error verifying configured device: ${error.message}`, "warning");
365
+ progress.log("Falling back to auto-detection...", "info");
366
+ }
367
+ } else {
368
+ progress.log("No device UDID configured, using auto-detection", "info");
369
+ }
370
+ const tryInstruments = () => {
371
+ try {
372
+ const out = execSync("instruments -s devices").toString();
373
+ return out.split("\n").filter(line => {
374
+ const m = line.match(/^(.+?)\s+\((\d+\.\d+(?:\.\d+)?)\)\s+\[([A-F0-9-]{36})\](?:\s+\(Simulator\))?$/);
375
+ return m && !line.includes("(Simulator)");
376
+ }).map(line => {
377
+ const [, name, version, udid] = line.match(/^(.+?)\s+\((\d+\.\d+(?:\.\d+)?)\)\s+\[([A-F0-9-]{36})\]/);
378
+ return {
379
+ name: name.trim(),
380
+ version,
381
+ udid,
382
+ type: "physical"
383
+ };
384
+ });
385
+ } catch {
386
+ return [];
387
+ }
388
+ };
389
+ physicalDevices = tryInstruments();
390
+ if (physicalDevices.length > 0) progress.log(`Found ${physicalDevices.length} physical device(s) via instruments`, "success");
391
+ if (physicalDevices.length === 0) {
392
+ try {
393
+ const xcodebuildOutput = execSync(`xcodebuild -scheme "${SCHEME_NAME}" -showdestinations` // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - SCHEME_NAME is generated from trusted iOS project config.
394
+ ).toString();
395
+ progress.log("Scanning xcodebuild destinations for physical devices...", "info");
396
+ for (const line of xcodebuildOutput.split("\n")) {
397
+ const m = line.match(/\{\s*platform:iOS,\s*arch:(\w+),\s*id:([A-F0-9-]+),\s*name:(.+?)\s*\}/);
398
+ if (m) {
399
+ const [, arch, udid, name] = m;
400
+ progress.log(`Found device candidate: ${name.trim()} - ${udid}`, "info");
401
+ if (!udid.includes("placeholder") && udid.length > 20) {
402
+ progress.log(`✅ Valid physical device: ${name.trim()}`, "success");
403
+ physicalDevices.push({
404
+ name: name.trim(),
405
+ version: "Unknown",
406
+ udid,
407
+ arch,
408
+ type: "physical"
409
+ });
410
+ } else {
411
+ progress.log(`❌ Skipping placeholder: ${name.trim()}`, "warning");
412
+ }
413
+ }
414
+ }
415
+ } catch (error) {
416
+ progress.log("xcodebuild destinations failed", "warning");
417
+ progress.log(`Error: ${error.message}`, "error");
418
+ }
419
+ }
420
+ if (physicalDevices.length === 0) physicalDevices = tryInstruments();
421
+ if (physicalDevices.length === 0) {
422
+ try {
423
+ for (const line of execSync("xcrun devicectl list devices").toString().split("\n")) {
424
+ if (line.includes("Connected") && !line.includes("Simulator")) {
425
+ const udidMatch = line.match(/([A-F0-9-]{36})/);
426
+ const nameMatch = line.match(/^(.+?)\s+\(/);
427
+ if (udidMatch && nameMatch) physicalDevices.push({
428
+ name: nameMatch[1].trim(),
429
+ version: "Unknown",
430
+ udid: udidMatch[1],
431
+ type: "physical"
432
+ });
433
+ }
434
+ }
435
+ } catch {
436
+ progress.log("devicectl command not available or failed", "warning");
437
+ }
438
+ }
439
+ if (physicalDevices.length > 0) {
440
+ progress.log(`Found ${physicalDevices.length} physical device(s):`, "success");
441
+ physicalDevices.forEach(d => progress.log(` 📱 ${d.name} (${d.version || "Unknown iOS"}) - ${d.udid}`, "info"));
442
+ progress.complete("deviceDetection");
443
+ return physicalDevices[0];
444
+ } else {
445
+ progress.log("No physical devices detected", "warning");
446
+ progress.complete("deviceDetection");
447
+ return null;
448
+ }
449
+ } catch (error) {
450
+ progress.fail("deviceDetection", error.message);
451
+ return null;
452
+ }
453
+ }
454
+ async function buildProjectForPhysicalDevice(scheme, bundleId, derivedDataPath, projectName, device) {
455
+ progress.log(`Building for physical device: ${device.name}`, "info");
456
+ const projectPath = `${process.cwd()}/${projectName}.xcodeproj`;
457
+ if (!fs.existsSync(projectPath)) throw new Error(`Xcode project not found at: ${projectPath}. Current directory: ${process.cwd()}`);
458
+ const isNotificationsEnabled = WEBVIEW_CONFIG.notifications?.enabled ?? false;
459
+ progress.log(isNotificationsEnabled ? "Building with notifications enabled" : "Building without notifications", "info");
460
+ const buildCommand = `xcodebuild \
31
461
  -scheme ${scheme} \
32
462
  -sdk iphoneos \
33
463
  -configuration Debug \
34
464
  -destination platform=iOS,id=${device.udid} \
35
465
  PRODUCT_BUNDLE_IDENTIFIER=${bundleId} \
36
466
  ONLY_ACTIVE_ARCH=YES \
37
- build`;progress.log(`Executing command: ${buildCommand}`,"info");return runCommand(buildCommand,{maxBuffer:1024*1024*10});}async function findPhysicalDeviceAppPath(){const DERIVED_DATA_DIR=path.join(os.homedir(),"Library/Developer/Xcode/DerivedData");let APP_PATH="";try{APP_PATH=execSync(`find "${DERIVED_DATA_DIR}" -name "${PROJECT_NAME}.app" -path "*/Build/Products/Debug-iphoneos/*" -not -path "*/Index.noindex/*" -type d | head -n 1`// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - DERIVED_DATA_DIR and PROJECT_NAME are internal iOS build paths.
38
- ).toString().trim();}catch{progress.log("Primary app path search failed for physical device, trying fallback...","warning");}if(!APP_PATH){try{APP_PATH=execSync(`find "${DERIVED_DATA_DIR}" -path "*${PROJECT_NAME}-Build/Build/Products/Debug-iphoneos/${PROJECT_NAME}.app" -type d | head -n 1`// nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - DERIVED_DATA_DIR and PROJECT_NAME are internal iOS build paths.
39
- ).toString().trim();}catch{progress.log("Fallback app path search also failed","warning");}}if(!APP_PATH)throw new Error("No .app file found for physical device. Check if build completed successfully.");progress.log(`Found app bundle: ${APP_PATH}`,"success");return APP_PATH;}async function installAndLaunchOnPhysicalDevice(APP_PATH,device){progress.start("install");try{progress.log(`Installing app on physical device: ${device.name}`,"info");try{await runCommand(`xcrun devicectl device install app --device ${device.udid} "${APP_PATH}"`);progress.log("App installed successfully using devicectl","success");}catch(devicectlError){throw new Error(`devicectl installation failed: ${devicectlError.message}`);}progress.complete("install");progress.start("launch");try{await runCommand(`xcrun devicectl device process launch --device ${device.udid} "${APP_BUNDLE_ID}"`);progress.log("App launched successfully using devicectl","success");}catch(launchError){progress.log("App installation completed, but launch failed. You can manually launch the app on your device.","warning");progress.log(`To launch manually, look for the app with bundle ID: ${APP_BUNDLE_ID}`,"info");}progress.complete("launch");}catch(error){const currentStep=progress.currentStep.id;progress.fail(currentStep,error.message);progress.printTreeContent("Physical Device Installation Failed",["Installation failed. Common solutions:",{text:"Ensure device is connected and unlocked",indent:1,prefix:"├─ ",color:"yellow"},{text:"Trust the development certificate on your device",indent:1,prefix:"├─ ",color:"yellow"},{text:"Check that device is in developer mode",indent:1,prefix:"├─ ",color:"yellow"},{text:"Verify app bundle is valid",indent:1,prefix:"└─ ",color:"yellow"},"","Device Details:",{text:`Device: ${device.name}`,indent:1,prefix:"├─ ",color:"gray"},{text:`UDID: ${device.udid}`,indent:1,prefix:"├─ ",color:"gray"},{text:`App Bundle: ${APP_PATH}`,indent:1,prefix:"└─ ",color:"gray"}]);throw error;}}// ─── Launch simulator ─────────────────────────────────────────────────────
40
- async function launchIOSSimulator(simulatorName){progress.start("launchSimulator");try{progress.log("Launching iOS Simulator...");const simulatorsJson=JSON.parse(execSync("xcrun simctl list devices -j").toString());let foundSimulator=null,foundSimulatorId=null,isBooted=false;Object.entries(simulatorsJson.devices).forEach(([,devices])=>{devices.forEach(device=>{if(device.name===simulatorName){foundSimulator=device;foundSimulatorId=device.udid;isBooted=device.state==="Booted";}});});if(!foundSimulator){console.log(`Configured simulator "${simulatorName}" not found.`);return;}if(!isBooted){progress.log(`Attempting to boot simulator: ${simulatorName} (${foundSimulatorId})`,"warning");if(foundSimulator.state==="Shutdown"){progress.log(`Simulator is shutdown, attempting to boot...`,"info");try{await runCommand(`xcrun simctl boot ${foundSimulatorId}`);progress.log(`Successfully booted simulator`,"success");}catch(bootError){progress.log(`Boot failed: ${bootError.message}`,"error");progress.log(`Looking for any booted simulator as fallback...`,"info");const fallbackUUID=await getBootedSimulatorUUID(simulatorName);if(fallbackUUID){progress.log(`Found fallback booted simulator: ${fallbackUUID}`,"success");foundSimulatorId=fallbackUUID;}else throw new Error(`Failed to boot simulator and no fallback available: ${bootError.message}`);}}else{progress.log(`Simulator state is '${foundSimulator.state}', not 'Shutdown'. Checking if we can use it...`,"warning");if(foundSimulator.state==="Booted")progress.log(`Wait, simulator is actually booted! Using it.`,"success");else throw new Error(`Simulator is in unexpected state: ${foundSimulator.state}`);}}else{progress.log(`Simulator ${simulatorName} is already booted`,"success");}progress.log("Opening Simulator.app...");await runCommand("open -a Simulator");await new Promise(resolve=>setTimeout(resolve,1000));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);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"}]);throw error;}}return{cleanBuildArtifacts,buildXcodeProject,buildProjectForTesting,findAppPath,moveAppToBuildOutput,installAndLaunchApp,detectPhysicalDevices,buildProjectForPhysicalDevice,findPhysicalDeviceAppPath,installAndLaunchOnPhysicalDevice,launchIOSSimulator,getBootedSimulatorUUID};};
467
+ build`;
468
+ progress.log(`Executing command: ${buildCommand}`, "info");
469
+ return runCommand(buildCommand, {
470
+ maxBuffer: 1024 * 1024 * 10
471
+ });
472
+ }
473
+ async function findPhysicalDeviceAppPath() {
474
+ const DERIVED_DATA_DIR = path.join(os.homedir(), "Library/Developer/Xcode/DerivedData");
475
+ let APP_PATH = "";
476
+ try {
477
+ APP_PATH = execSync(`find "${DERIVED_DATA_DIR}" -name "${PROJECT_NAME}.app" -path "*/Build/Products/Debug-iphoneos/*" -not -path "*/Index.noindex/*" -type d | head -n 1` // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - DERIVED_DATA_DIR and PROJECT_NAME are internal iOS build paths.
478
+ ).toString().trim();
479
+ } catch {
480
+ progress.log("Primary app path search failed for physical device, trying fallback...", "warning");
481
+ }
482
+ if (!APP_PATH) {
483
+ try {
484
+ APP_PATH = execSync(`find "${DERIVED_DATA_DIR}" -path "*${PROJECT_NAME}-Build/Build/Products/Debug-iphoneos/${PROJECT_NAME}.app" -type d | head -n 1` // nosemgrep: javascript.lang.security.detect-child-process.detect-child-process - DERIVED_DATA_DIR and PROJECT_NAME are internal iOS build paths.
485
+ ).toString().trim();
486
+ } catch {
487
+ progress.log("Fallback app path search also failed", "warning");
488
+ }
489
+ }
490
+ if (!APP_PATH) throw new Error("No .app file found for physical device. Check if build completed successfully.");
491
+ progress.log(`Found app bundle: ${APP_PATH}`, "success");
492
+ return APP_PATH;
493
+ }
494
+ async function installAndLaunchOnPhysicalDevice(APP_PATH, device) {
495
+ progress.start("install");
496
+ try {
497
+ progress.log(`Installing app on physical device: ${device.name}`, "info");
498
+ try {
499
+ await runCommand(`xcrun devicectl device install app --device ${device.udid} "${APP_PATH}"`);
500
+ progress.log("App installed successfully using devicectl", "success");
501
+ } catch (devicectlError) {
502
+ throw new Error(`devicectl installation failed: ${devicectlError.message}`);
503
+ }
504
+ progress.complete("install");
505
+ progress.start("launch");
506
+ try {
507
+ await runCommand(`xcrun devicectl device process launch --device ${device.udid} "${APP_BUNDLE_ID}"`);
508
+ progress.log("App launched successfully using devicectl", "success");
509
+ } catch (launchError) {
510
+ progress.log("App installation completed, but launch failed. You can manually launch the app on your device.", "warning");
511
+ progress.log(`To launch manually, look for the app with bundle ID: ${APP_BUNDLE_ID}`, "info");
512
+ }
513
+ progress.complete("launch");
514
+ } catch (error) {
515
+ const currentStep = progress.currentStep.id;
516
+ progress.fail(currentStep, error.message);
517
+ progress.printTreeContent("Physical Device Installation Failed", ["Installation failed. Common solutions:", {
518
+ text: "Ensure device is connected and unlocked",
519
+ indent: 1,
520
+ prefix: "├─ ",
521
+ color: "yellow"
522
+ }, {
523
+ text: "Trust the development certificate on your device",
524
+ indent: 1,
525
+ prefix: "├─ ",
526
+ color: "yellow"
527
+ }, {
528
+ text: "Check that device is in developer mode",
529
+ indent: 1,
530
+ prefix: "├─ ",
531
+ color: "yellow"
532
+ }, {
533
+ text: "Verify app bundle is valid",
534
+ indent: 1,
535
+ prefix: "└─ ",
536
+ color: "yellow"
537
+ }, "", "Device Details:", {
538
+ text: `Device: ${device.name}`,
539
+ indent: 1,
540
+ prefix: "├─ ",
541
+ color: "gray"
542
+ }, {
543
+ text: `UDID: ${device.udid}`,
544
+ indent: 1,
545
+ prefix: "├─ ",
546
+ color: "gray"
547
+ }, {
548
+ text: `App Bundle: ${APP_PATH}`,
549
+ indent: 1,
550
+ prefix: "└─ ",
551
+ color: "gray"
552
+ }]);
553
+ throw error;
554
+ }
555
+ }
556
+
557
+ // ─── Launch simulator ─────────────────────────────────────────────────────
558
+
559
+ async function launchIOSSimulator(simulatorName) {
560
+ progress.start("launchSimulator");
561
+ try {
562
+ progress.log("Launching iOS Simulator...");
563
+ const simulatorsJson = JSON.parse(execSync("xcrun simctl list devices -j").toString());
564
+ let foundSimulator = null,
565
+ foundSimulatorId = null,
566
+ isBooted = false;
567
+ Object.entries(simulatorsJson.devices).forEach(([, devices]) => {
568
+ devices.forEach(device => {
569
+ if (device.name === simulatorName) {
570
+ foundSimulator = device;
571
+ foundSimulatorId = device.udid;
572
+ isBooted = device.state === "Booted";
573
+ }
574
+ });
575
+ });
576
+ if (!foundSimulator) {
577
+ console.log(`Configured simulator "${simulatorName}" not found.`);
578
+ return;
579
+ }
580
+ if (!isBooted) {
581
+ progress.log(`Attempting to boot simulator: ${simulatorName} (${foundSimulatorId})`, "warning");
582
+ if (foundSimulator.state === "Shutdown") {
583
+ progress.log(`Simulator is shutdown, attempting to boot...`, "info");
584
+ try {
585
+ await runCommand(`xcrun simctl boot ${foundSimulatorId}`);
586
+ progress.log(`Successfully booted simulator`, "success");
587
+ } catch (bootError) {
588
+ progress.log(`Boot failed: ${bootError.message}`, "error");
589
+ progress.log(`Looking for any booted simulator as fallback...`, "info");
590
+ const fallbackUUID = await getBootedSimulatorUUID(simulatorName);
591
+ if (fallbackUUID) {
592
+ progress.log(`Found fallback booted simulator: ${fallbackUUID}`, "success");
593
+ foundSimulatorId = fallbackUUID;
594
+ } else throw new Error(`Failed to boot simulator and no fallback available: ${bootError.message}`);
595
+ }
596
+ } else {
597
+ progress.log(`Simulator state is '${foundSimulator.state}', not 'Shutdown'. Checking if we can use it...`, "warning");
598
+ if (foundSimulator.state === "Booted") progress.log(`Wait, simulator is actually booted! Using it.`, "success");else throw new Error(`Simulator is in unexpected state: ${foundSimulator.state}`);
599
+ }
600
+ } else {
601
+ progress.log(`Simulator ${simulatorName} is already booted`, "success");
602
+ }
603
+ progress.log("Opening Simulator.app...");
604
+ await runCommand("open -a Simulator");
605
+ await new Promise(resolve => setTimeout(resolve, 1000));
606
+ await runCommand("osascript -e 'tell application \"Simulator\" to activate'");
607
+ progress.log("iOS Simulator launched successfully.", "success");
608
+ progress.complete("launchSimulator");
609
+ } catch (error) {
610
+ progress.fail("launchSimulator", error.message);
611
+ progress.printTreeContent("Simulator Troubleshooting", ["iOS Simulator failed to launch. Common solutions:", {
612
+ text: "Delete and recreate the simulator in Xcode",
613
+ indent: 1,
614
+ prefix: "├─ ",
615
+ color: "yellow"
616
+ }, {
617
+ text: "Reset simulator content: Device > Erase All Content and Settings",
618
+ indent: 1,
619
+ prefix: "├─ ",
620
+ color: "yellow"
621
+ }, {
622
+ text: "Check available simulators: xcrun simctl list devices",
623
+ indent: 1,
624
+ prefix: "├─ ",
625
+ color: "yellow"
626
+ }, {
627
+ text: "Restart Xcode and Simulator app",
628
+ indent: 1,
629
+ prefix: "└─ ",
630
+ color: "yellow"
631
+ }, "", "Error Details:", {
632
+ text: `Simulator: ${simulatorName}`,
633
+ indent: 1,
634
+ prefix: "├─ ",
635
+ color: "gray"
636
+ }, {
637
+ text: `Error: ${error.message}`,
638
+ indent: 1,
639
+ prefix: "└─ ",
640
+ color: "red"
641
+ }]);
642
+ throw error;
643
+ }
644
+ }
645
+ return {
646
+ cleanBuildArtifacts,
647
+ buildXcodeProject,
648
+ buildProjectForTesting,
649
+ findAppPath,
650
+ moveAppToBuildOutput,
651
+ installAndLaunchApp,
652
+ detectPhysicalDevices,
653
+ buildProjectForPhysicalDevice,
654
+ findPhysicalDeviceAppPath,
655
+ installAndLaunchOnPhysicalDevice,
656
+ launchIOSSimulator,
657
+ getBootedSimulatorUUID
658
+ };
659
+ };