@interopio/desktop-cli
Version:
CLI tool for setting up, building and packaging io.Connect Desktop projects
274 lines • 13.4 kB
JavaScript
;
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