UNPKG

@interopio/desktop-cli

Version:

io.Connect Desktop Seed Repository CLI Tools

200 lines 8.8 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseComponentProcessor = void 0; const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const glob_1 = require("glob"); const utils_1 = require("../../utils"); /** * Base implementation of ComponentProcessor providing common functionality */ class BaseComponentProcessor { constructor(config) { this.config = config; } getComponentDir() { if (this.config.paths?.componentDir) { return path_1.default.resolve(this.config.paths.componentDir); } return utils_1.PathUtils.getComponentDir(this.config.name); } getModificationDir() { if (this.config.paths?.modificationDir) { return path_1.default.resolve(this.config.paths.modificationDir); } return utils_1.PathUtils.getModificationDir(this.config.name); } getConfigDir() { if (this.config.paths?.configDir) { return path_1.default.resolve(this.config.paths.configDir); } return path_1.default.join(this.getComponentDir(), 'config'); } async scanModifications() { const operations = []; const modDir = this.getModificationDir(); if (!(await utils_1.FileUtils.exists(modDir))) { return operations; } // Use only general overrides (path mirroring) const generalOps = await this.scanGeneralOverrides(); operations.push(...generalOps); return operations; } async applyModifications() { const operations = await this.scanModifications(); if (operations.length === 0) { utils_1.Logger.debug(`No modifications found for component: ${this.config.displayName}`); return; } utils_1.Logger.info(`Applying ${operations.length} modification(s) for ${this.config.displayName}...`); for (const operation of operations) { await this.applyOperation(operation); } } async validateModifications() { const operations = await this.scanModifications(); let isValid = true; for (const operation of operations) { if (operation.type === 'copy') { if (!(await utils_1.FileUtils.exists(operation.source))) { utils_1.Logger.error(`Source file not found for ${this.config.displayName}: ${operation.source}`); isValid = false; } } } return isValid; } async hasModifications() { const modDir = this.getModificationDir(); if (!(await utils_1.FileUtils.exists(modDir))) { return false; } try { const contents = await fs_extra_1.default.readdir(modDir); return contents.length > 0; } catch (error) { utils_1.Logger.warning(`Failed to check modifications for ${this.config.displayName}: ${error instanceof Error ? error.message : String(error)}`); return false; } } // Protected methods for subclass customization async applyOperation(operation) { try { switch (operation.type) { case 'copy': await this.applyCopy(operation); break; case 'delete': await this.applyDelete(operation); break; default: utils_1.Logger.warning(`Unknown operation type for ${this.config.displayName}: ${operation.type}`); } } catch (error) { utils_1.Logger.error(`Failed to apply ${operation.type} operation for ${this.config.displayName}: ${error instanceof Error ? error.message : String(error)}`); throw error; } } async applyCopy(operation) { await fs_extra_1.default.ensureDir(path_1.default.dirname(operation.target)); await fs_extra_1.default.copy(operation.source, operation.target); utils_1.Logger.info(`Copied file for ${this.config.displayName}: ${path_1.default.basename(operation.target)}`); } async applyDelete(operation) { if (await utils_1.FileUtils.exists(operation.target)) { await fs_extra_1.default.remove(operation.target); utils_1.Logger.info(`Deleted for ${this.config.displayName}: ${path_1.default.basename(operation.target)}`); } } /** * Scan for general override files placed directly under the component modification directory. * This enables simple path mirroring e.g. modifications/iocd/config/app.json -> components/iocd/config/app.json * No reserved directories - everything is treated as direct path mirroring. * * Behavior: * - Default: Selective file replacement (preserves existing files) * - Explicit markers for directory operations: * - <dir>.delete: Delete entire directory * - <dir>.replace: Replace entire directory (delete + copy) * - Individual file markers: * - <file>.delete: Delete specific file */ async scanGeneralOverrides() { const operations = []; const modDir = this.getModificationDir(); if (!(await utils_1.FileUtils.exists(modDir))) return operations; // Read top-level entries first to check for explicit markers let topEntries = []; try { topEntries = (await fs_extra_1.default.readdir(modDir)).map(e => path_1.default.join(modDir, e)); } catch { return operations; } const replaceDirectories = new Set(); for (const entry of topEntries) { const name = path_1.default.basename(entry); // Directory-level delete marker: <dir>.delete if (name.endsWith('.delete')) { const targetDir = path_1.default.join(this.getComponentDir(), name.replace(/\.delete$/, '')); operations.push({ type: 'delete', source: entry, target: targetDir, isDirectory: true }); continue; } // Directory-level replace marker: <dir>.replace if (name.endsWith('.replace')) { const dirName = name.replace(/\.replace$/, ''); const targetDir = path_1.default.join(this.getComponentDir(), dirName); // First delete the existing directory operations.push({ type: 'delete', source: entry, target: targetDir, isDirectory: true }); // Mark this directory for replacement (we'll copy files from the actual directory) replaceDirectories.add(dirName); continue; } } // Now glob all files const pattern = path_1.default.join(modDir, '**', '*').replace(/\\/g, '/'); const files = await (0, glob_1.glob)(pattern, { dot: true, nodir: false }); for (const filePath of files) { const relative = path_1.default.relative(modDir, filePath); if (!relative) continue; const base = path_1.default.basename(filePath); // Skip directory entries; we'll process contained files const stat = await fs_extra_1.default.stat(filePath); if (stat.isDirectory()) continue; // File delete marker: <file>.delete (handle before checking for marker files) if (base.endsWith('.delete')) { const target = path_1.default.join(this.getComponentDir(), relative.replace(/\.delete$/, '')); operations.push({ type: 'delete', source: filePath, target, isDirectory: false }); continue; } // Skip marker files (they don't contain actual content) if (base.endsWith('.replace')) { continue; } // Check if this file is part of a directory marked for replacement const topLevelDir = relative.split(path_1.default.sep)[0]; const isInReplaceDir = replaceDirectories.has(topLevelDir); // All other files are direct replacements const target = path_1.default.join(this.getComponentDir(), relative); operations.push({ type: 'copy', source: filePath, target, isDirectory: false, isDirectoryReplacement: isInReplaceDir }); } return operations; } } exports.BaseComponentProcessor = BaseComponentProcessor; //# sourceMappingURL=base-component-processor.js.map