UNPKG

@interopio/desktop-cli

Version:

CLI tool for setting up, building and packaging io.Connect Desktop projects

274 lines 13.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.renameAppBundleContents = renameAppBundleContents; const path_1 = require("path"); const logger_1 = require("../../utils/logger"); const config_service_1 = require("../config/config.service"); const fs_1 = require("fs"); const child_process_1 = require("child_process"); const promises_1 = require("fs/promises"); const logger = logger_1.Logger.getInstance(); /** * Renames an app bundle contents (not the bundle itself) to a new base name. This means * - Updating the executable name inside as well * - Updating the Info.plist file accordingly * - Updating all Electron Helper apps in Contents/Frameworks to match the new base name (bundle name + executable + plist) * - Copies the logo.icns file from Contents/assets/images to Contents/Resources * * @param appBundlePath Absolute path to YourApp.app * @param newBaseName Desired base name (e.g., "JPM Connect") * */ async function renameAppBundleContents(appBundlePath, newBaseName) { logger.debug(`Renaming app bundle contents to a new ${newBaseName}`); const contentsPath = (0, path_1.join)(appBundlePath, 'Contents'); await renameAppBundleExe(contentsPath); await updateInfoPlist(contentsPath); await renameElectronHelpers(appBundlePath, newBaseName.replace('.app', '')); await copyLogoToResources(contentsPath); } async function renameAppBundleExe(contentsPath) { const currentExePath = (0, path_1.join)(contentsPath, "MacOS/io.Connect Desktop"); const newExePath = (0, path_1.join)(contentsPath, "MacOS", config_service_1.ConfigManager.config.mac.appBundleName.replace('.app', '')); if ((0, fs_1.existsSync)(newExePath)) { logger.debug(`Executable already correctly named: ${newExePath}`); return; } logger.debug(`Renaming executable if needed: ${currentExePath}${newExePath}`); if ((0, fs_1.existsSync)(currentExePath)) { (0, child_process_1.execSync)(`mv "${currentExePath}" "${newExePath}"`); logger.debug(`Renamed executable: ${currentExePath}${newExePath} successfully!`); } else { logger.warn(`Executable not found for renaming: ${currentExePath}`); } } /** * Rename all Helper apps in Contents/Frameworks to match newBaseName. * Handles any existing helper app names (e.g., "Electron Helper", "io.Connect Desktop Helper", etc.) * Renames executables in MacOS to match the new base name as well. * Only renames when the base name differs. * Also updates the inner Info.plist files accordingly. * * @param appBundlePath Absolute path to YourApp.app * @param newBaseName Desired base name (e.g., "JPM Connect") */ async function renameElectronHelpers(appBundlePath, newBaseName) { try { logger.debug(`Renaming Electron Helper apps to match: ${newBaseName}`); const frameworksPath = (0, path_1.join)(appBundlePath, 'Contents', 'Frameworks'); if (!(0, fs_1.existsSync)(frameworksPath)) { logger.debug('No Frameworks directory found - skipping Electron Helper renaming'); return; } // Find all Helper app bundles in Frameworks (they may have various names) const frameworkItems = (0, fs_1.readdirSync)(frameworksPath, { withFileTypes: true }); const helperApps = frameworkItems .filter(item => item.isDirectory() && item.name.includes('Helper') && item.name.endsWith('.app')) .map(item => item.name); if (helperApps.length === 0) { logger.debug('No Helper apps found in Frameworks directory'); return; } logger.debug(`Found ${helperApps.length} Helper apps to rename: ${helperApps.join(', ')}`); for (const helperAppName of helperApps) { const currentHelperPath = (0, path_1.join)(frameworksPath, helperAppName); // Determine the new helper app name based on the current helper type const newHelperAppName = getNewHelperAppName(helperAppName, newBaseName); if (newHelperAppName === helperAppName) { logger.debug(`Helper app ${helperAppName} already has correct name - skipping`); continue; } const newHelperPath = (0, path_1.join)(frameworksPath, newHelperAppName); try { // Rename the helper app bundle logger.debug(`Renaming helper app: ${helperAppName}${newHelperAppName}`); (0, child_process_1.execSync)(`mv "${currentHelperPath}" "${newHelperPath}"`); // Update the executable inside MacOS folder await renameHelperExecutable(newHelperPath, newBaseName); // Update the Info.plist inside the helper app await updateHelperInfoPlist(newHelperPath, newBaseName); logger.debug(`Successfully renamed helper app: ${helperAppName}${newHelperAppName}`); } catch (error) { logger.error(`Failed to rename helper app ${helperAppName}: ${error}`); // Continue with other helpers even if one fails } } logger.debug(`Completed renaming Electron Helper apps`); } catch (error) { logger.error(`Failed to rename Electron Helper apps: ${error}`); throw error; } } /** * Generate the new helper app name based on the helper type and new base name */ function getNewHelperAppName(currentHelperName, newBaseName) { // Helper patterns can be: // - "Electron Helper.app" // - "Electron Helper (GPU).app" // - "io.Connect Desktop Helper.app" // - "io.Connect Desktop Helper (GPU).app" // - "SomeApp Helper.app" // - "SomeApp Helper (Plugin).app" // Extract the suffix (e.g., " (GPU)", " (Plugin)", etc.) and the word "Helper" const helperMatch = currentHelperName.match(/^(.+?)\s+Helper(\s\([^)]+\))?\.app$/); if (helperMatch) { const suffix = helperMatch[2] || ''; // The part like " (GPU)" or empty string return `${newBaseName} Helper${suffix}.app`; } // Fallback: if we can't parse the pattern, try to preserve any suffix after "Helper" const suffixMatch = currentHelperName.match(/Helper(\s\([^)]+\))?\.app$/); const suffix = suffixMatch ? suffixMatch[1] || '' : ''; return `${newBaseName} Helper${suffix}.app`; } /** * Rename the executable inside the helper app's MacOS folder */ async function renameHelperExecutable(helperAppPath, newBaseName) { const macOSPath = (0, path_1.join)(helperAppPath, 'Contents', 'MacOS'); if (!(0, fs_1.existsSync)(macOSPath)) { logger.debug(`No MacOS directory found in helper app: ${helperAppPath}`); return; } const macOSItems = (0, fs_1.readdirSync)(macOSPath); const executables = macOSItems.filter(item => { const itemPath = (0, path_1.join)(macOSPath, item); try { const stats = (0, fs_1.statSync)(itemPath); return stats.isFile() && (stats.mode & 0o111) !== 0; // Check if executable } catch { return false; } }); for (const executable of executables) { const currentExePath = (0, path_1.join)(macOSPath, executable); // Determine new executable name based on pattern const newExeName = getNewHelperExecutableName(executable, newBaseName); if (newExeName === executable) { logger.debug(`Helper executable ${executable} already has correct name`); continue; } const newExePath = (0, path_1.join)(macOSPath, newExeName); try { (0, child_process_1.execSync)(`mv "${currentExePath}" "${newExePath}"`); logger.debug(`Renamed helper executable: ${executable}${newExeName}`); } catch (error) { logger.error(`Failed to rename helper executable ${executable}: ${error}`); } } } /** * Generate the new helper executable name */ function getNewHelperExecutableName(currentExeName, newBaseName) { // Parse the current executable name to extract the suffix // Examples: // - "Electron Helper" // - "Electron Helper (GPU)" // - "io.Connect Desktop Helper" // - "io.Connect Desktop Helper (Plugin)" const helperMatch = currentExeName.match(/^(.+?)\s+Helper(\s\([^)]+\))?$/); if (helperMatch) { const suffix = helperMatch[2] || ''; // The part like " (GPU)" or empty string return `${newBaseName} Helper${suffix}`; } // Fallback patterns for specific suffixes if (currentExeName.includes('Helper (GPU)')) { return `${newBaseName} Helper (GPU)`; } else if (currentExeName.includes('Helper (Plugin)')) { return `${newBaseName} Helper (Plugin)`; } else if (currentExeName.includes('Helper (Renderer)')) { return `${newBaseName} Helper (Renderer)`; } else if (currentExeName.includes('Helper')) { return `${newBaseName} Helper`; } // If no recognized pattern, keep original name return currentExeName; } async function updateInfoPlist(contentsPath) { logger.debug(`Patching Info.plist for iocd component on macOS`); const plistPath = (0, path_1.join)(contentsPath, "Info.plist"); const bundleNameWithoutApp = config_service_1.ConfigManager.config.mac.appBundleName.replace('.app', ''); const plist = require("plist"); const info = plist.parse((0, fs_1.readFileSync)(plistPath, "utf8")); // replace any special characters with dots and lowercase the name from package.json info.CFBundleIdentifier = config_service_1.ConfigManager.config.mac.appBundleId; // this needs to match the name of the app bundle, so we leave it unchanged; some day we might rename the app bundle to match the product name info.CFBundleName = bundleNameWithoutApp; info.CFBundleExecutable = bundleNameWithoutApp; info.CFBundleDisplayName = bundleNameWithoutApp; info.CFBundleVersion = config_service_1.ConfigManager.config.version; info.CFBundleShortVersionString = config_service_1.ConfigManager.config.version; info.CFBundleIconFile = "logo.icns"; (0, fs_1.writeFileSync)(plistPath, plist.build(info), "utf8"); logger.debug(`Patched Info.plist successfully!`); } /** * Update the Info.plist file inside the helper app */ async function updateHelperInfoPlist(helperAppPath, newBaseName) { const infoPlistPath = (0, path_1.join)(helperAppPath, 'Contents', 'Info.plist'); const plist = require("plist"); logger.debug(`Patching ${infoPlistPath} for helper app`); if (!(0, fs_1.existsSync)(infoPlistPath)) { logger.debug(`No Info.plist found in helper app: ${helperAppPath}`); return; } try { const plistContent = (0, fs_1.readFileSync)(infoPlistPath, 'utf8'); const plistData = plist.parse(plistContent); // Update relevant fields if (plistData.CFBundleName) { const newBundleName = getNewHelperExecutableName(plistData.CFBundleName, newBaseName); plistData.CFBundleName = newBundleName; } if (plistData.CFBundleDisplayName) { const newDisplayName = getNewHelperExecutableName(plistData.CFBundleDisplayName, newBaseName); plistData.CFBundleDisplayName = newDisplayName; } if (plistData.CFBundleExecutable) { const newExecutableName = getNewHelperExecutableName(plistData.CFBundleExecutable, newBaseName); plistData.CFBundleExecutable = newExecutableName; } // Update bundle identifier to reflect new name if (plistData.CFBundleIdentifier) { const mainIdentifier = config_service_1.ConfigManager.config.mac.appBundleId; // If the main bundle identifier is "com.interopio.io.connect.desktop", // then helper might be "com.interopio.io.connect.desktop.helper" // or "com.interopio.io.connect.desktop.helper.GPU", etc. // get suffix from the string in () in the new bundle name const suffixMatch = plistData.CFBundleName.indexOf('(') !== -1 ? plistData.CFBundleName.match(/\(([^)]+)\)/) : null; const suffix = suffixMatch ? `helper.${suffixMatch[1]}` : 'helper'; const identifier = `${mainIdentifier}.${suffix}`; // Replace any old app name in bundle identifier with new name plistData.CFBundleIdentifier = identifier; } // Write updated plist const updatedPlistContent = plist.build(plistData); (0, fs_1.writeFileSync)(infoPlistPath, updatedPlistContent, 'utf8'); logger.debug(`Patched Info.plist for helper app: ${helperAppPath}`); } catch (error) { logger.error(`Failed to update helper Info.plist at ${infoPlistPath}: ${error}`); } } /** Copies the logo.icns file from Contents/assets/images to Contents/Resources */ async function copyLogoToResources(contentsPath) { const sourcePath = (0, path_1.join)(contentsPath, "assets/images/logo.icns"); const destPath = (0, path_1.join)(contentsPath, "Resources/logo.icns"); if (!(0, fs_1.existsSync)(sourcePath)) { logger.warn(`Source logo.icns not found: ${sourcePath}`); return; } await (0, promises_1.copyFile)(sourcePath, destPath); logger.debug(`Copied logo.icns to Resources: ${destPath}`); } //# sourceMappingURL=macOS.helper.js.map