@interopio/desktop-cli
Version:
io.Connect Desktop Seed Repository CLI Tools
200 lines • 8.8 kB
JavaScript
;
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