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,19 +1,296 @@
1
- "use strict";const fs=require("fs");const path=require("path");const{buildAndroidAAB}=require("../renameAndroidProject.js");const DEFAULT_DEPLOYMENT_PATH="./deployment";const DEFAULT_OLD_PROJECT_NAME="androidProject";const DEFAULT_OVERWRITE_EXISTING=true;function createBuildPhase(ctx){const{configPath,pwd,progress,ANDROID_PACKAGE}=ctx;async function detectPhysicalDevice(ADB_PATH){try{progress.log("Detecting physical devices...","info");// nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - ADB_PATH is derived from androidConfig.sdkPath, a trusted internal config value.
2
- const devices=ctx.runCommand(`${ADB_PATH} devices -l`);const lines=devices.split("\n").filter(line=>line.trim()&&!line.includes("List of devices"));for(const line of lines){const parts=line.trim().split(/\s+/);if(parts.length>=2&&parts[1]==="device"){const deviceId=parts[0];if(!deviceId.startsWith("emulator-")){try{// nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - deviceId comes from adb devices output, not user input.
3
- const qemuCheck=ctx.runCommand(`${ADB_PATH} -s ${deviceId} shell getprop ro.kernel.qemu`);if(!qemuCheck.trim()){progress.log(`Physical device detected: ${deviceId}`,"success");let deviceModel="Unknown Device";try{// nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - deviceId comes from adb devices output, not user input.
4
- const model=ctx.runCommand(`${ADB_PATH} -s ${deviceId} shell getprop ro.product.model`);deviceModel=model.trim()||deviceModel;}catch(e){// Ignore model detection errors
5
- }return{id:deviceId,model:deviceModel};}}catch(error){continue;}}}}progress.log("No physical devices detected","info");return null;}catch(error){progress.log("Error detecting physical devices: "+error.message,"error");return null;}}async function testPhysicalDeviceInstallation(ADB_PATH,deviceId){try{progress.log(`Testing installation capability on device ${deviceId}...`,"info");// nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - deviceId comes from adb devices output, not user input.
6
- const connectionTest=ctx.runCommand(`${ADB_PATH} -s ${deviceId} shell echo "test"`);if(!connectionTest.includes("test")){progress.log("Device connection test failed","error");return false;}try{// nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - deviceId comes from adb devices output, not user input.
7
- const developerOptions=ctx.runCommand(`${ADB_PATH} -s ${deviceId} shell settings get global development_settings_enabled`);if(developerOptions.trim()!=="1"){progress.log("Developer options not enabled on device","warning");return false;}}catch(error){progress.log("Cannot verify developer options status","warning");}try{// nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - deviceId comes from adb devices output, not user input.
8
- const usbDebugging=ctx.runCommand(`${ADB_PATH} -s ${deviceId} shell settings get global adb_enabled`);if(usbDebugging.trim()!=="1"){progress.log("USB debugging not enabled on device","warning");return false;}}catch(error){progress.log("Cannot verify USB debugging status","warning");}try{// nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - deviceId comes from adb devices output, not user input.
9
- ctx.runCommand(`${ADB_PATH} -s ${deviceId} shell pm list packages -3 | head -1`);}catch(error){progress.log("Cannot access package manager on device","error");return false;}progress.log(`Device ${deviceId} is ready for installation`,"success");return true;}catch(error){progress.log(`Installation capability test failed: ${error.message}`,"error");return false;}}async function checkEmulator(ADB_PATH){try{// nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - ADB_PATH is derived from androidConfig.sdkPath, a trusted internal config value.
10
- const devices=ctx.runCommand(`${ADB_PATH} devices`);return devices.includes("emulator");}catch(error){progress.log("Error checking emulator status: "+error.message,"error");return false;}}async function startEmulator(EMULATOR_PATH,androidConfig){progress.log(`Starting emulator: ${androidConfig.emulatorName}...`,"info");// nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - EMULATOR_PATH is derived from androidConfig.sdkPath, a trusted internal config value.
11
- return ctx.runInteractiveCommand(EMULATOR_PATH,["-avd",androidConfig.emulatorName,"-read-only"],{}).then(()=>{progress.log("Emulator started successfully","success");}).catch(error=>{progress.log("Error starting emulator: "+error.message,"error");throw error;});}async function handleEmulatorSetup(ADB_PATH,EMULATOR_PATH,androidConfig){progress.log("Setting up emulator...","info");const emulatorRunning=await checkEmulator(ADB_PATH);if(!emulatorRunning){progress.log("No emulator running, attempting to start one...","info");await startEmulator(EMULATOR_PATH,androidConfig);await new Promise(resolve=>setTimeout(resolve,5000));}else{progress.log("Emulator already running","success");}return{type:"emulator",name:androidConfig.emulatorName};}async function buildApp(ADB_PATH,androidConfig,buildOptimisation,targetDevice=null){progress.log("Building and installing app...","info");try{// nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - pwd and configPath are internal resolved paths, not user input.
12
- let buildCommand=`cd ${pwd}/androidProject && ./gradlew generateWebViewConfig -PconfigPath=${configPath} -PbuildOptimisation=${buildOptimisation} && ./gradlew clean installDebug -PconfigPath=${configPath} --quiet --console=rich`;if(targetDevice&&targetDevice.type==="physical"){buildCommand=buildCommand.replace("installDebug",`installDebug -Pandroid.injected.target.device=${targetDevice.id}`);}await ctx.runInteractiveCommand("sh",["-c",buildCommand],{"BUILD SUCCESSFUL":""});progress.log("App build and installation completed successfully!","success");}catch(error){throw new Error("Error building/installing app: "+error.message);}}async function launchApp(ADB_PATH,buildType="debug",targetDevice=null){if(!targetDevice){progress.log("No target device specified, skipping launch","warning");return;}if(targetDevice.type==="physical"){progress.log(`App installed on physical device: ${targetDevice.model}`,"success");progress.log("Manual launch required - check your device to open the app","info");return;}try{progress.log("Launching app on emulator...","info");const packageName=`${ANDROID_PACKAGE}${buildType==="debug"?".debug":""}`;// nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - ADB_PATH is derived from androidConfig.sdkPath, a trusted internal config value.
13
- const launchCommand=`${ADB_PATH} shell monkey -p ${packageName} 1`;await ctx.runInteractiveCommand("sh",["-c",launchCommand],{});progress.log("App launched successfully on emulator!","success");}catch(error){progress.log(`Warning: Could not auto-launch app: ${error.message}`,"warning");progress.log("App was installed successfully, but auto-launch failed","info");}}async function createAABConfig(androidConfig){const DEFAULT_PROJECT_PATH=`${pwd}/androidProject`;const aabConfig={projectPath:androidConfig.projectPath||DEFAULT_PROJECT_PATH,deploymentPath:DEFAULT_DEPLOYMENT_PATH,oldProjectName:DEFAULT_OLD_PROJECT_NAME,overwriteExisting:androidConfig.overwriteExisting!==undefined?androidConfig.overwriteExisting:DEFAULT_OVERWRITE_EXISTING,newProjectName:androidConfig.newProjectName||androidConfig.appName||androidConfig.packageName?.split(".").pop()||"catalystapp",packageName:androidConfig.packageName||null,createSignedAAB:true,outputPath:androidConfig.outputPath||`${process.cwd()}/build-output`};if(androidConfig.keystoreConfig){aabConfig.keystoreConfig=androidConfig.keystoreConfig;}else if(androidConfig.keystore){aabConfig.keystoreConfig={keyAlias:androidConfig.keystore.alias||"release",storePassword:androidConfig.keystore.storePassword,keyPassword:androidConfig.keystore.keyPassword,validityYears:25,organizationInfo:{companyName:androidConfig.keystore.organizationName||"YourCompany",city:androidConfig.keystore.city||"YourCity",state:androidConfig.keystore.state||"YourState",countryCode:androidConfig.keystore.countryCode||"US"}};}progress.log(`AAB Configuration:`,"info");progress.log(` Project Path: ${aabConfig.projectPath} ${aabConfig.projectPath===DEFAULT_PROJECT_PATH?"(default)":"(configured)"}`,"info");progress.log(` Deployment Path: ${aabConfig.deploymentPath} ${aabConfig.deploymentPath===DEFAULT_DEPLOYMENT_PATH?"(default)":"(configured)"}`,"info");progress.log(` Old Project Name: ${aabConfig.oldProjectName} ${aabConfig.oldProjectName===DEFAULT_OLD_PROJECT_NAME?"(default)":"(configured)"}`,"info");progress.log(` New Project Name: ${aabConfig.newProjectName}`,"info");progress.log(` Overwrite Existing: ${aabConfig.overwriteExisting} ${aabConfig.overwriteExisting===DEFAULT_OVERWRITE_EXISTING?"(default)":"(configured)"}`,"info");progress.log(` Output Path: ${aabConfig.outputPath}`,"info");return aabConfig;}async function moveApkToOutputPath(buildType,BUILD_OUTPUT_PATH,appName){try{if(!BUILD_OUTPUT_PATH){progress.log("BUILD_OUTPUT_PATH not set, skipping APK move","warning");return null;}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,"-");let destinationApkFileName="";if(appName){destinationApkFileName=buildType==="release"?`${appName}-${currentTime}.release.apk`:`${appName}-${currentTime}.debug.apk`;}else{destinationApkFileName=buildType==="release"?`app-${currentTime}.release.apk`:`app-${currentTime}.debug.apk`;}const sourceApkFileName=buildType==="release"?`app.apk`:`app-debug.apk`;const sourceApkPath=path.join(pwd,// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - pwd is an internal resolved path.
14
- "androidProject","app","build","outputs","apk",buildType,// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - buildType is "debug" or "release" from config.
15
- sourceApkFileName);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.
16
- "native","android",currentDate,buildType// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - buildType is "debug" or "release" from config.
17
- );const destinationApkPath=path.join(destinationDir,destinationApkFileName);// nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - destinationDir and destinationApkFileName are derived from internal config values, not user input.
18
- if(!fs.existsSync(sourceApkPath)){progress.log(`APK not found at source path: ${sourceApkPath}`,"warning");return null;}if(!fs.existsSync(destinationDir)){fs.mkdirSync(destinationDir,{recursive:true});}fs.copyFileSync(sourceApkPath,destinationApkPath);return destinationApkPath;}catch(error){progress.log(`Error moving APK: ${error.message}`,"error");return null;}}async function buildSignedAAB(androidConfig){progress.log("Building signed AAB for release...","info");try{progress.log("Generating webview configuration for release...","info");try{// nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - pwd and configPath are internal resolved paths, not user input.
19
- const generateConfigCommand=`cd ${pwd}/androidProject && ./gradlew generateWebViewConfig -PconfigPath=${configPath}`;await ctx.runInteractiveCommand("sh",["-c",generateConfigCommand],{"BUILD SUCCESSFUL":""});progress.log("Webview config generated successfully","success");}catch(configError){progress.log(`Warning: Webview config generation failed: ${configError.message}`,"warning");throw new Error("Webview config generation is required for release builds");}const aabConfig=await createAABConfig(androidConfig);await buildAndroidAAB(aabConfig);progress.log("Signed AAB build completed successfully!","success");}catch(error){throw new Error("Error building signed AAB: "+error.message);}}return{detectPhysicalDevice,testPhysicalDeviceInstallation,checkEmulator,handleEmulatorSetup,startEmulator,buildApp,launchApp,createAABConfig,moveApkToOutputPath,buildSignedAAB};}module.exports=createBuildPhase;
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+ const {
6
+ buildAndroidAAB
7
+ } = require("../renameAndroidProject.js");
8
+ const DEFAULT_DEPLOYMENT_PATH = "./deployment";
9
+ const DEFAULT_OLD_PROJECT_NAME = "androidProject";
10
+ const DEFAULT_OVERWRITE_EXISTING = true;
11
+ function createBuildPhase(ctx) {
12
+ const {
13
+ configPath,
14
+ pwd,
15
+ progress,
16
+ ANDROID_PACKAGE
17
+ } = ctx;
18
+ async function detectPhysicalDevice(ADB_PATH) {
19
+ try {
20
+ progress.log("Detecting physical devices...", "info");
21
+ // nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - ADB_PATH is derived from androidConfig.sdkPath, a trusted internal config value.
22
+ const devices = ctx.runCommand(`${ADB_PATH} devices -l`);
23
+ const lines = devices.split("\n").filter(line => line.trim() && !line.includes("List of devices"));
24
+ for (const line of lines) {
25
+ const parts = line.trim().split(/\s+/);
26
+ if (parts.length >= 2 && parts[1] === "device") {
27
+ const deviceId = parts[0];
28
+ if (!deviceId.startsWith("emulator-")) {
29
+ try {
30
+ // nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - deviceId comes from adb devices output, not user input.
31
+ const qemuCheck = ctx.runCommand(`${ADB_PATH} -s ${deviceId} shell getprop ro.kernel.qemu`);
32
+ if (!qemuCheck.trim()) {
33
+ progress.log(`Physical device detected: ${deviceId}`, "success");
34
+ let deviceModel = "Unknown Device";
35
+ try {
36
+ // nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - deviceId comes from adb devices output, not user input.
37
+ const model = ctx.runCommand(`${ADB_PATH} -s ${deviceId} shell getprop ro.product.model`);
38
+ deviceModel = model.trim() || deviceModel;
39
+ } catch (e) {
40
+ // Ignore model detection errors
41
+ }
42
+ return {
43
+ id: deviceId,
44
+ model: deviceModel
45
+ };
46
+ }
47
+ } catch (error) {
48
+ continue;
49
+ }
50
+ }
51
+ }
52
+ }
53
+ progress.log("No physical devices detected", "info");
54
+ return null;
55
+ } catch (error) {
56
+ progress.log("Error detecting physical devices: " + error.message, "error");
57
+ return null;
58
+ }
59
+ }
60
+ async function testPhysicalDeviceInstallation(ADB_PATH, deviceId) {
61
+ try {
62
+ progress.log(`Testing installation capability on device ${deviceId}...`, "info");
63
+
64
+ // nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - deviceId comes from adb devices output, not user input.
65
+ const connectionTest = ctx.runCommand(`${ADB_PATH} -s ${deviceId} shell echo "test"`);
66
+ if (!connectionTest.includes("test")) {
67
+ progress.log("Device connection test failed", "error");
68
+ return false;
69
+ }
70
+ try {
71
+ // nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - deviceId comes from adb devices output, not user input.
72
+ const developerOptions = ctx.runCommand(`${ADB_PATH} -s ${deviceId} shell settings get global development_settings_enabled`);
73
+ if (developerOptions.trim() !== "1") {
74
+ progress.log("Developer options not enabled on device", "warning");
75
+ return false;
76
+ }
77
+ } catch (error) {
78
+ progress.log("Cannot verify developer options status", "warning");
79
+ }
80
+ try {
81
+ // nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - deviceId comes from adb devices output, not user input.
82
+ const usbDebugging = ctx.runCommand(`${ADB_PATH} -s ${deviceId} shell settings get global adb_enabled`);
83
+ if (usbDebugging.trim() !== "1") {
84
+ progress.log("USB debugging not enabled on device", "warning");
85
+ return false;
86
+ }
87
+ } catch (error) {
88
+ progress.log("Cannot verify USB debugging status", "warning");
89
+ }
90
+ try {
91
+ // nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - deviceId comes from adb devices output, not user input.
92
+ ctx.runCommand(`${ADB_PATH} -s ${deviceId} shell pm list packages -3 | head -1`);
93
+ } catch (error) {
94
+ progress.log("Cannot access package manager on device", "error");
95
+ return false;
96
+ }
97
+ progress.log(`Device ${deviceId} is ready for installation`, "success");
98
+ return true;
99
+ } catch (error) {
100
+ progress.log(`Installation capability test failed: ${error.message}`, "error");
101
+ return false;
102
+ }
103
+ }
104
+ async function checkEmulator(ADB_PATH) {
105
+ try {
106
+ // nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - ADB_PATH is derived from androidConfig.sdkPath, a trusted internal config value.
107
+ const devices = ctx.runCommand(`${ADB_PATH} devices`);
108
+ return devices.includes("emulator");
109
+ } catch (error) {
110
+ progress.log("Error checking emulator status: " + error.message, "error");
111
+ return false;
112
+ }
113
+ }
114
+ async function startEmulator(EMULATOR_PATH, androidConfig) {
115
+ progress.log(`Starting emulator: ${androidConfig.emulatorName}...`, "info");
116
+ // nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - EMULATOR_PATH is derived from androidConfig.sdkPath, a trusted internal config value.
117
+ return ctx.runInteractiveCommand(EMULATOR_PATH, ["-avd", androidConfig.emulatorName, "-read-only"], {}).then(() => {
118
+ progress.log("Emulator started successfully", "success");
119
+ }).catch(error => {
120
+ progress.log("Error starting emulator: " + error.message, "error");
121
+ throw error;
122
+ });
123
+ }
124
+ async function handleEmulatorSetup(ADB_PATH, EMULATOR_PATH, androidConfig) {
125
+ progress.log("Setting up emulator...", "info");
126
+ const emulatorRunning = await checkEmulator(ADB_PATH);
127
+ if (!emulatorRunning) {
128
+ progress.log("No emulator running, attempting to start one...", "info");
129
+ await startEmulator(EMULATOR_PATH, androidConfig);
130
+ await new Promise(resolve => setTimeout(resolve, 5000));
131
+ } else {
132
+ progress.log("Emulator already running", "success");
133
+ }
134
+ return {
135
+ type: "emulator",
136
+ name: androidConfig.emulatorName
137
+ };
138
+ }
139
+ async function buildApp(ADB_PATH, androidConfig, buildOptimisation, targetDevice = null) {
140
+ progress.log("Building and installing app...", "info");
141
+ try {
142
+ // nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - pwd and configPath are internal resolved paths, not user input.
143
+ let buildCommand = `cd ${pwd}/androidProject && ./gradlew generateWebViewConfig -PconfigPath=${configPath} -PbuildOptimisation=${buildOptimisation} && ./gradlew clean installDebug -PconfigPath=${configPath} --quiet --console=rich`;
144
+ if (targetDevice && targetDevice.type === "physical") {
145
+ buildCommand = buildCommand.replace("installDebug", `installDebug -Pandroid.injected.target.device=${targetDevice.id}`);
146
+ }
147
+ await ctx.runInteractiveCommand("sh", ["-c", buildCommand], {
148
+ "BUILD SUCCESSFUL": ""
149
+ });
150
+ progress.log("App build and installation completed successfully!", "success");
151
+ } catch (error) {
152
+ throw new Error("Error building/installing app: " + error.message);
153
+ }
154
+ }
155
+ async function launchApp(ADB_PATH, buildType = "debug", targetDevice = null) {
156
+ if (!targetDevice) {
157
+ progress.log("No target device specified, skipping launch", "warning");
158
+ return;
159
+ }
160
+ if (targetDevice.type === "physical") {
161
+ progress.log(`App installed on physical device: ${targetDevice.model}`, "success");
162
+ progress.log("Manual launch required - check your device to open the app", "info");
163
+ return;
164
+ }
165
+ try {
166
+ progress.log("Launching app on emulator...", "info");
167
+ const packageName = `${ANDROID_PACKAGE}${buildType === "debug" ? ".debug" : ""}`;
168
+ // nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - ADB_PATH is derived from androidConfig.sdkPath, a trusted internal config value.
169
+ const launchCommand = `${ADB_PATH} shell monkey -p ${packageName} 1`;
170
+ await ctx.runInteractiveCommand("sh", ["-c", launchCommand], {});
171
+ progress.log("App launched successfully on emulator!", "success");
172
+ } catch (error) {
173
+ progress.log(`Warning: Could not auto-launch app: ${error.message}`, "warning");
174
+ progress.log("App was installed successfully, but auto-launch failed", "info");
175
+ }
176
+ }
177
+ async function createAABConfig(androidConfig) {
178
+ const DEFAULT_PROJECT_PATH = `${pwd}/androidProject`;
179
+ const aabConfig = {
180
+ projectPath: androidConfig.projectPath || DEFAULT_PROJECT_PATH,
181
+ deploymentPath: DEFAULT_DEPLOYMENT_PATH,
182
+ oldProjectName: DEFAULT_OLD_PROJECT_NAME,
183
+ overwriteExisting: androidConfig.overwriteExisting !== undefined ? androidConfig.overwriteExisting : DEFAULT_OVERWRITE_EXISTING,
184
+ newProjectName: androidConfig.newProjectName || androidConfig.appName || androidConfig.packageName?.split(".").pop() || "catalystapp",
185
+ packageName: androidConfig.packageName || null,
186
+ createSignedAAB: true,
187
+ outputPath: androidConfig.outputPath || `${process.cwd()}/build-output`
188
+ };
189
+ if (androidConfig.keystoreConfig) {
190
+ aabConfig.keystoreConfig = androidConfig.keystoreConfig;
191
+ } else if (androidConfig.keystore) {
192
+ aabConfig.keystoreConfig = {
193
+ keyAlias: androidConfig.keystore.alias || "release",
194
+ storePassword: androidConfig.keystore.storePassword,
195
+ keyPassword: androidConfig.keystore.keyPassword,
196
+ validityYears: 25,
197
+ organizationInfo: {
198
+ companyName: androidConfig.keystore.organizationName || "YourCompany",
199
+ city: androidConfig.keystore.city || "YourCity",
200
+ state: androidConfig.keystore.state || "YourState",
201
+ countryCode: androidConfig.keystore.countryCode || "US"
202
+ }
203
+ };
204
+ }
205
+ progress.log(`AAB Configuration:`, "info");
206
+ progress.log(` Project Path: ${aabConfig.projectPath} ${aabConfig.projectPath === DEFAULT_PROJECT_PATH ? "(default)" : "(configured)"}`, "info");
207
+ progress.log(` Deployment Path: ${aabConfig.deploymentPath} ${aabConfig.deploymentPath === DEFAULT_DEPLOYMENT_PATH ? "(default)" : "(configured)"}`, "info");
208
+ progress.log(` Old Project Name: ${aabConfig.oldProjectName} ${aabConfig.oldProjectName === DEFAULT_OLD_PROJECT_NAME ? "(default)" : "(configured)"}`, "info");
209
+ progress.log(` New Project Name: ${aabConfig.newProjectName}`, "info");
210
+ progress.log(` Overwrite Existing: ${aabConfig.overwriteExisting} ${aabConfig.overwriteExisting === DEFAULT_OVERWRITE_EXISTING ? "(default)" : "(configured)"}`, "info");
211
+ progress.log(` Output Path: ${aabConfig.outputPath}`, "info");
212
+ return aabConfig;
213
+ }
214
+ async function moveApkToOutputPath(buildType, BUILD_OUTPUT_PATH, appName) {
215
+ try {
216
+ if (!BUILD_OUTPUT_PATH) {
217
+ progress.log("BUILD_OUTPUT_PATH not set, skipping APK move", "warning");
218
+ return null;
219
+ }
220
+ const currentDate = new Date().toLocaleDateString("en-GB").replace(/\//g, "-");
221
+ const currentTime = new Date().toLocaleTimeString("en-US", {
222
+ hour12: true,
223
+ hour: "2-digit",
224
+ minute: "2-digit",
225
+ second: "2-digit"
226
+ }).replace(/:/g, "-");
227
+ let destinationApkFileName = "";
228
+ if (appName) {
229
+ destinationApkFileName = buildType === "release" ? `${appName}-${currentTime}.release.apk` : `${appName}-${currentTime}.debug.apk`;
230
+ } else {
231
+ destinationApkFileName = buildType === "release" ? `app-${currentTime}.release.apk` : `app-${currentTime}.debug.apk`;
232
+ }
233
+ const sourceApkFileName = buildType === "release" ? `app.apk` : `app-debug.apk`;
234
+ const sourceApkPath = path.join(pwd,
235
+ // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - pwd is an internal resolved path.
236
+ "androidProject", "app", "build", "outputs", "apk", buildType,
237
+ // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - buildType is "debug" or "release" from config.
238
+ sourceApkFileName);
239
+ const destinationDir = path.join(process.cwd(), BUILD_OUTPUT_PATH,
240
+ // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - BUILD_OUTPUT_PATH is trusted application config.
241
+ "native", "android", currentDate, buildType // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - buildType is "debug" or "release" from config.
242
+ );
243
+ const destinationApkPath = path.join(destinationDir, destinationApkFileName); // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - destinationDir and destinationApkFileName are derived from internal config values, not user input.
244
+
245
+ if (!fs.existsSync(sourceApkPath)) {
246
+ progress.log(`APK not found at source path: ${sourceApkPath}`, "warning");
247
+ return null;
248
+ }
249
+ if (!fs.existsSync(destinationDir)) {
250
+ fs.mkdirSync(destinationDir, {
251
+ recursive: true
252
+ });
253
+ }
254
+ fs.copyFileSync(sourceApkPath, destinationApkPath);
255
+ return destinationApkPath;
256
+ } catch (error) {
257
+ progress.log(`Error moving APK: ${error.message}`, "error");
258
+ return null;
259
+ }
260
+ }
261
+ async function buildSignedAAB(androidConfig) {
262
+ progress.log("Building signed AAB for release...", "info");
263
+ try {
264
+ progress.log("Generating webview configuration for release...", "info");
265
+ try {
266
+ // nosemgrep: javascript.lang.security.audit.dangerous-spawn-shell-command.dangerous-spawn-shell-command - pwd and configPath are internal resolved paths, not user input.
267
+ const generateConfigCommand = `cd ${pwd}/androidProject && ./gradlew generateWebViewConfig -PconfigPath=${configPath}`;
268
+ await ctx.runInteractiveCommand("sh", ["-c", generateConfigCommand], {
269
+ "BUILD SUCCESSFUL": ""
270
+ });
271
+ progress.log("Webview config generated successfully", "success");
272
+ } catch (configError) {
273
+ progress.log(`Warning: Webview config generation failed: ${configError.message}`, "warning");
274
+ throw new Error("Webview config generation is required for release builds");
275
+ }
276
+ const aabConfig = await createAABConfig(androidConfig);
277
+ await buildAndroidAAB(aabConfig);
278
+ progress.log("Signed AAB build completed successfully!", "success");
279
+ } catch (error) {
280
+ throw new Error("Error building signed AAB: " + error.message);
281
+ }
282
+ }
283
+ return {
284
+ detectPhysicalDevice,
285
+ testPhysicalDeviceInstallation,
286
+ checkEmulator,
287
+ handleEmulatorSetup,
288
+ startEmulator,
289
+ buildApp,
290
+ launchApp,
291
+ createAABConfig,
292
+ moveApkToOutputPath,
293
+ buildSignedAAB
294
+ };
295
+ }
296
+ module.exports = createBuildPhase;
@@ -1 +1,87 @@
1
- "use strict";const fs=require("fs");function createConfigPhase(ctx){const{configPath,progress}=ctx;async function initializeConfig(){const configFile=fs.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");}const buildType=WEBVIEW_CONFIG.android.buildType||"debug";progress.log(`Build Type: ${buildType}`,"info");if(buildType==="release"){progress.log("Release build detected - AAB will be generated","info");}return{WEBVIEW_CONFIG,BUILD_OUTPUT_PATH};}function validateAndroidTools(androidConfig){const ANDROID_SDK=androidConfig.sdkPath;const ADB_PATH=`${ANDROID_SDK}/platform-tools/adb`;const EMULATOR_PATH=`${ANDROID_SDK}/emulator/emulator`;progress.log("Validating Android tools...","info");if(!ANDROID_SDK){throw new Error("Android SDK path is not configured");}if(!fs.existsSync(ANDROID_SDK)){throw new Error(`Android SDK path does not exist: ${ANDROID_SDK}`);}if(!fs.existsSync(ADB_PATH)){throw new Error(`ADB not found at: ${ADB_PATH}`);}try{ctx.runCommand(`${ADB_PATH} version`);progress.log("ADB validation successful","success");}catch(error){throw new Error(`ADB is not working properly: ${error.message}`);}const buildType=androidConfig.buildType||"debug";if(buildType!=="release"){if(!fs.existsSync(EMULATOR_PATH)){throw new Error(`Emulator not found at: ${EMULATOR_PATH}`);}try{ctx.runCommand(`${EMULATOR_PATH} -version`);progress.log("Emulator validation successful","success");}catch(error){throw new Error(`Emulator is not working properly: ${error.message}`);}try{const avdOutput=ctx.runCommand(`${EMULATOR_PATH} -list-avds`);if(!avdOutput.includes(androidConfig.emulatorName)){throw new Error(`Specified emulator "${androidConfig.emulatorName}" not found in available AVDs`);}progress.log(`Emulator "${androidConfig.emulatorName}" exists`,"success");}catch(error){throw new Error(`Error checking emulator AVD: ${error.message}`);}}else{progress.log("Skipping emulator validation for release build","info");}progress.log("Android tools validation completed successfully!","success");return{ANDROID_SDK,ADB_PATH,EMULATOR_PATH};}return{initializeConfig,validateAndroidTools};}module.exports=createConfigPhase;
1
+ "use strict";
2
+
3
+ const fs = require("fs");
4
+ function createConfigPhase(ctx) {
5
+ const {
6
+ configPath,
7
+ progress
8
+ } = ctx;
9
+ async function initializeConfig() {
10
+ const configFile = fs.readFileSync(configPath, "utf8");
11
+ const config = JSON.parse(configFile);
12
+ const {
13
+ WEBVIEW_CONFIG,
14
+ BUILD_OUTPUT_PATH
15
+ } = config;
16
+ if (!WEBVIEW_CONFIG || Object.keys(WEBVIEW_CONFIG).length === 0) {
17
+ throw new Error("WebView Config missing in " + configPath);
18
+ }
19
+ if (!WEBVIEW_CONFIG.android) {
20
+ throw new Error("Android config missing in WebView Config");
21
+ }
22
+ const buildType = WEBVIEW_CONFIG.android.buildType || "debug";
23
+ progress.log(`Build Type: ${buildType}`, "info");
24
+ if (buildType === "release") {
25
+ progress.log("Release build detected - AAB will be generated", "info");
26
+ }
27
+ return {
28
+ WEBVIEW_CONFIG,
29
+ BUILD_OUTPUT_PATH
30
+ };
31
+ }
32
+ function validateAndroidTools(androidConfig) {
33
+ const ANDROID_SDK = androidConfig.sdkPath;
34
+ const ADB_PATH = `${ANDROID_SDK}/platform-tools/adb`;
35
+ const EMULATOR_PATH = `${ANDROID_SDK}/emulator/emulator`;
36
+ progress.log("Validating Android tools...", "info");
37
+ if (!ANDROID_SDK) {
38
+ throw new Error("Android SDK path is not configured");
39
+ }
40
+ if (!fs.existsSync(ANDROID_SDK)) {
41
+ throw new Error(`Android SDK path does not exist: ${ANDROID_SDK}`);
42
+ }
43
+ if (!fs.existsSync(ADB_PATH)) {
44
+ throw new Error(`ADB not found at: ${ADB_PATH}`);
45
+ }
46
+ try {
47
+ ctx.runCommand(`${ADB_PATH} version`);
48
+ progress.log("ADB validation successful", "success");
49
+ } catch (error) {
50
+ throw new Error(`ADB is not working properly: ${error.message}`);
51
+ }
52
+ const buildType = androidConfig.buildType || "debug";
53
+ if (buildType !== "release") {
54
+ if (!fs.existsSync(EMULATOR_PATH)) {
55
+ throw new Error(`Emulator not found at: ${EMULATOR_PATH}`);
56
+ }
57
+ try {
58
+ ctx.runCommand(`${EMULATOR_PATH} -version`);
59
+ progress.log("Emulator validation successful", "success");
60
+ } catch (error) {
61
+ throw new Error(`Emulator is not working properly: ${error.message}`);
62
+ }
63
+ try {
64
+ const avdOutput = ctx.runCommand(`${EMULATOR_PATH} -list-avds`);
65
+ if (!avdOutput.includes(androidConfig.emulatorName)) {
66
+ throw new Error(`Specified emulator "${androidConfig.emulatorName}" not found in available AVDs`);
67
+ }
68
+ progress.log(`Emulator "${androidConfig.emulatorName}" exists`, "success");
69
+ } catch (error) {
70
+ throw new Error(`Error checking emulator AVD: ${error.message}`);
71
+ }
72
+ } else {
73
+ progress.log("Skipping emulator validation for release build", "info");
74
+ }
75
+ progress.log("Android tools validation completed successfully!", "success");
76
+ return {
77
+ ANDROID_SDK,
78
+ ADB_PATH,
79
+ EMULATOR_PATH
80
+ };
81
+ }
82
+ return {
83
+ initializeConfig,
84
+ validateAndroidTools
85
+ };
86
+ }
87
+ module.exports = createConfigPhase;