UNPKG

repoweaver

Version:

A GitHub App that skillfully weaves multiple templates together to create and update repositories with intelligent merge strategies

387 lines 15 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.MergeStrategyRegistry = void 0; const fs = __importStar(require("fs/promises")); const path = __importStar(require("path")); class MergeStrategyRegistry { constructor() { this.strategies = new Map(); this.plugins = new Map(); this.customStrategies = new Map(); // Cache for compiled regex patterns this.patternRegexCache = new Map(); this.loadBuiltinStrategies(); } loadBuiltinStrategies() { // Load built-in merge strategies const builtinStrategies = [new OverwriteMergeStrategy(), new SkipExistingMergeStrategy(), new SimpleMergeStrategy(), new JsonMergeStrategy(), new PackageJsonMergeStrategy(), new MarkdownMergeStrategy(), new YamlMergeStrategy(), new ConfigMergeStrategy(), new CodeMergeStrategy()]; builtinStrategies.forEach((strategy) => { this.strategies.set(strategy.name, strategy); }); } async loadPlugin(pluginName) { try { // Try to load from node_modules first const pluginPath = require.resolve(`repoweaver-plugin-${pluginName}`); const pluginModule = await Promise.resolve(`${pluginPath}`).then(s => __importStar(require(s))); const plugin = pluginModule.default || pluginModule; if (plugin.initialize) { await plugin.initialize(); } // Register plugin strategies plugin.strategies.forEach((strategy) => { this.strategies.set(`${pluginName}:${strategy.name}`, strategy); }); this.plugins.set(pluginName, plugin); console.log(`✅ Loaded plugin: ${pluginName} (${plugin.version})`); } catch (error) { console.error(`❌ Failed to load plugin ${pluginName}:`, error); throw new Error(`Plugin ${pluginName} could not be loaded`); } } async loadCustomStrategy(implementation, name) { try { const strategyPath = path.resolve(implementation); // Check if file exists await fs.access(strategyPath); // Clear require cache to allow reloading delete require.cache[require.resolve(strategyPath)]; const strategyModule = require(strategyPath); const strategy = strategyModule.default || strategyModule; const strategyName = name || strategy.name || path.basename(implementation, path.extname(implementation)); this.customStrategies.set(strategyName, strategy); this.strategies.set(strategyName, strategy); console.log(`✅ Loaded custom strategy: ${strategyName} from ${implementation}`); } catch (error) { console.error(`❌ Failed to load custom strategy ${implementation}:`, error); throw new Error(`Custom strategy ${implementation} could not be loaded`); } } getStrategy(name) { return this.strategies.get(name); } listStrategies() { return Array.from(this.strategies.keys()); } async resolveStrategyForFile(filePath, strategies, defaultStrategy) { // Sort strategies by priority (higher priority first) const sortedStrategies = [...strategies].sort((a, b) => (b.priority || 0) - (a.priority || 0)); // Find the first matching strategy for (const strategyConfig of sortedStrategies) { if (this.matchesPatterns(filePath, strategyConfig.patterns)) { return await this.resolveStrategy(strategyConfig.strategy); } } // Fall back to default strategy if (defaultStrategy) { return await this.resolveStrategy(defaultStrategy); } // Ultimate fallback return this.getStrategy('merge') || new SimpleMergeStrategy(); } async resolveStrategy(config) { switch (config.type) { case 'overwrite': return this.getStrategy('overwrite') || new OverwriteMergeStrategy(); case 'merge': return this.getStrategy('merge') || new SimpleMergeStrategy(); case 'skip': return this.getStrategy('skip') || new SkipExistingMergeStrategy(); case 'custom': if (!config.implementation) { throw new Error('Custom strategy requires implementation path'); } await this.loadCustomStrategy(config.implementation); return this.getStrategy(path.basename(config.implementation, path.extname(config.implementation))); case 'plugin': if (!config.implementation) { throw new Error('Plugin strategy requires plugin name'); } const [pluginName, strategyName] = config.implementation.split(':'); if (!this.plugins.has(pluginName)) { await this.loadPlugin(pluginName); } return this.getStrategy(config.implementation); default: throw new Error(`Unknown strategy type: ${config.type}`); } } matchesPatterns(filePath, patterns) { return patterns.some((pattern) => { let regex = this.patternRegexCache.get(pattern); if (!regex) { regex = new RegExp(pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*')); this.patternRegexCache.set(pattern, regex); } return regex.test(filePath); }); } async cleanup() { // Cleanup plugins for (const plugin of this.plugins.values()) { if (plugin.cleanup) { await plugin.cleanup(); } } this.plugins.clear(); this.customStrategies.clear(); } } exports.MergeStrategyRegistry = MergeStrategyRegistry; // Built-in merge strategies class OverwriteMergeStrategy { constructor() { this.name = 'overwrite'; this.description = 'Overwrite existing content with new content'; } async merge(context) { return { success: true, content: context.newContent, }; } } class SkipExistingMergeStrategy { constructor() { this.name = 'skip'; this.description = 'Skip files that already exist'; } async merge(context) { return { success: true, content: context.existingContent, }; } } class SimpleMergeStrategy { constructor() { this.name = 'merge'; this.description = 'Simple merge by appending new content to existing content'; } async merge(context) { if (!context.existingContent.trim()) { return { success: true, content: context.newContent, }; } const separator = context.options?.separator || '\n\n# --- Template Update ---\n\n'; const content = context.existingContent + separator + context.newContent; return { success: true, content, warnings: ['Content was appended with separator'], }; } } class JsonMergeStrategy { constructor() { this.name = 'json'; this.description = 'Deep merge JSON objects'; } async merge(context) { try { const existingObj = JSON.parse(context.existingContent); const newObj = JSON.parse(context.newContent); const merged = this.deepMerge(existingObj, newObj); return { success: true, content: JSON.stringify(merged, null, 2), metadata: { mergedKeys: Object.keys(merged) }, }; } catch (error) { return { success: false, content: context.existingContent, warnings: [`JSON merge failed: ${error}. Using existing content.`], }; } } deepMerge(target, source) { const result = { ...target }; for (const key in source) { if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { result[key] = this.deepMerge(target[key] || {}, source[key]); } else { result[key] = source[key]; } } return result; } } class PackageJsonMergeStrategy { constructor() { this.name = 'package-json'; this.description = 'Merge package.json files with special handling for dependencies'; } async merge(context) { try { const existingPkg = JSON.parse(context.existingContent); const newPkg = JSON.parse(context.newContent); const merged = { ...existingPkg, ...newPkg, dependencies: { ...existingPkg.dependencies, ...newPkg.dependencies }, devDependencies: { ...existingPkg.devDependencies, ...newPkg.devDependencies }, peerDependencies: { ...existingPkg.peerDependencies, ...newPkg.peerDependencies }, scripts: { ...existingPkg.scripts, ...newPkg.scripts }, }; return { success: true, content: JSON.stringify(merged, null, 2), metadata: { dependenciesAdded: Object.keys(newPkg.dependencies || {}), scriptsAdded: Object.keys(newPkg.scripts || {}), }, }; } catch (error) { return { success: false, content: context.existingContent, warnings: [`package.json merge failed: ${error}`], }; } } } class MarkdownMergeStrategy { constructor() { this.name = 'markdown'; this.description = 'Merge Markdown files by appending sections'; } async merge(context) { const separator = context.options?.separator || '\n\n---\n\n'; const content = context.existingContent + separator + context.newContent; return { success: true, content, warnings: ['Markdown content was appended with separator'], }; } } class YamlMergeStrategy { constructor() { this.name = 'yaml'; this.description = 'Merge YAML files (requires js-yaml)'; } async merge(context) { try { // Try to use js-yaml if available const yaml = require('js-yaml'); const existingObj = yaml.load(context.existingContent); const newObj = yaml.load(context.newContent); const merged = this.deepMerge(existingObj, newObj); return { success: true, content: yaml.dump(merged, { indent: 2 }), }; } catch (error) { // Fallback to simple merge if js-yaml is not available const separator = context.options?.separator || '\n\n# --- Template Update ---\n\n'; return { success: true, content: context.existingContent + separator + context.newContent, warnings: ['YAML merge failed, used simple merge instead'], }; } } deepMerge(target, source) { const result = { ...target }; for (const key in source) { if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) { result[key] = this.deepMerge(target[key] || {}, source[key]); } else { result[key] = source[key]; } } return result; } } class ConfigMergeStrategy { constructor() { this.name = 'config'; this.description = 'Merge configuration files (JSON, YAML, TOML)'; } async merge(context) { const ext = path.extname(context.filePath).toLowerCase(); switch (ext) { case '.json': return new JsonMergeStrategy().merge(context); case '.yaml': case '.yml': return new YamlMergeStrategy().merge(context); case '.toml': return this.mergeToml(context); default: return new SimpleMergeStrategy().merge(context); } } async mergeToml(context) { // Simple TOML merge - could be enhanced with a proper TOML parser const separator = context.options?.separator || '\n\n# --- Template Update ---\n\n'; return { success: true, content: context.existingContent + separator + context.newContent, warnings: ['TOML merge used simple strategy'], }; } } class CodeMergeStrategy { constructor() { this.name = 'code'; this.description = 'Merge code files with conflict markers'; } async merge(context) { const conflicts = []; if (context.existingContent.trim() && context.newContent.trim()) { conflicts.push(`Potential conflict in ${context.filePath}`); } const content = `${context.existingContent}\n\n<<<<<<< existing\n${context.existingContent}\n=======\n${context.newContent}\n>>>>>>> ${context.templateName}\n`; return { success: true, content, conflicts, warnings: ['Code merge created conflict markers'], }; } } //# sourceMappingURL=merge-strategy-registry.js.map