UNPKG

@interopio/desktop-cli

Version:

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

346 lines 16.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.applyModifications = applyModifications; const fs_1 = require("fs"); const path_1 = require("path"); const child_process_1 = require("child_process"); const logger_1 = require("../../utils/logger"); const config_service_1 = require("../config/config.service"); const deep_merge_1 = require("../../utils/deep.merge"); const windows_helper_1 = require("./windows.helper"); // Conditionally import macOS helper only on macOS let macOSHelper = null; if (process.platform === 'darwin') { try { macOSHelper = require('./macOS.helper'); } catch (error) { // Will be handled later when needed } } const logger = logger_1.Logger.getInstance(); async function applyModifications() { try { logger.info('Applying modifications...'); const modificationsDir = (0, path_1.join)(process.cwd(), 'modifications'); const componentsDir = (0, path_1.join)(process.cwd(), 'components'); if (!(0, fs_1.existsSync)(modificationsDir)) { logger.debug('No modifications directory found - skipping modifications'); return; } if (!(0, fs_1.existsSync)(componentsDir)) { logger.debug('No components directory found - skipping modifications'); return; } if (process.platform === "win32") { logger.debug('Running on Windows, proceeding with Windows-specific modifications'); await (0, windows_helper_1.modifyExe)(); logger.debug('Windows-specific modifications applied successfully!'); } // Get all component directories in modifications const componentDirs = (0, fs_1.readdirSync)(modificationsDir, { withFileTypes: true }) .filter(dirent => dirent.isDirectory()) .map(dirent => dirent.name); if (componentDirs.length === 0) { logger.debug('No component modifications found'); return; } for (const componentName of componentDirs) { const modComponentDir = (0, path_1.join)(modificationsDir, componentName); const baseTargetComponentDir = (0, path_1.join)(componentsDir, componentName); if (!(0, fs_1.existsSync)(baseTargetComponentDir)) { logger.warn(`Target component directory ${componentName} not found - skipping`); continue; } // Handle macOS app bundle structure for iocd component if (process.platform === 'darwin' && componentName === 'iocd') { await processIocdComponentOnMacOS(modComponentDir, baseTargetComponentDir); } else { const targetComponentDir = getActualComponentPath(baseTargetComponentDir, componentName); if (!(0, fs_1.existsSync)(targetComponentDir)) { logger.warn(`Target component path ${(0, path_1.relative)(process.cwd(), targetComponentDir)} not found - skipping`); continue; } logger.info(`Applying modifications for component: ${componentName}`); await processModificationDirectory(modComponentDir, targetComponentDir); } logger.info(`Modifications applied successfully for component: ${componentName}`); } logger.info('All Modifications applied successfully!'); } catch (error) { logger.error(`Failed to apply modifications: ${error}`); throw error; } } /** * Recursively process a modification directory and apply changes to the target */ async function processModificationDirectory(modDir, targetDir) { const items = (0, fs_1.readdirSync)(modDir, { withFileTypes: true }); logger.debug(`Processing modifications in ${modDir} with targetDir ${targetDir}`); for (const item of items) { logger.debug(`Processing item ${item.name}`); const modItemPath = (0, path_1.join)(modDir, item.name); const targetItemPath = (0, path_1.join)(targetDir, item.name); if (item.isDirectory()) { await processDirectory(modItemPath, targetItemPath); } else { await processFile(modItemPath, targetItemPath, item.name); } } } /** * Process a directory modification */ async function processDirectory(modDirPath, targetDirPath) { logger.debug(`Processing directory ${modDirPath} with targetDir ${targetDirPath}`); // Check for .replace marker in the modification directory const replaceMarkerPath = (0, path_1.join)(modDirPath, '.replace'); const hasReplaceMarker = (0, fs_1.existsSync)(replaceMarkerPath); // Check for .delete marker in the modification directory const deleteMarkerPath = (0, path_1.join)(modDirPath, '.delete'); const hasDeleteMarker = (0, fs_1.existsSync)(deleteMarkerPath); if (hasDeleteMarker) { // Delete the entire target directory if ((0, fs_1.existsSync)(targetDirPath)) { logger.debug(` Deleting directory: ${(0, path_1.relative)(process.cwd(), targetDirPath)}`); (0, fs_1.rmSync)(targetDirPath, { recursive: true, force: true }); logger.debug(`Deleted directory: ${(0, path_1.relative)(process.cwd(), targetDirPath)}`); } return; } if (hasReplaceMarker) { // Replace the entire directory if ((0, fs_1.existsSync)(targetDirPath)) { logger.debug(`Replacing directory: ${(0, path_1.relative)(process.cwd(), targetDirPath)}`); (0, fs_1.rmSync)(targetDirPath, { recursive: true, force: true }); } // Create target directory (0, fs_1.mkdirSync)(targetDirPath, { recursive: true }); // Copy all files except the .replace marker const items = (0, fs_1.readdirSync)(modDirPath, { withFileTypes: true }); for (const item of items) { if (item.name === '.replace') continue; const srcPath = (0, path_1.join)(modDirPath, item.name); const destPath = (0, path_1.join)(targetDirPath, item.name); if (item.isDirectory()) { await copyDirectoryRecursive(srcPath, destPath); } else { ensureDirectoryExists((0, path_1.dirname)(destPath)); (0, fs_1.copyFileSync)(srcPath, destPath); logger.debug(`Copied file: ${(0, path_1.relative)(process.cwd(), destPath)}`); } } logger.debug(`Replaced directory: ${(0, path_1.relative)(process.cwd(), targetDirPath)}`); } else { // Normal directory processing - ensure target exists and recurse if (!(0, fs_1.existsSync)(targetDirPath)) { (0, fs_1.mkdirSync)(targetDirPath, { recursive: true }); } await processModificationDirectory(modDirPath, targetDirPath); } } /** * Process a file modification */ async function processFile(modFilePath, targetFilePath, fileName) { logger.debug(`Processing file ${fileName} with target ${targetFilePath} and modFilePath ${modFilePath}`); if (fileName.endsWith('.gitkeep')) { // ignore .gitkeep files return; } // Handle .delete files if (fileName.endsWith('.delete')) { const targetFile = fileName.slice(0, -7); // Remove '.delete' suffix const actualTargetPath = (0, path_1.join)((0, path_1.dirname)(targetFilePath), targetFile); logger.debug(`Deleting file: ${(0, path_1.relative)(process.cwd(), actualTargetPath)}`); if ((0, fs_1.existsSync)(actualTargetPath)) { (0, fs_1.rmSync)(actualTargetPath, { force: true }); logger.debug(`Deleted file: ${(0, path_1.relative)(process.cwd(), actualTargetPath)}`); } else { logger.debug(`File not found: ${(0, path_1.relative)(process.cwd(), actualTargetPath)}`); } return; } // Handle .json.merge files // names can be like system.json.merge or system.json.merge-suffix if (fileName.endsWith('.json.merge') || fileName.includes('.json.merge-')) { // get the base filename without the .merge or .merge-suffix const baseFileName = fileName.split('.json.merge')[0]; const targetFile = baseFileName + '.json'; // Remove '.merge' and ensure .json const actualTargetPath = (0, path_1.join)((0, path_1.dirname)(targetFilePath), targetFile); await mergeJsonFile(modFilePath, actualTargetPath); return; } // Handle regular file replacement ensureDirectoryExists((0, path_1.dirname)(targetFilePath)); (0, fs_1.copyFileSync)(modFilePath, targetFilePath); logger.debug(`Copied file: ${(0, path_1.relative)(process.cwd(), targetFilePath)}`); } /** * Merge a JSON modification file with the target JSON file */ async function mergeJsonFile(mergePath, targetPath) { try { logger.debug(`Merging JSON file: ${mergePath} into ${targetPath}`); // Read the merge file const mergeContent = (0, fs_1.readFileSync)(mergePath, 'utf-8'); const mergeData = JSON.parse(mergeContent); let targetData = {}; // Read existing target file if it exists if ((0, fs_1.existsSync)(targetPath)) { const targetContent = (0, fs_1.readFileSync)(targetPath, 'utf-8'); targetData = JSON.parse(targetContent); } // Deep merge the objects const mergedData = (0, deep_merge_1.deepMerge)(targetData, mergeData); // Write the merged result ensureDirectoryExists((0, path_1.dirname)(targetPath)); (0, fs_1.writeFileSync)(targetPath, JSON.stringify(mergedData, null, 2), 'utf-8'); logger.debug(`Merged JSON file: ${(0, path_1.relative)(process.cwd(), targetPath)}`); } catch (error) { logger.error(`Failed to merge JSON file ${mergePath}: ${error}`); throw error; } } /** * Recursively copy a directory */ async function copyDirectoryRecursive(srcDir, destDir) { (0, fs_1.mkdirSync)(destDir, { recursive: true }); const items = (0, fs_1.readdirSync)(srcDir, { withFileTypes: true }); for (const item of items) { const srcPath = (0, path_1.join)(srcDir, item.name); const destPath = (0, path_1.join)(destDir, item.name); if (item.isDirectory()) { await copyDirectoryRecursive(srcPath, destPath); } else { (0, fs_1.copyFileSync)(srcPath, destPath); logger.debug(`📄 Copied file: ${(0, path_1.relative)(process.cwd(), destPath)}`); } } } /** * Ensure a directory exists */ function ensureDirectoryExists(dirPath) { if (!(0, fs_1.existsSync)(dirPath)) { (0, fs_1.mkdirSync)(dirPath, { recursive: true }); } } /** * Get the actual component path, handling platform-specific structures * On macOS, iocd component is packaged as an app bundle with extra directory levels */ function getActualComponentPath(baseComponentPath, componentName) { // Handle macOS app bundle structure for iocd component if (process.platform === 'darwin' && componentName === 'iocd') { // Check for app bundle structure: iocd/{BUNDLE_NAME}/Contents/ const items = (0, fs_1.readdirSync)(baseComponentPath, { withFileTypes: true }); const appBundle = items.find(item => item.isDirectory() && item.name.endsWith('.app')); if (appBundle) { const appBundlePath = (0, path_1.join)(baseComponentPath, appBundle.name); const contentsPath = (0, path_1.join)(appBundlePath, 'Contents'); // Verify the Contents directory exists if ((0, fs_1.existsSync)(contentsPath)) { logger.debug(`Using macOS app bundle path: ${(0, path_1.relative)(process.cwd(), contentsPath)}`); return contentsPath; } } } // For other platforms or components, use the base path return baseComponentPath; } /** * Special handling for iocd component on macOS app bundles. * * This function handles the complex process of applying modifications to macOS .app bundles, * which require special handling due to code signing and permission constraints. The strategy * involves copying the entire app bundle to a temporary location, applying modifications there, * and then moving it back to replace the original. * * Process: * 1. Locates the target app bundle (with fallback to "io.Connect Desktop.app") * 2. Creates a temporary copy of the entire app bundle * 3. Applies modifications to the Contents directory of the temp copy * 4. Renames app bundle contents (executable, Info.plist, Helper apps) using macOS helper * 5. Replaces the original app bundle with the modified version * 6. Cleans up temporary files * * @param modComponentDir - Path to the modifications directory for the iocd component * @param baseTargetComponentDir - Path to the base target component directory (components/iocd) * @throws {Error} When macOS helper is not available or app bundle cannot be found * @throws {Error} When file operations fail during the modification process */ async function processIocdComponentOnMacOS(modComponentDir, baseTargetComponentDir) { logger.info('Applying modifications for component: iocd (macOS app bundle)'); const appBundleName = config_service_1.ConfigManager.config.mac.appBundleName; const targetAppBundlePath = (0, path_1.join)(baseTargetComponentDir, appBundleName); // where we expect the app bundle to be at the end let currentAppBundlePath = targetAppBundlePath; // where the app bundle currently is // Check if macOS helper is available if (!macOSHelper) { logger.error('macOS helper module is required but not available'); throw new Error('macOS helper module is required but not available'); } if (!(0, fs_1.existsSync)(targetAppBundlePath)) { // APP BUNDLE NOT FOUND, TRY io.Connect Desktop.app as fallback const fallbackAppBundlePath = (0, path_1.join)(baseTargetComponentDir, "io.Connect Desktop.app"); if ((0, fs_1.existsSync)(fallbackAppBundlePath)) { logger.debug(`Using fallback app bundle: ${(0, path_1.relative)(process.cwd(), fallbackAppBundlePath)}`); currentAppBundlePath = fallbackAppBundlePath; } else { throw new Error(`App bundle path not found: ${currentAppBundlePath}`); } } // Strategy: Copy the entire app bundle to a temp location, modify it there, then copy back const tempDir = (0, path_1.join)(process.cwd(), 'temp'); const tempAppPath = (0, path_1.join)(tempDir, `${appBundleName}-temp`); try { // Create temp directory if (!(0, fs_1.existsSync)(tempDir)) { (0, fs_1.mkdirSync)(tempDir, { recursive: true }); } // Remove any existing temp app if ((0, fs_1.existsSync)(tempAppPath)) { (0, fs_1.rmSync)(tempAppPath, { recursive: true, force: true }); } logger.debug(`Copying app bundle to temp location: ${tempAppPath}`); // Copy the entire app bundle to temp (this should work since it's not constrained by permissions) (0, child_process_1.execSync)(`cp -R "${currentAppBundlePath}" "${tempAppPath}"`, { stdio: 'pipe' }); // Apply modifications to the temp copy const tempContentsPath = (0, path_1.join)(tempAppPath, 'Contents'); await processModificationDirectory(modComponentDir, tempContentsPath); // rename the app bundle name await macOSHelper.renameAppBundleContents(tempAppPath, appBundleName); // Remove the original app bundle logger.debug(`Removing original app bundle: ${currentAppBundlePath}`); (0, fs_1.rmSync)(currentAppBundlePath, { recursive: true, force: true }); // Move the modified temp app back logger.debug(`Moving modified app bundle back: ${targetAppBundlePath}`); (0, child_process_1.execSync)(`mv "${tempAppPath}" "${targetAppBundlePath}"`, { stdio: 'pipe' }); logger.debug('Successfully applied modifications to macOS app bundle'); } catch (error) { logger.error(`Failed to process iocd component on macOS: ${error}`); // Cleanup temp directory if it exists if ((0, fs_1.existsSync)(tempAppPath)) { try { (0, fs_1.rmSync)(tempAppPath, { recursive: true, force: true }); } catch (cleanupError) { logger.warn(`Could not clean up temp directory: ${cleanupError}`); } } throw error; } } //# sourceMappingURL=modifications.service.js.map