repoweaver
Version:
A GitHub App that skillfully weaves multiple templates together to create and update repositories with intelligent merge strategies
162 lines (156 loc) • 7.25 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.GitHubTemplateManager = void 0;
const merge_strategy_registry_1 = require("./merge-strategy-registry");
class GitHubTemplateManager {
constructor(client) {
this.client = client;
this.mergeRegistry = new merge_strategy_registry_1.MergeStrategyRegistry();
}
async processTemplate(template, targetOwner, targetRepo, excludePatterns = [], mergeStrategy = 'merge', mergeStrategies = [], plugins = []) {
const result = {
success: true,
template,
filesProcessed: 0,
errors: [],
};
try {
// Load plugins
for (const plugin of plugins) {
await this.mergeRegistry.loadPlugin(plugin);
}
// Get template files from GitHub
const templateFiles = await this.client.getTemplateFiles(template);
// Filter out excluded files
const filteredFiles = this.filterFiles(templateFiles, excludePatterns);
// Process files based on merge strategy
await this.processFiles(filteredFiles, targetOwner, targetRepo, mergeStrategy, mergeStrategies, result);
}
catch (error) {
result.success = false;
result.errors.push(`Template processing failed: ${error}`);
}
return result;
}
filterFiles(files, excludePatterns) {
return files.filter((file) => {
// Skip directories in processing
if (file.type === 'dir') {
return false;
}
// Apply exclude patterns
return !this.shouldExclude(file.path, excludePatterns);
});
}
shouldExclude(filePath, excludePatterns) {
return excludePatterns.some((pattern) => {
const regex = new RegExp(pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*'));
return regex.test(filePath);
});
}
async processFiles(files, targetOwner, targetRepo, mergeStrategy, mergeStrategies, result) {
const branch = `boots-strapper-update-${Date.now()}`;
try {
// Create a new branch for the template updates
await this.client.createBranch(targetOwner, targetRepo, branch);
for (const file of files) {
try {
// Determine merge strategy for this file
const defaultMergeStrategy = typeof mergeStrategy === 'string' ? { type: mergeStrategy } : mergeStrategy;
const fileStrategy = await this.mergeRegistry.resolveStrategyForFile(file.path, mergeStrategies, defaultMergeStrategy);
const shouldProcess = await this.shouldProcessFile(targetOwner, targetRepo, file.path, fileStrategy.name);
if (shouldProcess) {
let content = file.content;
// Get existing content if file exists
let existingContent = '';
try {
const existingFiles = await this.client.getRepositoryContents(targetOwner, targetRepo, file.path);
const existingFile = existingFiles.find((f) => f.path === file.path && f.type === 'file');
existingContent = existingFile?.content || '';
}
catch (error) {
// File doesn't exist, which is fine
}
// Apply merge strategy
if (existingContent) {
const mergeResult = await fileStrategy.merge({
filePath: file.path,
templateName: result.template.name,
existingContent,
newContent: file.content,
});
if (mergeResult.success) {
content = mergeResult.content;
// Track warnings and conflicts
if (mergeResult.warnings) {
result.errors.push(...mergeResult.warnings.map((w) => `Warning: ${w}`));
}
if (mergeResult.conflicts) {
result.errors.push(...mergeResult.conflicts.map((c) => `Conflict: ${c}`));
}
}
else {
result.errors.push(`Merge failed for ${file.path}, using new content`);
}
}
await this.client.createOrUpdateFile(targetOwner, targetRepo, file.path, content, `Update ${file.path} from template ${result.template.name} using ${fileStrategy.name} strategy`, branch);
result.filesProcessed++;
}
}
catch (error) {
result.errors.push(`Failed to process file ${file.path}: ${error}`);
}
}
// Create a pull request with the changes
if (result.filesProcessed > 0) {
const prNumber = await this.client.createPullRequest(targetOwner, targetRepo, `Update repository from template: ${result.template.name}`, this.generatePullRequestBody(result.template, result), branch, 'main');
result.pullRequestNumber = prNumber;
}
}
catch (error) {
result.success = false;
result.errors.push(`Branch creation or PR failed: ${error}`);
}
}
async shouldProcessFile(owner, repo, filePath, strategyName) {
if (strategyName === 'overwrite') {
return true;
}
try {
// Check if file exists
await this.client.getRepositoryContents(owner, repo, filePath);
// File exists
if (strategyName === 'skip') {
return false;
}
// All other strategies process existing files
return true;
}
catch (error) {
// File doesn't exist, so we can create it
return true;
}
}
async cleanup() {
await this.mergeRegistry.cleanup();
}
generatePullRequestBody(template, result) {
return `
## Template Update
This pull request updates the repository with changes from the template: **${template.name}**
**Template Details:**
- Repository: ${template.url}
- Branch: ${template.branch || 'main'}
${template.subDirectory ? `- Subdirectory: ${template.subDirectory}` : ''}
**Changes:**
- ${result.filesProcessed} files processed
${result.errors.length > 0 ? `- ${result.errors.length} errors encountered` : ''}
**Files Modified:**
<!-- This will be populated with the actual file list -->
---
*This PR was automatically generated by [RepoWeaver](https://github.com/apps/repoweaver)*
`.trim();
}
}
exports.GitHubTemplateManager = GitHubTemplateManager;
//# sourceMappingURL=github-template-manager.js.map