diff --git a/.github/workflows/MarketplaceRelease.yml b/.github/workflows/MarketplaceRelease.yml index 3b2c646a6..946d02fc0 100644 --- a/.github/workflows/MarketplaceRelease.yml +++ b/.github/workflows/MarketplaceRelease.yml @@ -35,7 +35,7 @@ jobs: - name: "Building native widgets and js actions" run: pnpm -r run release - name: "Updating Native Mobile Resources project" - run: pnpm -r run create-modules + run: pnpm run create-modules env: GH_USERNAME: ${{ secrets.GH_USERNAME }} GH_EMAIL: ${{ secrets.GH_EMAIL }} diff --git a/packages/jsActions/mobile-resources-native/CHANGELOG.md b/packages/jsActions/mobile-resources-native/CHANGELOG.md index 767cef935..0b7bf889b 100644 --- a/packages/jsActions/mobile-resources-native/CHANGELOG.md +++ b/packages/jsActions/mobile-resources-native/CHANGELOG.md @@ -6,8 +6,48 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [11.0.3] Native Mobile Resources - 2025-8-21 + - Updated react-native from version 0.75.4 to 0.77.3. +## [2.2.0] BackgroundGradient + +- Updated react-native-linear-gradient to latest version. + +## [5.0.1] BottomSheet + +- Updated react-native-device-info to latest version. + +## [4.2.0] IntroScreen + +### Fixes + +- Initial slide now correctly positioned on mount. + +### Changed + +- Updated react-native-device-info to latest version. + +### Fixed + +- We have fixed defaultProps deprecation warning. + +## [5.0.0] Notifications + +- The widget has been migrated to notifee. + +## [3.1.1] SafeAreaView + +- We fixed the issue where navigation buttons were being overlapped due to improper safe area handling. + +## [2.3.0] Signature + +- Updated react-native-webview from version v13.12.5 to latest + +## [4.3.0] WebView + +- Updated react-native-webview from version v13.12.5 to latest to support react-native 0.77 + ## [10.0.0] Native Mobile Resources - 2025-3-31 - We migrated from react-native-inappbrowser-reborn to @swan-io/react-native-browser to prevent issues for next rn upgrade. diff --git a/packages/jsActions/mobile-resources-native/package.json b/packages/jsActions/mobile-resources-native/package.json index 5ddb99c90..b16f06050 100644 --- a/packages/jsActions/mobile-resources-native/package.json +++ b/packages/jsActions/mobile-resources-native/package.json @@ -1,7 +1,7 @@ { "name": "mobile-resources-native", "moduleName": "Native Mobile Resources", - "version": "10.0.0", + "version": "11.0.3", "license": "Apache-2.0", "copyright": "© Mendix Technology BV 2022. All rights reserved.", "repository": { diff --git a/packages/pluggableWidgets/background-gradient-native/CHANGELOG.md b/packages/pluggableWidgets/background-gradient-native/CHANGELOG.md index 1eb9cc7e1..7fb8521a1 100644 --- a/packages/pluggableWidgets/background-gradient-native/CHANGELOG.md +++ b/packages/pluggableWidgets/background-gradient-native/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [2.2.0] - 2025-8-21 + - Updated react-native-linear-gradient to latest version. ## [2.1.0] - 2024-12-3 diff --git a/packages/pluggableWidgets/bottom-sheet-native/CHANGELOG.md b/packages/pluggableWidgets/bottom-sheet-native/CHANGELOG.md index ab4e97380..4ca8460d9 100644 --- a/packages/pluggableWidgets/bottom-sheet-native/CHANGELOG.md +++ b/packages/pluggableWidgets/bottom-sheet-native/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [5.0.1] - 2025-8-21 + - Updated react-native-device-info to latest version. ## [5.0.0] - 2025-3-31 diff --git a/packages/pluggableWidgets/intro-screen-native/CHANGELOG.md b/packages/pluggableWidgets/intro-screen-native/CHANGELOG.md index ff4a6db94..57c9d4291 100644 --- a/packages/pluggableWidgets/intro-screen-native/CHANGELOG.md +++ b/packages/pluggableWidgets/intro-screen-native/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [4.2.0] - 2025-8-21 + ### Fixes - Initial slide now correctly positioned on mount. diff --git a/packages/pluggableWidgets/notifications-native/CHANGELOG.md b/packages/pluggableWidgets/notifications-native/CHANGELOG.md index 4d6cee63d..cf7f32b21 100644 --- a/packages/pluggableWidgets/notifications-native/CHANGELOG.md +++ b/packages/pluggableWidgets/notifications-native/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [5.0.0] - 2025-8-21 + - The widget has been migrated to notifee. ## [4.1.0] - 2024-12-3 diff --git a/packages/pluggableWidgets/safe-area-view-native/CHANGELOG.md b/packages/pluggableWidgets/safe-area-view-native/CHANGELOG.md index 610ed41ca..6d27564dd 100644 --- a/packages/pluggableWidgets/safe-area-view-native/CHANGELOG.md +++ b/packages/pluggableWidgets/safe-area-view-native/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [3.1.1] - 2025-8-21 + - We fixed the issue where navigation buttons were being overlapped due to improper safe area handling. ## [3.1.0] - 2025-4-25 diff --git a/packages/pluggableWidgets/signature-native/CHANGELOG.md b/packages/pluggableWidgets/signature-native/CHANGELOG.md index e0764ac72..a4d3d588b 100644 --- a/packages/pluggableWidgets/signature-native/CHANGELOG.md +++ b/packages/pluggableWidgets/signature-native/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [2.3.0] - 2025-8-21 + - Updated react-native-webview from version v13.12.5 to latest ## [2.2.0] - 2025-3-31 diff --git a/packages/pluggableWidgets/web-view-native/CHANGELOG.md b/packages/pluggableWidgets/web-view-native/CHANGELOG.md index ec14537e0..258c9a12d 100644 --- a/packages/pluggableWidgets/web-view-native/CHANGELOG.md +++ b/packages/pluggableWidgets/web-view-native/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +## [4.3.0] - 2025-8-21 + - Updated react-native-webview from version v13.12.5 to latest to support react-native 0.77 ## [4.2.0] - 2025-3-31 diff --git a/scripts/release/createNativeModules.js b/scripts/release/createNativeModules.js index 1c464bfe1..88b3af12a 100644 --- a/scripts/release/createNativeModules.js +++ b/scripts/release/createNativeModules.js @@ -17,204 +17,457 @@ const { } = require("./module-automation/commons"); const repoRootPath = join(__dirname, "../../"); -const [moduleFolderNameInRepo, version] = process.env.TAG.split("-v"); +const [moduleFolderNameInRepo, version] = process.env.TAG?.split("-v") || []; + +console.log(`[NATIVE MODULES] Starting native modules creation process`); +console.log(`[NATIVE MODULES] Repository root path: ${repoRootPath}`); +console.log(`[NATIVE MODULES] Module folder name: ${moduleFolderNameInRepo}`); +console.log(`[NATIVE MODULES] Version: ${version}`); +console.log(`[NATIVE MODULES] TAG environment variable: ${process.env.TAG}`); main().catch(e => { - console.error(e); + console.error(`[NATIVE MODULES ERROR] Fatal error in main process:`); + console.error(`[NATIVE MODULES ERROR] ${e.message}`); + console.error(`[NATIVE MODULES ERROR] Stack trace: ${e.stack}`); process.exit(1); }); async function main() { + console.log(`[NATIVE MODULES] Starting native module creation process`); + console.log(`[NATIVE MODULES] TAG environment variable: ${process.env.TAG}`); + console.log(`[NATIVE MODULES] Parsed module name: ${moduleFolderNameInRepo}`); + console.log(`[NATIVE MODULES] Parsed version: ${version}`); + const modules = ["mobile-resources-native", "nanoflow-actions-native", "atlas-content-native"]; - if (!modules.includes(moduleFolderNameInRepo) || !version) { + + if (!process.env.TAG) { + console.log(`[NATIVE MODULES] No TAG environment variable found. Exiting.`); + return; + } + + if (!moduleFolderNameInRepo || !version) { + console.log(`[NATIVE MODULES] Could not parse TAG. Expected format: -v`); + console.log(`[NATIVE MODULES] Received TAG: ${process.env.TAG}`); + console.log(`[NATIVE MODULES] Parsed parts: module='${moduleFolderNameInRepo}', version='${version}'`); return; } - switch (moduleFolderNameInRepo) { - case "mobile-resources-native": - await createNativeMobileResourcesModule(); - break; - case "nanoflow-actions-native": - await createNanoflowCommonsModule(); - break; - case "atlas-content-native": - await createAtlasNativeContentModule(); - break; + if (!modules.includes(moduleFolderNameInRepo)) { + console.log(`[NATIVE MODULES] Module '${moduleFolderNameInRepo}' is not a recognized module.`); + console.log(`[NATIVE MODULES] Supported modules: ${modules.join(", ")}`); + return; + } + + console.log(`[NATIVE MODULES] Processing module: ${moduleFolderNameInRepo} version ${version}`); + + try { + switch (moduleFolderNameInRepo) { + case "mobile-resources-native": + await createNativeMobileResourcesModule(); + break; + case "nanoflow-actions-native": + await createNanoflowCommonsModule(); + break; + case "atlas-content-native": + await createAtlasNativeContentModule(); + break; + } + console.log(`[NATIVE MODULES] Successfully completed processing for ${moduleFolderNameInRepo}`); + } catch (error) { + console.error(`[NATIVE MODULES ERROR] Failed to process module ${moduleFolderNameInRepo}`); + console.error(`[NATIVE MODULES ERROR] Error: ${error.message}`); + console.error(`[NATIVE MODULES ERROR] Stack trace: ${error.stack}`); + throw error; } + + console.log(`[MAIN] Successfully completed processing for module: ${moduleFolderNameInRepo}`); } async function createNativeMobileResourcesModule() { - console.log("Creating the Native Mobile Resource module."); - const moduleFolder = join(repoRootPath, "packages/jsActions", moduleFolderNameInRepo); - const tmpFolder = join(repoRootPath, "tmp", moduleFolderNameInRepo); - const widgetFolders = await readdir(join(repoRootPath, "packages/pluggableWidgets")); - const nativeWidgetFolders = widgetFolders - .filter(folder => folder.includes("-native")) - .map(folder => join(repoRootPath, "packages/pluggableWidgets", folder)); - let moduleInfo = { - ...(await getPackageInfo(moduleFolder)), - moduleNameInModeler: "NativeMobileResources", - moduleFolderNameInModeler: "nativemobileresources" - }; - moduleInfo = await bumpVersionInPackageJson(moduleFolder, moduleInfo); - - await githubAuthentication(moduleInfo); - const moduleChangelogs = await updateChangelogs(nativeWidgetFolders, moduleInfo); - await commitAndCreatePullRequest(moduleInfo); - await updateNativeComponentsTestProject(moduleInfo, tmpFolder, nativeWidgetFolders); - const mpkOutput = await createMPK(tmpFolder, moduleInfo, regex.excludeFiles); - await exportModuleWithWidgets(moduleInfo.moduleNameInModeler, mpkOutput, nativeWidgetFolders); - await createGithubRelease(moduleInfo, moduleChangelogs, mpkOutput); - if (process.env.CI !== "true") { - try { - await execShellCommand(`rm -rf ${tmpFolder}`); - } catch (e) { - console.error("Failed to remove the temporary folder"); + console.log(`[MOBILE RESOURCES] Starting Native Mobile Resource module creation`); + + try { + const moduleFolder = join(repoRootPath, "packages/jsActions", moduleFolderNameInRepo); + const tmpFolder = join(repoRootPath, "tmp", moduleFolderNameInRepo); + + console.log(`[MOBILE RESOURCES] Module folder: ${moduleFolder}`); + console.log(`[MOBILE RESOURCES] Temporary folder: ${tmpFolder}`); + + console.log(`[MOBILE RESOURCES] Reading widget folders...`); + const widgetFolders = await readdir(join(repoRootPath, "packages/pluggableWidgets")); + const nativeWidgetFolders = widgetFolders + .filter(folder => folder.includes("-native")) + .map(folder => join(repoRootPath, "packages/pluggableWidgets", folder)); + + console.log(`[MOBILE RESOURCES] Found ${widgetFolders.length} total widget folders`); + console.log(`[MOBILE RESOURCES] Found ${nativeWidgetFolders.length} native widget folders`); + + console.log(`[MOBILE RESOURCES] Getting package info...`); + let moduleInfo = { + ...(await getPackageInfo(moduleFolder)), + moduleNameInModeler: "NativeMobileResources", + moduleFolderNameInModeler: "nativemobileresources" + }; + + console.log(`[MOBILE RESOURCES] Package name: ${moduleInfo.name}`); + console.log(`[MOBILE RESOURCES] Current version: ${moduleInfo.version}`); + + console.log(`[MOBILE RESOURCES] Bumping version in package.json...`); + moduleInfo = await bumpVersionInPackageJson(moduleFolder, moduleInfo); + console.log(`[MOBILE RESOURCES] New version: ${moduleInfo.version}`); + + console.log(`[MOBILE RESOURCES] Setting up GitHub authentication...`); + await githubAuthentication(moduleInfo); + + console.log(`[MOBILE RESOURCES] Updating changelogs...`); + const moduleChangelogs = await updateChangelogs(nativeWidgetFolders, moduleInfo); + console.log(`[MOBILE RESOURCES] Changelog length: ${moduleChangelogs?.length || 0} characters`); + + console.log(`[MOBILE RESOURCES] Creating pull request...`); + await commitAndCreatePullRequest(moduleInfo); + + console.log(`[MOBILE RESOURCES] Updating Native Components Test Project...`); + await updateNativeComponentsTestProject(moduleInfo, tmpFolder, nativeWidgetFolders); + + console.log(`[MOBILE RESOURCES] Creating MPK file...`); + const mpkOutput = await createMPK(tmpFolder, moduleInfo, regex.excludeFiles); + console.log(`[MOBILE RESOURCES] MPK created at: ${mpkOutput}`); + + console.log(`[MOBILE RESOURCES] Exporting module with widgets...`); + await exportModuleWithWidgets(moduleInfo.moduleNameInModeler, mpkOutput, nativeWidgetFolders); + + console.log(`[MOBILE RESOURCES] Creating GitHub release...`); + await createGithubRelease(moduleInfo, moduleChangelogs, mpkOutput); + + if (process.env.CI !== "true") { + console.log(`[MOBILE RESOURCES] Cleaning up temporary folder...`); + try { + await execShellCommand(`rm -rf ${tmpFolder}`); + console.log(`[MOBILE RESOURCES] Temporary folder cleaned up successfully`); + } catch (e) { + console.error(`[MOBILE RESOURCES ERROR] Failed to remove the temporary folder: ${e.message}`); + } + } else { + console.log(`[MOBILE RESOURCES] Running in CI, skipping temporary folder cleanup`); } + + console.log(`[MOBILE RESOURCES] Native Mobile Resource module creation completed successfully`); + } catch (error) { + console.error(`[MOBILE RESOURCES ERROR] Failed to create Native Mobile Resource module`); + console.error(`[MOBILE RESOURCES ERROR] Error: ${error.message}`); + console.error(`[MOBILE RESOURCES ERROR] Stack trace: ${error.stack}`); + throw error; } - console.log("Done."); } async function createNanoflowCommonsModule() { - console.log("Creating the Nanoflow Commons module."); - const moduleFolder = join(repoRootPath, "packages/jsActions", moduleFolderNameInRepo); - const tmpFolder = join(repoRootPath, "tmp", moduleFolderNameInRepo); - let moduleInfo = { - ...(await getPackageInfo(moduleFolder)), - moduleNameInModeler: "NanoflowCommons", - moduleFolderNameInModeler: "nanoflowcommons" - }; - moduleInfo = await bumpVersionInPackageJson(moduleFolder, moduleInfo); - - await githubAuthentication(moduleInfo); - const moduleChangelogs = await updateModuleChangelogs(moduleInfo); - await commitAndCreatePullRequest(moduleInfo); - await updateNativeComponentsTestProject(moduleInfo, tmpFolder); - const mpkOutput = await createMPK(tmpFolder, moduleInfo, regex.excludeFiles); - await createGithubRelease(moduleInfo, moduleChangelogs, mpkOutput); - if (process.env.CI !== "true") { - try { - await execShellCommand(`rm -rf ${tmpFolder}`); - } catch (e) { - console.error("Failed to remove the temporary folder"); + console.log(`[NANOFLOW COMMONS] Starting Nanoflow Commons module creation`); + + try { + const moduleFolder = join(repoRootPath, "packages/jsActions", moduleFolderNameInRepo); + const tmpFolder = join(repoRootPath, "tmp", moduleFolderNameInRepo); + + console.log(`[NANOFLOW COMMONS] Module folder: ${moduleFolder}`); + console.log(`[NANOFLOW COMMONS] Temporary folder: ${tmpFolder}`); + + console.log(`[NANOFLOW COMMONS] Getting package info...`); + let moduleInfo = { + ...(await getPackageInfo(moduleFolder)), + moduleNameInModeler: "NanoflowCommons", + moduleFolderNameInModeler: "nanoflowcommons" + }; + + console.log(`[NANOFLOW COMMONS] Package name: ${moduleInfo.name}`); + console.log(`[NANOFLOW COMMONS] Current version: ${moduleInfo.version}`); + + console.log(`[NANOFLOW COMMONS] Bumping version in package.json...`); + moduleInfo = await bumpVersionInPackageJson(moduleFolder, moduleInfo); + console.log(`[NANOFLOW COMMONS] New version: ${moduleInfo.version}`); + + console.log(`[NANOFLOW COMMONS] Setting up GitHub authentication...`); + await githubAuthentication(moduleInfo); + + console.log(`[NANOFLOW COMMONS] Updating module changelogs...`); + const moduleChangelogs = await updateModuleChangelogs(moduleInfo); + console.log(`[NANOFLOW COMMONS] Changelog length: ${moduleChangelogs?.length || 0} characters`); + + console.log(`[NANOFLOW COMMONS] Creating pull request...`); + await commitAndCreatePullRequest(moduleInfo); + + console.log(`[NANOFLOW COMMONS] Updating Native Components Test Project...`); + await updateNativeComponentsTestProject(moduleInfo, tmpFolder); + + console.log(`[NANOFLOW COMMONS] Creating MPK file...`); + const mpkOutput = await createMPK(tmpFolder, moduleInfo, regex.excludeFiles); + console.log(`[NANOFLOW COMMONS] MPK created at: ${mpkOutput}`); + + console.log(`[NANOFLOW COMMONS] Creating GitHub release...`); + await createGithubRelease(moduleInfo, moduleChangelogs, mpkOutput); + + if (process.env.CI !== "true") { + console.log(`[NANOFLOW COMMONS] Cleaning up temporary folder...`); + try { + await execShellCommand(`rm -rf ${tmpFolder}`); + console.log(`[NANOFLOW COMMONS] Temporary folder cleaned up successfully`); + } catch (e) { + console.error(`[NANOFLOW COMMONS ERROR] Failed to remove the temporary folder: ${e.message}`); + } + } else { + console.log(`[NANOFLOW COMMONS] Running in CI, skipping temporary folder cleanup`); } + + console.log(`[NANOFLOW COMMONS] Nanoflow Commons module creation completed successfully`); + } catch (error) { + console.error(`[NANOFLOW COMMONS ERROR] Failed to create Nanoflow Commons module`); + console.error(`[NANOFLOW COMMONS ERROR] Error: ${error.message}`); + console.error(`[NANOFLOW COMMONS ERROR] Stack trace: ${error.stack}`); + throw error; } - console.log("Done."); } async function createAtlasNativeContentModule() { - console.log("Creating the Atlas Native Content module."); - const moduleFolder = join(repoRootPath, `packages/modules/${moduleFolderNameInRepo}`); - const tmpFolder = join(repoRootPath, "tmp", moduleFolderNameInRepo); - let moduleInfo = { - ...(await getPackageInfo(moduleFolder)), - moduleNameInModeler: "Atlas_NativeMobile_Content", - moduleFolderNameInModeler: "atlas_nativemobile_content" - }; - moduleInfo = await bumpVersionInPackageJson(moduleFolder, moduleInfo); - await githubAuthentication(moduleInfo); - const moduleChangelogs = await updateModuleChangelogs(moduleInfo); - await commitAndCreatePullRequest(moduleInfo); - await updateNativeComponentsTestProjectWithAtlas(moduleInfo, tmpFolder); - const mpkOutput = await createMPK(tmpFolder, moduleInfo, regex.excludeFiles); - await createGithubRelease(moduleInfo, moduleChangelogs, mpkOutput); - await execShellCommand(`rm -rf ${tmpFolder}`); - console.log("Done."); + console.log(`[ATLAS CONTENT] Starting Atlas Native Content module creation`); + + try { + const moduleFolder = join(repoRootPath, `packages/modules/${moduleFolderNameInRepo}`); + const tmpFolder = join(repoRootPath, "tmp", moduleFolderNameInRepo); + + console.log(`[ATLAS CONTENT] Module folder: ${moduleFolder}`); + console.log(`[ATLAS CONTENT] Temporary folder: ${tmpFolder}`); + + console.log(`[ATLAS CONTENT] Getting package info...`); + let moduleInfo = { + ...(await getPackageInfo(moduleFolder)), + moduleNameInModeler: "Atlas_NativeMobile_Content", + moduleFolderNameInModeler: "atlas_nativemobile_content" + }; + + console.log(`[ATLAS CONTENT] Package name: ${moduleInfo.name}`); + console.log(`[ATLAS CONTENT] Current version: ${moduleInfo.version}`); + + console.log(`[ATLAS CONTENT] Bumping version in package.json...`); + moduleInfo = await bumpVersionInPackageJson(moduleFolder, moduleInfo); + console.log(`[ATLAS CONTENT] New version: ${moduleInfo.version}`); + + console.log(`[ATLAS CONTENT] Setting up GitHub authentication...`); + await githubAuthentication(moduleInfo); + + console.log(`[ATLAS CONTENT] Updating module changelogs...`); + const moduleChangelogs = await updateModuleChangelogs(moduleInfo); + console.log(`[ATLAS CONTENT] Changelog length: ${moduleChangelogs?.length || 0} characters`); + + console.log(`[ATLAS CONTENT] Creating pull request...`); + await commitAndCreatePullRequest(moduleInfo); + + console.log(`[ATLAS CONTENT] Updating Native Components Test Project with Atlas...`); + await updateNativeComponentsTestProjectWithAtlas(moduleInfo, tmpFolder); + + console.log(`[ATLAS CONTENT] Creating MPK file...`); + const mpkOutput = await createMPK(tmpFolder, moduleInfo, regex.excludeFiles); + console.log(`[ATLAS CONTENT] MPK created at: ${mpkOutput}`); + + console.log(`[ATLAS CONTENT] Creating GitHub release...`); + await createGithubRelease(moduleInfo, moduleChangelogs, mpkOutput); + + console.log(`[ATLAS CONTENT] Cleaning up temporary folder...`); + await execShellCommand(`rm -rf ${tmpFolder}`); + console.log(`[ATLAS CONTENT] Temporary folder cleaned up successfully`); + + console.log(`[ATLAS CONTENT] Atlas Native Content module creation completed successfully`); + } catch (error) { + console.error(`[ATLAS CONTENT ERROR] Failed to create Atlas Native Content module`); + console.error(`[ATLAS CONTENT ERROR] Error: ${error.message}`); + console.error(`[ATLAS CONTENT ERROR] Stack trace: ${error.stack}`); + throw error; + } } // Update test project with latest changes and update version in themesource async function updateNativeComponentsTestProject(moduleInfo, tmpFolder, nativeWidgetFolders) { - const jsActionsPath = join(repoRootPath, `packages/jsActions/${moduleFolderNameInRepo}/dist`); - const jsActions = await getFiles(jsActionsPath); - const tmpFolderActions = join(tmpFolder, `javascriptsource/${moduleInfo.moduleFolderNameInModeler}/actions`); - - console.log("Updating NativeComponentsTestProject..."); - await cloneRepo(moduleInfo.testProjectUrl, tmpFolder); - - console.log("Deleting existing JS Actions from test project..."); - await rm(tmpFolderActions, { force: true, recursive: true }); // this is useful to avoid retaining stale dependencies in the test project. - await mkdir(tmpFolderActions); - console.log("Copying widget resources JS actions into test project..."); - await Promise.all([ - ...jsActions.map(async file => { - const dest = join(tmpFolderActions, file.replace(jsActionsPath, "")); - await mkdir(dirname(dest), { recursive: true }); - await copyFile(file, dest); - }) - ]); - if (nativeWidgetFolders) { - console.log("Deleting existing widgets from test project..."); - const widgetsDir = join(tmpFolder, "widgets"); - - // backup two web widgets that are used in the test project - const tempBackup = join(tmpFolder, "widgets-backup"); - await mkdir(tempBackup); - - const htmlSnippetWidgetName = "HTMLSnippet.mpk"; - const htmlSnippetTempPath = join(tempBackup, htmlSnippetWidgetName); - const htmlSnippetPath = join(widgetsDir, htmlSnippetWidgetName); - await copyFile(htmlSnippetPath, htmlSnippetTempPath); - - const sprintrFeedbackWidgetName = "SprintrFeedbackWidget.mpk"; - const sprintFeedbackTempPath = join(tempBackup, sprintrFeedbackWidgetName); - const sprintrFeedbackPath = join(widgetsDir, sprintrFeedbackWidgetName); - await copyFile(sprintrFeedbackPath, sprintFeedbackTempPath); - - await rm(widgetsDir, { force: true, recursive: true }); // this is useful to avoid retaining stale widgets in the test project. - await mkdir(widgetsDir); - - await copyFile(htmlSnippetTempPath, htmlSnippetPath); - await copyFile(sprintFeedbackTempPath, sprintrFeedbackPath); - await rm(tempBackup, { force: true, recursive: true }); - - console.log("Copying widget resources widgets into test project..."); + console.log(`[TEST PROJECT] Starting Native Components Test Project update`); + + try { + const jsActionsPath = join(repoRootPath, `packages/jsActions/${moduleFolderNameInRepo}/dist`); + const tmpFolderActions = join(tmpFolder, `javascriptsource/${moduleInfo.moduleFolderNameInModeler}/actions`); + + console.log(`[TEST PROJECT] JS Actions path: ${jsActionsPath}`); + console.log(`[TEST PROJECT] Target actions path: ${tmpFolderActions}`); + + console.log(`[TEST PROJECT] Getting JS Actions files...`); + const jsActions = await getFiles(jsActionsPath); + console.log(`[TEST PROJECT] Found ${jsActions.length} JS Actions files`); + + console.log(`[TEST PROJECT] Cloning test project repository...`); + await cloneRepo(moduleInfo.testProjectUrl, tmpFolder); + console.log(`[TEST PROJECT] Test project cloned successfully`); + + console.log(`[TEST PROJECT] Deleting existing JS Actions from test project...`); + await rm(tmpFolderActions, { force: true, recursive: true }); // this is useful to avoid retaining stale dependencies in the test project. + await mkdir(tmpFolderActions); + console.log(`[TEST PROJECT] Existing JS Actions removed and directory recreated`); + + console.log(`[TEST PROJECT] Copying widget resources JS actions into test project...`); await Promise.all([ - ...nativeWidgetFolders.map(async folder => { - const src = (await getFiles(folder, [`.mpk`]))[0]; - const dest = join(widgetsDir, basename(src)); - await copyFile(src, dest); + ...jsActions.map(async file => { + const dest = join(tmpFolderActions, file.replace(jsActionsPath, "")); + await mkdir(dirname(dest), { recursive: true }); + await copyFile(file, dest); }) ]); - } + console.log(`[TEST PROJECT] JS Actions copied successfully`); + + if (nativeWidgetFolders) { + console.log(`[TEST PROJECT] Processing ${nativeWidgetFolders.length} native widget folders...`); + console.log(`[TEST PROJECT] Deleting existing widgets from test project...`); + const widgetsDir = join(tmpFolder, "widgets"); + + // backup two web widgets that are used in the test project + const tempBackup = join(tmpFolder, "widgets-backup"); + await mkdir(tempBackup); + console.log(`[TEST PROJECT] Created backup directory for web widgets`); + + const htmlSnippetWidgetName = "HTMLSnippet.mpk"; + const htmlSnippetTempPath = join(tempBackup, htmlSnippetWidgetName); + const htmlSnippetPath = join(widgetsDir, htmlSnippetWidgetName); + + const sprintrFeedbackWidgetName = "SprintrFeedbackWidget.mpk"; + const sprintFeedbackTempPath = join(tempBackup, sprintrFeedbackWidgetName); + const sprintrFeedbackPath = join(widgetsDir, sprintrFeedbackWidgetName); + + try { + console.log(`[TEST PROJECT] Backing up web widgets...`); + await copyFile(htmlSnippetPath, htmlSnippetTempPath); + await copyFile(sprintrFeedbackPath, sprintFeedbackTempPath); + console.log(`[TEST PROJECT] Web widgets backed up successfully`); + } catch (error) { + console.warn(`[TEST PROJECT WARN] Failed to backup some web widgets: ${error.message}`); + } + + await rm(widgetsDir, { force: true, recursive: true }); // this is useful to avoid retaining stale widgets in the test project. + await mkdir(widgetsDir); + console.log(`[TEST PROJECT] Widgets directory recreated`); + + try { + console.log(`[TEST PROJECT] Restoring web widgets...`); + await copyFile(htmlSnippetTempPath, htmlSnippetPath); + await copyFile(sprintFeedbackTempPath, sprintrFeedbackPath); + console.log(`[TEST PROJECT] Web widgets restored successfully`); + } catch (error) { + console.warn(`[TEST PROJECT WARN] Failed to restore some web widgets: ${error.message}`); + } - await execShellCommand(`echo ${version} > themesource/${moduleInfo.moduleFolderNameInModeler}/.version`, tmpFolder); - const gitOutput = await execShellCommand(`cd ${tmpFolder} && git status`); - if (!/nothing to commit/i.test(gitOutput)) { + await rm(tempBackup, { force: true, recursive: true }); + + console.log(`[TEST PROJECT] Copying widget resources widgets into test project...`); + await Promise.all([ + ...nativeWidgetFolders.map(async folder => { + try { + const mpkFiles = await getFiles(folder, [`.mpk`]); + if (mpkFiles.length === 0) { + console.warn(`[TEST PROJECT WARN] No MPK file found in ${folder}`); + return; + } + const src = mpkFiles[0]; + const dest = join(widgetsDir, basename(src)); + await copyFile(src, dest); + console.log(`[TEST PROJECT] Copied widget: ${basename(src)}`); + } catch (error) { + console.error(`[TEST PROJECT ERROR] Failed to copy widget from ${folder}: ${error.message}`); + } + }) + ]); + console.log(`[TEST PROJECT] All widgets copied successfully`); + } else { + console.log(`[TEST PROJECT] No native widget folders provided, skipping widget processing`); + } + + console.log(`[TEST PROJECT] Updating version file...`); await execShellCommand( - `git add . && git commit -m "Updated JS actions ${nativeWidgetFolders ? "and widgets" : ""}" && git push`, + `echo ${version} > themesource/${moduleInfo.moduleFolderNameInModeler}/.version`, tmpFolder ); - } else { - console.warn(`Nothing to commit from repo ${tmpFolder}`); + + console.log(`[TEST PROJECT] Checking git status...`); + const gitOutput = await execShellCommand(`cd ${tmpFolder} && git status`); + if (!/nothing to commit/i.test(gitOutput)) { + console.log(`[TEST PROJECT] Changes detected, committing and pushing...`); + await execShellCommand( + `git add . && git commit -m "Updated JS actions ${ + nativeWidgetFolders ? "and widgets" : "" + }" && git push`, + tmpFolder + ); + console.log(`[TEST PROJECT] Changes committed and pushed successfully`); + } else { + console.warn(`[TEST PROJECT WARN] Nothing to commit from repo ${tmpFolder}`); + } + + console.log(`[TEST PROJECT] Native Components Test Project update completed successfully`); + } catch (error) { + console.error(`[TEST PROJECT ERROR] Failed to update Native Components Test Project`); + console.error(`[TEST PROJECT ERROR] Error: ${error.message}`); + console.error(`[TEST PROJECT ERROR] Stack trace: ${error.stack}`); + throw error; } } // Update test project with latest changes and update version in themesource async function updateNativeComponentsTestProjectWithAtlas(moduleInfo, tmpFolder) { - const atlasNativeContentPath = join( - repoRootPath, - `packages/modules/${moduleFolderNameInRepo}/dist/themesource/${moduleInfo.moduleFolderNameInModeler}` - ); - const atlasNativeContent = await getFiles(atlasNativeContentPath); - const tmpFolderNativeStyles = join(tmpFolder, `themesource/${moduleInfo.moduleFolderNameInModeler}`); - - console.log("Updating NativeComponentsTestProject.."); - await cloneRepo(moduleInfo.testProjectUrl, tmpFolder); - - console.log("Copying Native styling files.."); - await Promise.all([ - ...atlasNativeContent.map(async file => { - const dest = join(tmpFolderNativeStyles, file.replace(atlasNativeContentPath, "")); - await rm(dest); - await copyFile(file, dest); - }) - ]); - - await execShellCommand(`echo ${version} > themesource/${moduleInfo.moduleFolderNameInModeler}/.version`, tmpFolder); - const gitOutput = await execShellCommand(`cd ${tmpFolder} && git status`); - if (!/nothing to commit/i.test(gitOutput)) { - await execShellCommand("git add . && git commit -m 'Updated Atlas native styling' && git push", tmpFolder); - } else { - console.warn(`Nothing to commit from repo ${tmpFolder}`); + console.log(`[ATLAS TEST PROJECT] Starting Native Components Test Project update with Atlas`); + + try { + const atlasNativeContentPath = join( + repoRootPath, + `packages/modules/${moduleFolderNameInRepo}/dist/themesource/${moduleInfo.moduleFolderNameInModeler}` + ); + const tmpFolderNativeStyles = join(tmpFolder, `themesource/${moduleInfo.moduleFolderNameInModeler}`); + + console.log(`[ATLAS TEST PROJECT] Atlas content path: ${atlasNativeContentPath}`); + console.log(`[ATLAS TEST PROJECT] Target styles path: ${tmpFolderNativeStyles}`); + + console.log(`[ATLAS TEST PROJECT] Getting Atlas native content files...`); + const atlasNativeContent = await getFiles(atlasNativeContentPath); + console.log(`[ATLAS TEST PROJECT] Found ${atlasNativeContent.length} Atlas content files`); + + console.log(`[ATLAS TEST PROJECT] Cloning test project repository...`); + await cloneRepo(moduleInfo.testProjectUrl, tmpFolder); + console.log(`[ATLAS TEST PROJECT] Test project cloned successfully`); + + console.log(`[ATLAS TEST PROJECT] Copying Native styling files...`); + await Promise.all([ + ...atlasNativeContent.map(async file => { + try { + const dest = join(tmpFolderNativeStyles, file.replace(atlasNativeContentPath, "")); + await rm(dest, { force: true }); + await copyFile(file, dest); + } catch (error) { + console.error(`[ATLAS TEST PROJECT ERROR] Failed to copy file ${file}: ${error.message}`); + } + }) + ]); + console.log(`[ATLAS TEST PROJECT] Native styling files copied successfully`); + + console.log(`[ATLAS TEST PROJECT] Updating version file...`); + await execShellCommand( + `echo ${version} > themesource/${moduleInfo.moduleFolderNameInModeler}/.version`, + tmpFolder + ); + + console.log(`[ATLAS TEST PROJECT] Checking git status...`); + const gitOutput = await execShellCommand(`cd ${tmpFolder} && git status`); + if (!/nothing to commit/i.test(gitOutput)) { + console.log(`[ATLAS TEST PROJECT] Changes detected, committing and pushing...`); + await execShellCommand("git add . && git commit -m 'Updated Atlas native styling' && git push", tmpFolder); + console.log(`[ATLAS TEST PROJECT] Changes committed and pushed successfully`); + } else { + console.warn(`[ATLAS TEST PROJECT WARN] Nothing to commit from repo ${tmpFolder}`); + } + + console.log(`[ATLAS TEST PROJECT] Atlas test project update completed successfully`); + } catch (error) { + console.error(`[ATLAS TEST PROJECT ERROR] Failed to update Native Components Test Project with Atlas`); + console.error(`[ATLAS TEST PROJECT ERROR] Error: ${error.message}`); + console.error(`[ATLAS TEST PROJECT ERROR] Stack trace: ${error.stack}`); + throw error; } } diff --git a/scripts/release/createWidgetRelease.js b/scripts/release/createWidgetRelease.js index 9ed94e92d..e568e5a03 100644 --- a/scripts/release/createWidgetRelease.js +++ b/scripts/release/createWidgetRelease.js @@ -10,84 +10,144 @@ const { } = require("./module-automation/commons"); main().catch(e => { - console.error(e); + console.error(`[WIDGET RELEASE ERROR] Widget release process failed`); + console.error(`[WIDGET RELEASE ERROR] Widget scope: ${process.argv[2]}`); + console.error(`[WIDGET RELEASE ERROR] Error: ${e.message}`); + console.error(`[WIDGET RELEASE ERROR] Stack trace: ${e.stack}`); process.exit(-1); }); async function main() { const widgetScope = process.argv[2]; + console.log(`[WIDGET RELEASE] Starting widget release process`); + console.log(`[WIDGET RELEASE] Widget scope: ${widgetScope}`); + if (!widgetScope.endsWith("-web")) { - throw new Error(`${widgetScope} is not a valid widget package.`); + const errorMessage = `${widgetScope} is not a valid widget package.`; + console.error(`[WIDGET RELEASE ERROR] ${errorMessage}`); + throw new Error(errorMessage); } - const { - releaseMpkPath, - repositoryUrl, - unreleasedChangelogs, - version, - widgetName, - changelogPath - } = await getWidgetReleaseInformation(widgetScope); - - if (!unreleasedChangelogs) { - throw new Error(`No unreleased changes found in the CHANGELOG.md for ${widgetName} ${version}.`); + try { + const { releaseMpkPath, repositoryUrl, unreleasedChangelogs, version, widgetName, changelogPath } = + await getWidgetReleaseInformation(widgetScope); + + console.log(`[WIDGET RELEASE] Widget information loaded`); + console.log(`[WIDGET RELEASE] Widget name: ${widgetName}`); + console.log(`[WIDGET RELEASE] Version: ${version}`); + console.log(`[WIDGET RELEASE] Repository URL: ${repositoryUrl}`); + console.log(`[WIDGET RELEASE] MPK path: ${releaseMpkPath}`); + console.log(`[WIDGET RELEASE] Changelog path: ${changelogPath}`); + + if (!unreleasedChangelogs) { + const errorMessage = `No unreleased changes found in the CHANGELOG.md for ${widgetName} ${version}.`; + console.error(`[WIDGET RELEASE ERROR] ${errorMessage}`); + throw new Error(errorMessage); + } + + console.log(`[WIDGET RELEASE] Starting widget release process for ${widgetName} v${version}`); + await githubAuthentication({ url: repositoryUrl }); + + console.log(`[WIDGET RELEASE] Creating GitHub release`); + await createGithubReleaseFrom({ + title: `${widgetName} (Web) - Marketplace Release v${version}`, + body: unreleasedChangelogs.replace(/"/g, "'"), + tag: `${widgetScope}-v${version}`, + mpkOutput: releaseMpkPath, + isDraft: true + }); + + console.log(`[WIDGET RELEASE] Updating widget CHANGELOG.md`); + await writeToWidgetChangelogs(unreleasedChangelogs, { changelogPath, version }); + + console.log(`[WIDGET RELEASE] Creating pull request for CHANGELOG.md`); + await commitAndCreatePullRequest({ nameWithDash: widgetScope, version, nameWithSpace: widgetName }); + + console.log(`[WIDGET RELEASE] Successfully completed widget release for ${widgetName} v${version}`); + } catch (error) { + console.error(`[WIDGET RELEASE ERROR] Failed to complete widget release`); + console.error(`[WIDGET RELEASE ERROR] Widget scope: ${widgetScope}`); + console.error(`[WIDGET RELEASE ERROR] Error: ${error.message}`); + console.error(`[WIDGET RELEASE ERROR] Stack trace: ${error.stack}`); + throw error; } - - console.log("Starting widget release..."); - await githubAuthentication({ url: repositoryUrl }); - console.log("Creating Github release..."); - await createGithubReleaseFrom({ - title: `${widgetName} (Web) - Marketplace Release v${version}`, - body: unreleasedChangelogs.replace(/"/g, "'"), - tag: `${widgetScope}-v${version}`, - mpkOutput: releaseMpkPath, - isDraft: true - }); - console.log("Updating widget CHANGELOG.md..."); - await writeToWidgetChangelogs(unreleasedChangelogs, { changelogPath, version }); - console.log("Creating pull request for CHANGELOG.md..."); - await commitAndCreatePullRequest({ nameWithDash: widgetScope, version, nameWithSpace: widgetName }); - console.log("Done."); } async function getWidgetReleaseInformation(widgetScope) { - const pluggableWidgetsFolder = join(process.cwd(), "packages/pluggableWidgets"); - const pluggableWidgets = await readdir(pluggableWidgetsFolder); - - if (!pluggableWidgets.includes(widgetScope)) { - throw new Error(`${widgetScope} is not a valid pluggable widget.`); - } - - const widgetPath = join(pluggableWidgetsFolder, widgetScope); - const pkgPath = join(widgetPath, "package.json"); - const { name, widgetName, version, repository } = require(pkgPath); - - console.log(`Getting the widget release information for ${widgetName} widget...`); - - if (!name || !widgetName || !version || !version.includes(".") || !repository?.url) { - throw new Error(`${pkgPath} does not define expected keys.`); + console.log(`[WIDGET INFO] Getting widget release information for: ${widgetScope}`); + + try { + const pluggableWidgetsFolder = join(process.cwd(), "packages/pluggableWidgets"); + const pluggableWidgets = await readdir(pluggableWidgetsFolder); + + console.log(`[WIDGET INFO] Pluggable widgets folder: ${pluggableWidgetsFolder}`); + console.log(`[WIDGET INFO] Found ${pluggableWidgets.length} widgets in folder`); + + if (!pluggableWidgets.includes(widgetScope)) { + const errorMessage = `${widgetScope} is not a valid pluggable widget.`; + console.error(`[WIDGET INFO ERROR] ${errorMessage}`); + console.error(`[WIDGET INFO ERROR] Available widgets: ${pluggableWidgets.join(", ")}`); + throw new Error(errorMessage); + } + + const widgetPath = join(pluggableWidgetsFolder, widgetScope); + const pkgPath = join(widgetPath, "package.json"); + const { name, widgetName, version, repository } = require(pkgPath); + + console.log(`[WIDGET INFO] Widget path: ${widgetPath}`); + console.log(`[WIDGET INFO] Package.json path: ${pkgPath}`); + console.log(`[WIDGET INFO] Widget name: ${widgetName} (${name})`); + console.log(`[WIDGET INFO] Version: ${version}`); + console.log(`[WIDGET INFO] Repository: ${repository?.url}`); + + if (!name || !widgetName || !version || !version.includes(".") || !repository?.url) { + const errorMessage = `${pkgPath} does not define expected keys.`; + console.error(`[WIDGET INFO ERROR] ${errorMessage}`); + console.error(`[WIDGET INFO ERROR] Name: ${name}`); + console.error(`[WIDGET INFO ERROR] Widget name: ${widgetName}`); + console.error(`[WIDGET INFO ERROR] Version: ${version}`); + console.error(`[WIDGET INFO ERROR] Repository URL: ${repository?.url}`); + throw new Error(errorMessage); + } + + if (version.split(".").length !== 3) { + const errorMessage = `${pkgPath} version is not defined correctly.`; + console.error(`[WIDGET INFO ERROR] ${errorMessage}`); + console.error(`[WIDGET INFO ERROR] Version provided: ${version}`); + console.error(`[WIDGET INFO ERROR] Expected format: x.y.z`); + throw new Error(errorMessage); + } + + const mpkFile = ls(join(widgetPath, "dist", "**/*.mpk")).toString(); + + if (!mpkFile) { + const errorMessage = "MPK file not found"; + console.error(`[WIDGET INFO ERROR] ${errorMessage}`); + console.error(`[WIDGET INFO ERROR] Searched in: ${join(widgetPath, "dist", "**/*.mpk")}`); + throw new Error(errorMessage); + } + + console.log(`[WIDGET INFO] MPK path: ${mpkFile}`); + const changelogPath = join(widgetPath, "CHANGELOG.md"); + console.log(`[WIDGET INFO] Changelog path: ${changelogPath}`); + + const result = { + releaseMpkPath: mpkFile, + repositoryUrl: repository.url, + unreleasedChangelogs: await getUnreleasedChangelogs({ version, changelogPath }), + version, + widgetName, + changelogPath + }; + + console.log(`[WIDGET INFO] Successfully loaded widget information for ${widgetName}`); + return result; + } catch (error) { + console.error(`[WIDGET INFO ERROR] Failed to get widget release information`); + console.error(`[WIDGET INFO ERROR] Widget scope: ${widgetScope}`); + console.error(`[WIDGET INFO ERROR] Error: ${error.message}`); + console.error(`[WIDGET INFO ERROR] Stack trace: ${error.stack}`); + throw error; } - - if (version.split(".").length !== 3) { - throw new Error(`${pkgPath} version is not defined correctly.`); - } - - const mpkFile = ls(join(widgetPath, "dist", "**/*.mpk")).toString(); - - if (!mpkFile) { - throw new Error("MPK file not found"); - } - - console.log(`MPK path: ${mpkFile}`); - const changelogPath = join(widgetPath, "CHANGELOG.md"); - - return { - releaseMpkPath: mpkFile, - repositoryUrl: repository.url, - unreleasedChangelogs: await getUnreleasedChangelogs({ version, changelogPath }), - version, - widgetName, - changelogPath - }; } diff --git a/scripts/release/marketplaceRelease.js b/scripts/release/marketplaceRelease.js index 8657cd544..1ad6b3ea1 100644 --- a/scripts/release/marketplaceRelease.js +++ b/scripts/release/marketplaceRelease.js @@ -13,69 +13,171 @@ const config = { }; main().catch(e => { - console.error(e); + console.error(`[MARKETPLACE RELEASE ERROR] Marketplace release process failed`); + console.error(`[MARKETPLACE RELEASE ERROR] TAG: ${process.env.TAG}`); + console.error(`[MARKETPLACE RELEASE ERROR] Error: ${e.message}`); + console.error(`[MARKETPLACE RELEASE ERROR] Stack trace: ${e.stack}`); process.exit(1); }); async function main() { - const pkgPath = join(process.cwd(), "package.json"); - const { - name, - widgetName, - version, - marketplace: { minimumMXVersion, marketplaceId } - } = require(pkgPath); - - console.log(`Starting release process for tag ${process.env.TAG}`); - - const pkgName = name ?? widgetName; - if (!pkgName || !version || !minimumMXVersion || !marketplaceId || !version.includes(".")) { - throw new Error(`${pkgPath} does not define expected keys.`); - } + console.log(`[MARKETPLACE] Starting marketplace release process`); + console.log(`[MARKETPLACE] TAG: ${process.env.TAG}`); - if (version.split(".").length !== 3) { - throw new Error(`${pkgPath} version is not defined correctly.`); - } + try { + const pkgPath = join(process.cwd(), "package.json"); + const { + name, + widgetName, + version, + marketplace: { minimumMXVersion, marketplaceId } + } = require(pkgPath); + + console.log(`[MARKETPLACE] Package path: ${pkgPath}`); + console.log(`[MARKETPLACE] Processing release for tag: ${process.env.TAG}`); + + const pkgName = name ?? widgetName; - await uploadModuleToAppStore(pkgName, marketplaceId, version, minimumMXVersion); + console.log(`[MARKETPLACE] Package name: ${pkgName}`); + console.log(`[MARKETPLACE] Widget name: ${widgetName}`); + console.log(`[MARKETPLACE] Version: ${version}`); + console.log(`[MARKETPLACE] Minimum MX Version: ${minimumMXVersion}`); + console.log(`[MARKETPLACE] Marketplace ID: ${marketplaceId}`); + + if (!pkgName || !version || !minimumMXVersion || !marketplaceId || !version.includes(".")) { + const errorMessage = `${pkgPath} does not define expected keys.`; + console.error(`[MARKETPLACE ERROR] ${errorMessage}`); + console.error(`[MARKETPLACE ERROR] Package name: ${pkgName}`); + console.error(`[MARKETPLACE ERROR] Version: ${version}`); + console.error(`[MARKETPLACE ERROR] Minimum MX Version: ${minimumMXVersion}`); + console.error(`[MARKETPLACE ERROR] Marketplace ID: ${marketplaceId}`); + throw new Error(errorMessage); + } + + if (version.split(".").length !== 3) { + const errorMessage = `${pkgPath} version is not defined correctly.`; + console.error(`[MARKETPLACE ERROR] ${errorMessage}`); + console.error(`[MARKETPLACE ERROR] Version provided: ${version}`); + console.error(`[MARKETPLACE ERROR] Expected format: x.y.z`); + throw new Error(errorMessage); + } + + await uploadModuleToAppStore(pkgName, marketplaceId, version, minimumMXVersion); + console.log(`[MARKETPLACE] Successfully completed marketplace release for ${pkgName} v${version}`); + } catch (error) { + console.error(`[MARKETPLACE ERROR] Failed to complete marketplace release`); + console.error(`[MARKETPLACE ERROR] TAG: ${process.env.TAG}`); + console.error(`[MARKETPLACE ERROR] Error: ${error.message}`); + console.error(`[MARKETPLACE ERROR] Stack trace: ${error.stack}`); + throw error; + } } async function uploadModuleToAppStore(pkgName, marketplaceId, version, minimumMXVersion) { + console.log(`[APPSTORE UPLOAD] Starting upload to App Store`); + console.log(`[APPSTORE UPLOAD] Package name: ${pkgName}`); + console.log(`[APPSTORE UPLOAD] Marketplace ID: ${marketplaceId}`); + console.log(`[APPSTORE UPLOAD] Version: ${version}`); + console.log(`[APPSTORE UPLOAD] Minimum MX Version: ${minimumMXVersion}`); + try { const postResponse = await createDraft(marketplaceId, version, minimumMXVersion); + console.log(`[APPSTORE UPLOAD] Successfully created draft with UUID: ${postResponse.UUID}`); + await publishDraft(postResponse.UUID); - console.log(`Successfully uploaded ${pkgName} to the Mendix Marketplace.`); + console.log(`[APPSTORE UPLOAD] Successfully published draft`); + console.log(`[APPSTORE UPLOAD] Successfully uploaded ${pkgName} to the Mendix Marketplace.`); } catch (error) { + console.error(`[APPSTORE UPLOAD ERROR] Failed uploading to app store`); + console.error(`[APPSTORE UPLOAD ERROR] Package name: ${pkgName}`); + console.error(`[APPSTORE UPLOAD ERROR] Marketplace ID: ${marketplaceId}`); + console.error(`[APPSTORE UPLOAD ERROR] Version: ${version}`); + console.error(`[APPSTORE UPLOAD ERROR] Minimum MX Version: ${minimumMXVersion}`); + console.error(`[APPSTORE UPLOAD ERROR] Error: ${error.message}`); + console.error(`[APPSTORE UPLOAD ERROR] Stack trace: ${error.stack}`); error.message = `Failed uploading ${pkgName} to appstore with error: ${error.message}`; throw error; } } async function getGithubAssetUrl() { - console.log("Retrieving informations from Github Tag"); - const request = await fetch("GET", "https://api.github.com/repos/mendix/native-widgets/releases?per_page=10"); - const data = (await request) ?? []; - const releaseId = data.find(info => info.tag_name === process.env.TAG)?.id; - if (!releaseId) { - throw new Error(`Could not find release with tag ${process.env.TAG} on GitHub`); - } - const assetsRequest = await fetch( - "GET", - `https://api.github.com/repos/mendix/native-widgets/releases/${releaseId}/assets` - ); - const assetsData = (await assetsRequest) ?? []; - const downloadUrl = assetsData.find(asset => asset.name.endsWith(".mpk"))?.browser_download_url; - if (!downloadUrl) { - throw new Error(`Could not retrieve MPK url from GitHub release with tag ${process.env.TAG}`); + console.log(`[GITHUB ASSET] Retrieving information from GitHub Tag: ${process.env.TAG}`); + + try { + const request = await fetch("GET", "https://api.github.com/repos/mendix/native-widgets/releases?per_page=20"); + const data = (await request) ?? []; + + console.log(`[GITHUB ASSET] Found ${data.length} releases on GitHub`); + console.log(`[GITHUB ASSET] Looking for release with tag: ${process.env.TAG}`); + + // Log available releases for debugging + const availableTags = data.map(release => release.tag_name); + console.log( + `[GITHUB ASSET] Available release tags: ${availableTags.slice(0, 10).join(", ")}${ + availableTags.length > 10 ? "..." : "" + }` + ); + + const releaseId = data.find(info => info.tag_name === process.env.TAG)?.id; + if (!releaseId) { + console.error(`[GITHUB ASSET ERROR] Could not find release with tag ${process.env.TAG} on GitHub`); + console.error(`[GITHUB ASSET ERROR] Available tags: ${availableTags.join(", ")}`); + console.error(`[GITHUB ASSET ERROR] This usually means the GitHub release creation step failed earlier`); + console.error(`[GITHUB ASSET ERROR] Check the native module creation logs for the GitHub release step`); + console.error(`[GITHUB ASSET ERROR] `); + console.error(`[GITHUB ASSET ERROR] To resolve this issue:`); + console.error(`[GITHUB ASSET ERROR] 1. Check if the native module creation process completed successfully`); + console.error(`[GITHUB ASSET ERROR] 2. Look for GitHub release creation errors in the logs`); + console.error(`[GITHUB ASSET ERROR] 3. If needed, manually create the GitHub release with the MPK asset`); + console.error(`[GITHUB ASSET ERROR] 4. Or re-run the native module creation process`); + console.error(`[GITHUB ASSET ERROR] `); + throw new Error(`Could not find release with tag ${process.env.TAG} on GitHub`); + } + + console.log(`[GITHUB ASSET] Found release with ID: ${releaseId}`); + + const assetsRequest = await fetch( + "GET", + `https://api.github.com/repos/mendix/native-widgets/releases/${releaseId}/assets` + ); + const assetsData = (await assetsRequest) ?? []; + + console.log(`[GITHUB ASSET] Found ${assetsData.length} assets in release`); + + const downloadUrl = assetsData.find(asset => asset.name.endsWith(".mpk"))?.browser_download_url; + if (!downloadUrl) { + console.error( + `[GITHUB ASSET ERROR] Could not retrieve MPK URL from GitHub release with tag ${process.env.TAG}` + ); + console.error(`[GITHUB ASSET ERROR] Available assets: ${assetsData.map(a => a.name).join(", ")}`); + console.error(`[GITHUB ASSET ERROR] Looking for assets ending with .mpk`); + throw new Error(`Could not retrieve MPK url from GitHub release with tag ${process.env.TAG}`); + } + + console.log(`[GITHUB ASSET] Successfully found MPK download URL: ${downloadUrl}`); + return downloadUrl; + } catch (error) { + console.error(`[GITHUB ASSET ERROR] Failed to retrieve GitHub asset URL`); + console.error(`[GITHUB ASSET ERROR] Tag: ${process.env.TAG}`); + console.error(`[GITHUB ASSET ERROR] Error: ${error.message}`); + console.error(`[GITHUB ASSET ERROR] Stack trace: ${error.stack}`); + throw error; } - return downloadUrl; } async function createDraft(marketplaceId, version, minimumMXVersion) { - console.log(`Creating draft in the Mendix Marketplace...`); - console.log(`ID: ${marketplaceId} - Version: ${version} - MXVersion: ${minimumMXVersion}`); + console.log(`[MARKETPLACE DRAFT] Creating draft in the Mendix Marketplace`); + console.log(`[MARKETPLACE DRAFT] ID: ${marketplaceId} - Version: ${version} - MXVersion: ${minimumMXVersion}`); + console.log(`[MARKETPLACE DRAFT] TAG: ${process.env.TAG}`); + const [major, minor, patch] = version.split("."); + console.log(`[MARKETPLACE DRAFT] Version parts - Major: ${major}, Minor: ${minor}, Patch: ${patch}`); + console.log(`[MARKETPLACE DRAFT] Studio Pro Version: ${minimumMXVersion.split(".").slice(0, 3).join(".")}`); + try { + console.log(`[MARKETPLACE DRAFT] Getting GitHub asset URL for release`); + const artifactURL = await getGithubAssetUrl(); + const body = { VersionMajor: major ?? 1, VersionMinor: minor ?? 0, @@ -84,22 +186,42 @@ async function createDraft(marketplaceId, version, minimumMXVersion) { IsSourceGitHub: true, GithubRepo: { UseReadmeForDoc: false, - ArtifactURL: await getGithubAssetUrl() + ArtifactURL: artifactURL } }; - return fetchContributor("POST", `packages/${marketplaceId}/versions`, JSON.stringify(body)); + console.log(`[MARKETPLACE DRAFT] Request body prepared, sending to marketplace API`); + console.log(`[MARKETPLACE DRAFT] Artifact URL: ${artifactURL}`); + + const result = await fetchContributor("POST", `packages/${marketplaceId}/versions`, JSON.stringify(body)); + console.log(`[MARKETPLACE DRAFT] Successfully created draft in marketplace`); + return result; } catch (error) { + console.error(`[MARKETPLACE DRAFT ERROR] Failed creating draft in the appstore`); + console.error(`[MARKETPLACE DRAFT ERROR] Marketplace ID: ${marketplaceId}`); + console.error(`[MARKETPLACE DRAFT ERROR] Version: ${version}`); + console.error(`[MARKETPLACE DRAFT ERROR] MX Version: ${minimumMXVersion}`); + console.error(`[MARKETPLACE DRAFT ERROR] TAG: ${process.env.TAG}`); + console.error(`[MARKETPLACE DRAFT ERROR] Error: ${error.message}`); + console.error(`[MARKETPLACE DRAFT ERROR] Stack trace: ${error.stack}`); error.message = `Failed creating draft in the appstore with error: ${error.message}`; throw error; } } function publishDraft(UUID) { - console.log(`Publishing draft in the Mendix Marketplace...`); + console.log(`[MARKETPLACE PUBLISH] Publishing draft in the Mendix Marketplace`); + console.log(`[MARKETPLACE PUBLISH] Draft UUID: ${UUID}`); + try { - return fetchContributor("PATCH", `package-versions/${UUID}`, JSON.stringify({ Status: "Publish" })); + const result = fetchContributor("PATCH", `package-versions/${UUID}`, JSON.stringify({ Status: "Publish" })); + console.log(`[MARKETPLACE PUBLISH] Successfully initiated publish for draft: ${UUID}`); + return result; } catch (error) { + console.error(`[MARKETPLACE PUBLISH ERROR] Failed publishing draft in the appstore`); + console.error(`[MARKETPLACE PUBLISH ERROR] Draft UUID: ${UUID}`); + console.error(`[MARKETPLACE PUBLISH ERROR] Error: ${error.message}`); + console.error(`[MARKETPLACE PUBLISH ERROR] Stack trace: ${error.stack}`); error.message = `Failed publishing draft in the appstore with error: ${error.message}`; throw error; } diff --git a/scripts/release/module-automation/commons.js b/scripts/release/module-automation/commons.js index e51de0002..d34d5ddc6 100644 --- a/scripts/release/module-automation/commons.js +++ b/scripts/release/module-automation/commons.js @@ -15,16 +15,28 @@ async function setLocalGitCredentials(workingDirectory) { } function execShellCommand(cmd, workingDirectory = process.cwd()) { + console.log(`[EXEC] Running command: ${cmd}`); + console.log(`[EXEC] Working directory: ${workingDirectory}`); + return new Promise((resolve, reject) => { exec(cmd, { cwd: workingDirectory }, (error, stdout, stderr) => { if (error) { - console.warn(stderr); - console.warn(stdout); + console.error(`[EXEC ERROR] Command failed: ${cmd}`); + console.error(`[EXEC ERROR] Working directory: ${workingDirectory}`); + console.error(`[EXEC ERROR] Exit code: ${error.code}`); + console.error(`[EXEC ERROR] Stderr: ${stderr}`); + console.error(`[EXEC ERROR] Stdout: ${stdout}`); + console.error(`[EXEC ERROR] Error message: ${error.message}`); reject(error); } if (stderr) { - console.warn(stderr); + console.warn(`[EXEC WARN] Stderr output: ${stderr}`); + } + console.log(`[EXEC SUCCESS] Command completed successfully: ${cmd}`); + if (stdout.trim()) { + console.log(`[EXEC SUCCESS] Output: ${stdout.trim()}`); } + console.log(`[EXEC SUCCESS] Command completed: ${cmd}`); resolve(stdout); }); }); @@ -50,7 +62,7 @@ async function getPackageInfo(path) { try { await access(pkgPath); const { name, widgetName, moduleName, version, marketplace, testProject, repository } = require(pkgPath); - return { + const packageInfo = { nameWithDash: name, nameWithSpace: moduleName ?? widgetName, version, @@ -60,8 +72,20 @@ async function getPackageInfo(path) { testProjectBranchName: testProject?.branchName, changelogPath: `${path}/CHANGELOG.md` }; + + console.log(`[PACKAGE INFO] Loading package info from: ${pkgPath}`); + console.log(`[PACKAGE INFO] Name: ${packageInfo.nameWithSpace} (${packageInfo.nameWithDash})`); + console.log(`[PACKAGE INFO] Version: ${packageInfo.version}`); + console.log(`[PACKAGE INFO] Minimum MX Version: ${packageInfo.minimumMXVersion}`); + console.log(`[PACKAGE INFO] Repository URL: ${packageInfo.url}`); + console.log(`[PACKAGE INFO] Test Project URL: ${packageInfo.testProjectUrl}`); + console.log(`[PACKAGE INFO] Test Project Branch: ${packageInfo.testProjectBranchName}`); + + return packageInfo; } catch (error) { - console.error(`ERROR: Path does not exist: ${pkgPath}`); + console.error(`[PACKAGE INFO ERROR] Path does not exist: ${pkgPath}`); + console.error(`[PACKAGE INFO ERROR] Error details: ${error.message}`); + console.error(`[PACKAGE INFO ERROR] Stack trace: ${error.stack}`); return null; } } @@ -91,18 +115,40 @@ async function createModuleMpkInDocker(sourceDir, moduleName, mendixVersion, exc async function bumpVersionInPackageJson(moduleFolder, moduleInfo) { const moduleVersionNew = process.env.TAG?.split("-v")?.[1]; + + console.log(`[VERSION BUMP] Processing version bump for: ${moduleInfo.nameWithSpace}`); + console.log(`[VERSION BUMP] Current version: ${moduleInfo.version}`); + console.log(`[VERSION BUMP] Target version: ${moduleVersionNew}`); + console.log(`[VERSION BUMP] Module folder: ${moduleFolder}`); + console.log(`[VERSION BUMP] TAG environment variable: ${process.env.TAG}`); + if (moduleInfo.version === moduleVersionNew) { - throw new Error( - `It looks like version ${moduleInfo.version} of "${moduleInfo.nameWithSpace}" has been released already. Did you manually bump the version without releasing? Then you should revert the bump before retrying.` - ); + const errorMessage = `It looks like version ${moduleInfo.version} of "${moduleInfo.nameWithSpace}" has been released already. Did you manually bump the version without releasing? Then you should revert the bump before retrying.`; + console.error(`[VERSION BUMP ERROR] ${errorMessage}`); + throw new Error(errorMessage); } else { console.log( - `Bumping "${moduleInfo.nameWithSpace}" version from ${moduleInfo.version} to ${moduleVersionNew}..` + `[VERSION BUMP] Bumping "${moduleInfo.nameWithSpace}" version from ${moduleInfo.version} to ${moduleVersionNew}..` ); - const pkgPath = join(moduleFolder, "package.json"); - const pkg = require(pkgPath); - moduleInfo.version = pkg.version = moduleVersionNew; - await writeFile(pkgPath, JSON.stringify(pkg, null, 2)); + + try { + const pkgPath = join(moduleFolder, "package.json"); + const pkg = require(pkgPath); + moduleInfo.version = pkg.version = moduleVersionNew; + await writeFile(pkgPath, JSON.stringify(pkg, null, 2)); + + console.log(`[VERSION BUMP] Successfully updated package.json at: ${pkgPath}`); + console.log(`[VERSION BUMP] New version set: ${moduleVersionNew}`); + } catch (error) { + console.error(`[VERSION BUMP ERROR] Failed to update package.json`); + console.error(`[VERSION BUMP ERROR] Module: ${moduleInfo.nameWithSpace}`); + console.error(`[VERSION BUMP ERROR] Folder: ${moduleFolder}`); + console.error(`[VERSION BUMP ERROR] Current version: ${moduleInfo.version}`); + console.error(`[VERSION BUMP ERROR] Target version: ${moduleVersionNew}`); + console.error(`[VERSION BUMP ERROR] Error: ${error.message}`); + console.error(`[VERSION BUMP ERROR] Stack trace: ${error.stack}`); + throw error; + } } return moduleInfo; } @@ -151,13 +197,50 @@ async function getUnreleasedChangelogs({ version, changelogPath }) { // Update changelogs and create PR in widget-resources async function commitAndCreatePullRequest(moduleInfo) { const changelogBranchName = `${moduleInfo.nameWithDash}-release-${moduleInfo.version}`; - await execShellCommand( - `git checkout -b ${changelogBranchName} && git add . && git commit -m "chore(${moduleInfo.nameWithDash}): update changelogs" && git push --set-upstream origin ${changelogBranchName}` - ); - await execShellCommand( - `gh pr create --title "${moduleInfo.nameWithSpace}: Updating changelogs" --body "This is an automated PR." --base main --head ${changelogBranchName}` - ); - console.log("Created PR for changelog updates."); + + console.log(`[BRANCH] Creating branch: ${changelogBranchName}`); + console.log(`[BRANCH] Module: ${moduleInfo.nameWithSpace}`); + console.log(`[BRANCH] Version: ${moduleInfo.version}`); + + try { + // Check if branch already exists locally or remotely and delete it + const branchExists = await execShellCommand(`git branch --list ${changelogBranchName}`) + .then(output => output.trim().length > 0) + .catch(() => false); + + const remoteBranchExists = await execShellCommand(`git ls-remote --heads origin ${changelogBranchName}`) + .then(output => output.trim().length > 0) + .catch(() => false); + + if (branchExists) { + console.log(`[BRANCH] Local branch ${changelogBranchName} exists, deleting it`); + await execShellCommand(`git branch -D ${changelogBranchName}`); + } + + if (remoteBranchExists) { + console.log(`[BRANCH] Remote branch ${changelogBranchName} exists, deleting it`); + await execShellCommand(`git push origin --delete ${changelogBranchName}`); + } + + // Create new branch and push + await execShellCommand( + `git checkout -b ${changelogBranchName} && git add . && git commit -m "chore(${moduleInfo.nameWithDash}): update changelogs" && git push --set-upstream origin ${changelogBranchName}` + ); + + await execShellCommand( + `gh pr create --title "${moduleInfo.nameWithSpace}: Updating changelogs" --body "This is an automated PR." --base main --head ${changelogBranchName}` + ); + + console.log(`[BRANCH] Created PR for changelog updates on branch: ${changelogBranchName}`); + } catch (error) { + console.error(`[BRANCH ERROR] Failed to create branch and PR`); + console.error(`[BRANCH ERROR] Branch name: ${changelogBranchName}`); + console.error(`[BRANCH ERROR] Module: ${moduleInfo.nameWithSpace}`); + console.error(`[BRANCH ERROR] Version: ${moduleInfo.version}`); + console.error(`[BRANCH ERROR] Error: ${error.message}`); + console.error(`[BRANCH ERROR] Stack trace: ${error.stack}`); + throw error; + } } async function updateWidgetChangelogs(widgetsFolders) { @@ -217,31 +300,112 @@ async function cloneRepo(githubUrl, localFolder) { } async function createMPK(tmpFolder, moduleInfo, excludeFilesRegExp) { - console.log("Creating module MPK.."); - await createModuleMpkInDocker( - tmpFolder, - moduleInfo.moduleNameInModeler, - moduleInfo.minimumMXVersion, - excludeFilesRegExp - ); - return (await getFiles(tmpFolder, [`.mpk`]))[0]; + console.log(`[MPK CREATION] Starting MPK creation process`); + console.log(`[MPK CREATION] Module: ${moduleInfo.nameWithSpace} (${moduleInfo.moduleNameInModeler})`); + console.log(`[MPK CREATION] Version: ${moduleInfo.version}`); + console.log(`[MPK CREATION] Minimum MX Version: ${moduleInfo.minimumMXVersion}`); + console.log(`[MPK CREATION] Temporary folder: ${tmpFolder}`); + console.log(`[MPK CREATION] Exclude files regex: ${excludeFilesRegExp}`); + + try { + await createModuleMpkInDocker( + tmpFolder, + moduleInfo.moduleNameInModeler, + moduleInfo.minimumMXVersion, + excludeFilesRegExp + ); + + const mpkFiles = await getFiles(tmpFolder, [`.mpk`]); + const mpkOutput = mpkFiles[0]; + + console.log(`[MPK CREATION] Successfully created MPK: ${mpkOutput}`); + return mpkOutput; + } catch (error) { + console.error(`[MPK CREATION ERROR] Failed to create MPK`); + console.error(`[MPK CREATION ERROR] Module: ${moduleInfo.nameWithSpace} (${moduleInfo.moduleNameInModeler})`); + console.error(`[MPK CREATION ERROR] Version: ${moduleInfo.version}`); + console.error(`[MPK CREATION ERROR] MX Version: ${moduleInfo.minimumMXVersion}`); + console.error(`[MPK CREATION ERROR] Temporary folder: ${tmpFolder}`); + console.error(`[MPK CREATION ERROR] Exclude regex: ${excludeFilesRegExp}`); + console.error(`[MPK CREATION ERROR] Error: ${error.message}`); + console.error(`[MPK CREATION ERROR] Stack trace: ${error.stack}`); + throw error; + } } async function createGithubRelease(moduleInfo, moduleChangelogs, mpkOutput) { - console.log(`Creating Github release for module ${moduleInfo.nameWithSpace}`); - await createGithubReleaseFrom({ - title: `${moduleInfo.nameWithSpace} ${moduleInfo.version} - Mendix ${moduleInfo.minimumMXVersion}`, - body: moduleChangelogs, - tag: process.env.TAG, - mpkOutput - }); + console.log(`[GITHUB RELEASE] Creating GitHub release for module: ${moduleInfo.nameWithSpace}`); + console.log(`[GITHUB RELEASE] Version: ${moduleInfo.version}`); + console.log(`[GITHUB RELEASE] MX Version: ${moduleInfo.minimumMXVersion}`); + console.log(`[GITHUB RELEASE] Tag: ${process.env.TAG}`); + console.log(`[GITHUB RELEASE] MPK Output: ${mpkOutput}`); + console.log(`[GITHUB RELEASE] Changelog length: ${moduleChangelogs?.length || 0} characters`); + + try { + await createGithubReleaseFrom({ + title: `${moduleInfo.nameWithSpace} ${moduleInfo.version} - Mendix ${moduleInfo.minimumMXVersion}`, + body: moduleChangelogs, + tag: process.env.TAG, + mpkOutput + }); + + console.log( + `[GITHUB RELEASE] Successfully created GitHub release for: ${moduleInfo.nameWithSpace} ${moduleInfo.version}` + ); + } catch (error) { + console.error(`[GITHUB RELEASE ERROR] Failed to create GitHub release`); + console.error(`[GITHUB RELEASE ERROR] Module: ${moduleInfo.nameWithSpace}`); + console.error(`[GITHUB RELEASE ERROR] Version: ${moduleInfo.version}`); + console.error(`[GITHUB RELEASE ERROR] MX Version: ${moduleInfo.minimumMXVersion}`); + console.error(`[GITHUB RELEASE ERROR] Tag: ${process.env.TAG}`); + console.error(`[GITHUB RELEASE ERROR] MPK Output: ${mpkOutput}`); + console.error(`[GITHUB RELEASE ERROR] Error: ${error.message}`); + console.error(`[GITHUB RELEASE ERROR] Stack trace: ${error.stack}`); + throw error; + } } async function createGithubReleaseFrom({ title, body = "", tag, mpkOutput, isDraft = false }) { + console.log(`[GITHUB RELEASE FROM] Starting GitHub release creation`); + console.log(`[GITHUB RELEASE FROM] Title: ${title}`); + console.log(`[GITHUB RELEASE FROM] Tag: ${tag}`); + console.log(`[GITHUB RELEASE FROM] MPK Output: ${mpkOutput}`); + console.log(`[GITHUB RELEASE FROM] Draft: ${isDraft}`); + console.log(`[GITHUB RELEASE FROM] Body length: ${body?.length || 0} characters`); + + // Validate inputs + if (!title || !tag || !mpkOutput) { + const missingParams = []; + if (!title) { + missingParams.push("title"); + } + if (!tag) { + missingParams.push("tag"); + } + if (!mpkOutput) { + missingParams.push("mpkOutput"); + } + + console.error(`[GITHUB RELEASE FROM ERROR] Missing required parameters: ${missingParams.join(", ")}`); + throw new Error(`Missing required parameters for GitHub release: ${missingParams.join(", ")}`); + } + + // Check if MPK file exists + try { + await access(mpkOutput); + console.log(`[GITHUB RELEASE FROM] MPK file exists: ${mpkOutput}`); + } catch (error) { + console.error(`[GITHUB RELEASE FROM ERROR] MPK file not found: ${mpkOutput}`); + throw new Error(`MPK file not found: ${mpkOutput}`); + } + + // Sanitize body to avoid command injection and quote issues + const sanitizedBody = body.replaceAll("'", "`").replaceAll('"', "`").replace(/\n/g, "\\n"); + const command = [ `gh release create`, `--title '${title}'`, - `--notes '${body.replaceAll("'", "`")}'`, + `--notes '${sanitizedBody}'`, isDraft ? "--draft" : "", `'${tag}'`, `'${mpkOutput}'` @@ -249,7 +413,49 @@ async function createGithubReleaseFrom({ title, body = "", tag, mpkOutput, isDra .filter(str => str !== "") .join(" "); - await execShellCommand(command); + console.log(`[GITHUB RELEASE FROM] Executing command: ${command}`); + + try { + const result = await execShellCommand(command); + console.log(`[GITHUB RELEASE FROM] Command output: ${result.trim()}`); + console.log(`[GITHUB RELEASE FROM] Successfully created GitHub release with tag: ${tag}`); + + // Verify the release was created by checking if the tag exists + try { + const verifyCommand = `gh release view '${tag}'`; + console.log(`[GITHUB RELEASE FROM] Verifying release creation: ${verifyCommand}`); + await execShellCommand(verifyCommand); + console.log(`[GITHUB RELEASE FROM] Release verification successful`); + } catch (verifyError) { + console.warn(`[GITHUB RELEASE FROM WARN] Could not verify release creation: ${verifyError.message}`); + } + + return result; + } catch (error) { + console.error(`[GITHUB RELEASE FROM ERROR] Failed to create GitHub release`); + console.error(`[GITHUB RELEASE FROM ERROR] Title: ${title}`); + console.error(`[GITHUB RELEASE FROM ERROR] Tag: ${tag}`); + console.error(`[GITHUB RELEASE FROM ERROR] MPK Output: ${mpkOutput}`); + console.error(`[GITHUB RELEASE FROM ERROR] Draft: ${isDraft}`); + console.error(`[GITHUB RELEASE FROM ERROR] Command: ${command}`); + console.error(`[GITHUB RELEASE FROM ERROR] Error: ${error.message}`); + console.error(`[GITHUB RELEASE FROM ERROR] Stack trace: ${error.stack}`); + + // Check if it's an authentication issue + if (error.message.includes("authentication") || error.message.includes("token")) { + console.error(`[GITHUB RELEASE FROM ERROR] This appears to be an authentication issue`); + console.error(`[GITHUB RELEASE FROM ERROR] Please check your GH_TOKEN environment variable`); + console.error(`[GITHUB RELEASE FROM ERROR] Or run: gh auth login`); + } + + // Check if it's a duplicate release issue + if (error.message.includes("already exists") || error.message.includes("tag name")) { + console.error(`[GITHUB RELEASE FROM ERROR] Release with tag '${tag}' might already exist`); + console.error(`[GITHUB RELEASE FROM ERROR] Check existing releases with: gh release list`); + } + + throw error; + } } function zip(src, fileName) { @@ -282,7 +488,9 @@ async function exportModuleWithWidgets(moduleName, mpkOutput, widgetsFolders) { // explicitly set the dest mode, in certain scenarios the file is read only // https://chmodcommand.com/chmod-644/ await chmod(dest, 0o644); - } catch (_) {} + } catch (chmodError) { + console.warn(`[EXPORT MODULE] Could not change file permissions for ${dest}: ${chmodError.message}`); + } await copyFile(src, dest); } // Add entries to the package.xml