repoweaver
Version:
A GitHub App that skillfully weaves multiple templates together to create and update repositories with intelligent merge strategies
191 lines • 8.66 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.WebhookHandler = void 0;
const crypto_1 = require("crypto");
const github_client_1 = require("./github-client");
const github_bootstrapper_1 = require("./github-bootstrapper");
class WebhookHandler {
constructor(webhookSecret, appId, privateKey, database) {
this.webhookSecret = webhookSecret;
this.appId = appId;
this.privateKey = privateKey;
this.database = database;
}
async handleWebhook(req, res) {
try {
// Verify webhook signature
if (!this.verifySignature(req)) {
res.status(401).json({ error: 'Invalid signature' });
return;
}
const event = req.headers['x-github-event'];
const payload = req.body;
console.log(`Received webhook: ${event} - ${payload.action}`);
switch (event) {
case 'installation':
await this.handleInstallation(payload);
break;
case 'installation_repositories':
await this.handleInstallationRepositories(payload);
break;
case 'push':
await this.handlePush(payload);
break;
case 'repository':
await this.handleRepository(payload);
break;
case 'pull_request':
await this.handlePullRequest(payload);
break;
default:
console.log(`Unhandled event: ${event}`);
}
res.status(200).json({ message: 'Webhook processed successfully' });
}
catch (error) {
console.error('Webhook processing error:', error);
res.status(500).json({ error: 'Internal server error' });
}
}
verifySignature(req) {
const signature = req.headers['x-hub-signature-256'];
if (!signature) {
return false;
}
const hmac = (0, crypto_1.createHmac)('sha256', this.webhookSecret);
hmac.update(JSON.stringify(req.body));
const expectedSignature = `sha256=${hmac.digest('hex')}`;
return signature === expectedSignature;
}
async handleInstallation(payload) {
if (!payload.installation)
return;
const { installation } = payload;
switch (payload.action) {
case 'created':
await this.database.createInstallation({
id: installation.id,
account: installation.account.login,
accountType: installation.account.type
});
console.log(`Installation created for ${installation.account.login}`);
break;
case 'deleted':
await this.database.deleteInstallation(installation.id);
console.log(`Installation deleted for ${installation.account.login}`);
break;
case 'suspend':
await this.database.suspendInstallation(installation.id);
console.log(`Installation suspended for ${installation.account.login}`);
break;
case 'unsuspend':
await this.database.unsuspendInstallation(installation.id);
console.log(`Installation unsuspended for ${installation.account.login}`);
break;
}
}
async handleInstallationRepositories(payload) {
if (!payload.installation || !payload.repositories)
return;
const { installation, repositories } = payload;
switch (payload.action) {
case 'added':
for (const repo of repositories) {
await this.database.addRepositoryToInstallation(installation.id, repo.name, repo.full_name);
}
console.log(`Added ${repositories.length} repositories to installation ${installation.id}`);
break;
case 'removed':
for (const repo of repositories) {
await this.database.removeRepositoryFromInstallation(installation.id, repo.name);
}
console.log(`Removed ${repositories.length} repositories from installation ${installation.id}`);
break;
}
}
async handlePush(payload) {
if (!payload.repository || !payload.installation)
return;
const { repository, installation } = payload;
// Check if this is a push to a template repository
const templateConfigs = await this.database.getTemplateConfigurations(repository.full_name);
if (templateConfigs.length > 0) {
console.log(`Template repository ${repository.full_name} was updated`);
// Queue updates for all repositories using this template
for (const config of templateConfigs) {
await this.queueTemplateUpdate(installation.id, config.targetRepository, repository.full_name);
}
}
}
async handleRepository(payload) {
if (!payload.repository || !payload.installation)
return;
const { repository, installation } = payload;
switch (payload.action) {
case 'created':
// Check if this repository should be auto-configured with templates
const installationConfig = await this.database.getInstallationConfig(installation.id);
if (installationConfig?.autoConfigureTemplates) {
await this.autoConfigureRepository(installation.id, repository);
}
break;
case 'deleted':
await this.database.deleteRepositoryConfig(repository.full_name);
break;
}
}
async handlePullRequest(payload) {
if (!payload.repository || !payload.installation)
return;
// Handle PR events related to template updates
// This could include auto-merging approved template updates
console.log(`Pull request ${payload.action} in ${payload.repository.full_name}`);
}
async queueTemplateUpdate(installationId, targetRepository, templateRepository) {
try {
const client = new github_client_1.GitHubClient(this.appId, this.privateKey, installationId);
const bootstrapper = new github_bootstrapper_1.GitHubBootstrapper(client);
// Get repository configuration
const [owner, repo] = targetRepository.split('/');
const templates = await bootstrapper.getRepositoryTemplates(owner, repo);
// Find the template that was updated
const templateConfig = templates.find(t => t.includes(templateRepository));
if (!templateConfig) {
console.log(`No template configuration found for ${templateRepository}`);
return;
}
// Queue the update job
await this.database.queueJob({
type: 'template_update',
installationId,
targetRepository,
templateRepository,
status: 'pending',
createdAt: new Date()
});
console.log(`Queued template update for ${targetRepository} from ${templateRepository}`);
}
catch (error) {
console.error(`Failed to queue template update: ${error}`);
}
}
async autoConfigureRepository(installationId, repository) {
try {
const client = new github_client_1.GitHubClient(this.appId, this.privateKey, installationId);
const bootstrapper = new github_bootstrapper_1.GitHubBootstrapper(client);
// Get default templates for this installation
const installationConfig = await this.database.getInstallationConfig(installationId);
const defaultTemplates = installationConfig?.defaultTemplates || [];
if (defaultTemplates.length > 0) {
// Save default templates to the new repository
await bootstrapper.saveRepositoryTemplates(repository.owner.login, repository.name, defaultTemplates);
console.log(`Auto-configured ${repository.full_name} with ${defaultTemplates.length} templates`);
}
}
catch (error) {
console.error(`Failed to auto-configure repository: ${error}`);
}
}
}
exports.WebhookHandler = WebhookHandler;
//# sourceMappingURL=webhook-handler.js.map