UNPKG

@entro314labs/at3-toolkit

Version:

Advanced development toolkit for AT3 Stack projects

178 lines (177 loc) 6.92 kB
import { existsSync, readFileSync, writeFileSync } from "fs"; import { join } from "path"; import { parse as parseYaml, stringify as stringifyYaml } from "yaml"; export class ConfigMerger { logger; constructor(logger) { this.logger = logger; } async mergeJsonConfig(targetPath, newConfig, strategy = "merge") { if (!existsSync(targetPath)) { writeFileSync(targetPath, JSON.stringify(newConfig, null, 2)); this.logger.debug(`Created new config file: ${targetPath}`); return; } if (strategy === "overwrite") { writeFileSync(targetPath, JSON.stringify(newConfig, null, 2)); this.logger.debug(`Overwrote config file: ${targetPath}`); return; } try { const existingConfig = JSON.parse(readFileSync(targetPath, "utf8")); const mergedConfig = this.deepMerge(existingConfig, newConfig); writeFileSync(targetPath, JSON.stringify(mergedConfig, null, 2)); this.logger.debug(`Merged config file: ${targetPath}`); } catch (error) { this.logger.warn(`Failed to parse existing config ${targetPath}, overwriting`); writeFileSync(targetPath, JSON.stringify(newConfig, null, 2)); } } async mergePackageJson(targetPath, updates) { if (!existsSync(targetPath)) { throw new Error(`package.json not found at ${targetPath}`); } try { const packageJson = JSON.parse(readFileSync(targetPath, "utf8")); // Merge dependencies if (updates.dependencies) { packageJson.dependencies = { ...packageJson.dependencies, ...updates.dependencies, }; } if (updates.devDependencies) { packageJson.devDependencies = { ...packageJson.devDependencies, ...updates.devDependencies, }; } // Merge scripts if (updates.scripts) { packageJson.scripts = { ...packageJson.scripts, ...updates.scripts, }; } // Remove conflicting dependencies if (updates.removeDependencies) { for (const dep of updates.removeDependencies) { delete packageJson.dependencies?.[dep]; delete packageJson.devDependencies?.[dep]; } } // Update other fields const fieldsToUpdate = ["engines", "packageManager", "type"]; fieldsToUpdate.forEach((field) => { if (updates[field]) { packageJson[field] = updates[field]; } }); writeFileSync(targetPath, JSON.stringify(packageJson, null, 2)); this.logger.success("Updated package.json"); } catch (error) { throw new Error(`Failed to update package.json: ${error}`); } } async mergeYamlConfig(targetPath, newConfig, strategy = "merge") { if (!existsSync(targetPath)) { writeFileSync(targetPath, stringifyYaml(newConfig)); return; } if (strategy === "overwrite") { writeFileSync(targetPath, stringifyYaml(newConfig)); return; } try { const existingConfig = parseYaml(readFileSync(targetPath, "utf8")); const mergedConfig = this.deepMerge(existingConfig, newConfig); writeFileSync(targetPath, stringifyYaml(mergedConfig)); this.logger.debug(`Merged YAML config: ${targetPath}`); } catch (error) { this.logger.warn(`Failed to parse existing YAML config ${targetPath}, overwriting`); writeFileSync(targetPath, stringifyYaml(newConfig)); } } async mergeTextConfig(targetPath, newContent, strategy = "overwrite") { if (!existsSync(targetPath) || strategy === "overwrite") { writeFileSync(targetPath, newContent); return; } // For text files, we typically append or replace sections const existingContent = readFileSync(targetPath, "utf8"); // Simple merge strategy for CSS/config files if (targetPath.endsWith(".css")) { const mergedContent = this.mergeCssContent(existingContent, newContent); writeFileSync(targetPath, mergedContent); } else { // For other text files, append with separator const mergedContent = existingContent + "\n\n" + newContent; writeFileSync(targetPath, mergedContent); } } mergeCssContent(existing, newContent) { // Remove existing Tailwind imports if present const withoutOldImports = existing.replace(/@import\s+["']tailwindcss[^"']*["'];?\s*/g, ""); // Add new content at the beginning return newContent + "\n\n" + withoutOldImports.trim(); } deepMerge(target, source) { if (typeof target !== "object" || target === null) { return source; } if (typeof source !== "object" || source === null) { return target; } const result = { ...target }; for (const key in source) { if (Array.isArray(source[key])) { result[key] = Array.isArray(target[key]) ? [...new Set([...target[key], ...source[key]])] : source[key]; } else if (typeof source[key] === "object" && source[key] !== null) { result[key] = this.deepMerge(target[key] || {}, source[key]); } else { result[key] = source[key]; } } return result; } async detectConfigType(filePath) { const extension = filePath.split(".").pop()?.toLowerCase(); switch (extension) { case "json": case "jsonc": return "json"; case "yaml": case "yml": return "yaml"; case "toml": return "toml"; case "js": case "mjs": return "js"; case "ts": return "ts"; case "css": return "css"; default: return "text"; } } async backupFile(filePath, backupDir) { const fileName = filePath.split("/").pop() || "unknown"; const backupPath = join(backupDir, fileName); if (existsSync(filePath)) { const content = readFileSync(filePath); writeFileSync(backupPath, content); this.logger.debug(`Backed up: ${fileName}`); } return backupPath; } }