Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .zed/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"code_actions_on_format": {
"source.fixAll": true,
"source.organizeImports": true
},
"lsp": {
"vtsls": {
"settings": {
"javascript": {
"suggest": {
"completeJSDocs": true
}
},
"typescript": {
"suggest": {
"completeJSDocs": true
}
}
}
}
}
}
23 changes: 23 additions & 0 deletions src/events/cleanup/branchCleanup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { defineEvent } from "../../lib/eventHandler.js";

export default defineEvent({
events: ["pull_request.closed"],
callback: async (context) => {
if (!context.payload.pull_request.merged) return;

const { owner, repo } = context.repo();
const branchName = context.payload.pull_request.head.ref;

if (!branchName.startsWith("devhub-bot/")) return;

try {
await context.octokit.rest.git.deleteRef({
owner,
repo,
ref: `heads/${branchName}`,
});
} catch (err: any) {
if (err.status !== 422 && err.status !== 404) throw err;
Comment on lines +19 to +20

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handler silently swallows errors without logging, making it impossible to debug branch deletion failures. Additionally, using err: any bypasses TypeScript type safety. If the error object doesn't have a status property, the code could fail unexpectedly.

Confidence: 4/5

Suggested Fix
Suggested change
} catch (err: any) {
if (err.status !== 422 && err.status !== 404) throw err;
} catch (err: any) {
if (err.status === 422 || err.status === 404) {
// Branch already deleted or doesn't exist - this is acceptable
return;
}
console.error(`Failed to delete branch ${branchName}:`, err);
throw err;
}

Add logging to capture unexpected errors and make the status code check more explicit. This helps with debugging while still allowing expected errors (422, 404) to be handled gracefully.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/events/cleanup/branchCleanup.ts around line 19-20, the error handler silently swallows errors without logging, making debugging difficult. Additionally, the `err: any` type bypasses TypeScript safety. Add logging for unexpected errors and improve the error status check to be more explicit. Make sure to log the branch name and error details when deletion fails unexpectedly.

📍 This suggestion applies to lines 19-20

}
},
});
129 changes: 129 additions & 0 deletions src/events/commands/addLinter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import { readFileSync } from "fs";
import { dirname, join } from "path";
import { Context } from "probot";
import ucid from "unique-custom-id";
import { fileURLToPath } from "url";
import { defineEvent } from "../../lib/eventHandler.js";

const __dirname = dirname(fileURLToPath(import.meta.url));

const LINT_WORKFLOW_PATHS = [
".github/workflows/linter.yaml",
".github/workflows/linter.yml",
];

async function fileExists(
context: Context<"issue_comment.created">,
owner: string,
repo: string,
path: string,
): Promise<boolean> {
try {
await context.octokit.rest.repos.getContent({ owner, repo, path });
return true;
} catch {
return false;
}
}

export default defineEvent({
events: ["issue_comment.created"],
callback: async (context) => {
const body = context.payload.comment.body.trim();

if (!body.startsWith("!addlinter")) return;

const { owner, repo } = context.repo();
const username = context.payload.sender.login;

const { data: permission } =
await context.octokit.rest.repos.getCollaboratorPermissionLevel({
owner,
repo,
username,
});

const allowed = ["admin", "maintain"];

if (!allowed.includes(permission.permission)) {
await context.octokit.rest.issues.createComment({
owner,
repo,
issue_number: context.payload.issue.number,
body: "❌ You must be a repository administrator to use this command.",
});

return;
}

for (const path of LINT_WORKFLOW_PATHS) {
if (await fileExists(context, owner, repo, path)) {
await context.octokit.rest.issues.createComment(
context.issue({
body: [
"> [!NOTE]",
`> A lint workflow already exists at \`${path}\`. Nothing to do!`,
].join("\n"),
}),
);
return;
}
}

const branchName = `devhub-bot/${ucid.format("mini")}`;
const workflowPath = ".github/workflows/linter.yaml";

const lintWorkflowContent = readFileSync(
join(__dirname, "../../templates/linter.yml"),
"utf-8",
);
Comment on lines +76 to +79

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The readFileSync call is not wrapped in error handling. If the template file doesn't exist or is inaccessible, the entire handler will crash without notifying the user. This is a critical failure point that should be handled gracefully.

Confidence: 5/5

Suggested Fix
Suggested change
const lintWorkflowContent = readFileSync(
join(__dirname, "../../templates/linter.yml"),
"utf-8",
);
let lintWorkflowContent: string;
try {
lintWorkflowContent = readFileSync(
join(__dirname, "../../templates/linter.yml"),
"utf-8",
);
} catch (err) {
await context.octokit.rest.issues.createComment(
context.issue({
body: "❌ Failed to read lint workflow template. Please contact the repository administrator.",
}),
);
throw err;
}

Wrap the file read in a try-catch block to handle missing or inaccessible template files. Notify the user of the failure before throwing the error.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/events/commands/addLinter.ts around line 76-79, the readFileSync call is not wrapped in error handling. If the template file doesn't exist, the handler will crash silently. Add a try-catch block around the file read operation, and notify the user with a comment if the template file cannot be read.

📍 This suggestion applies to lines 76-79


const encodedContent = Buffer.from(lintWorkflowContent).toString("base64");

const { data: mainRef } = await context.octokit.rest.git.getRef({
owner,
repo,
ref: "heads/main",
});
const mainSha = mainRef.object.sha;

await context.octokit.rest.git.createRef({
owner,
repo,
ref: `refs/heads/${branchName}`,
sha: mainSha,
});
Comment on lines +83 to +95

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getRef and createRef API calls are not wrapped in error handling. If the main branch doesn't exist or the branch creation fails (e.g., branch already exists), the handler will crash without user notification. Additionally, if createRef fails, the file creation and PR creation will still attempt to execute against a non-existent branch.

Confidence: 5/5

Suggested Fix
Suggested change
const { data: mainRef } = await context.octokit.rest.git.getRef({
owner,
repo,
ref: "heads/main",
});
const mainSha = mainRef.object.sha;
await context.octokit.rest.git.createRef({
owner,
repo,
ref: `refs/heads/${branchName}`,
sha: mainSha,
});
let mainSha: string;
try {
const { data: mainRef } = await context.octokit.rest.git.getRef({
owner,
repo,
ref: "heads/main",
});
mainSha = mainRef.object.sha;
} catch (err) {
await context.octokit.rest.issues.createComment(
context.issue({
body: "❌ Failed to find the main branch. Does it exist?",
}),
);
throw err;
}
try {
await context.octokit.rest.git.createRef({
owner,
repo,
ref: `refs/heads/${branchName}`,
sha: mainSha,
});
} catch (err) {
await context.octokit.rest.issues.createComment(
context.issue({
body: "❌ Failed to create feature branch. Please try again.",
}),
);
throw err;
}

Wrap both API calls in try-catch blocks to handle failures gracefully and notify the user of what went wrong.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/events/commands/addLinter.ts around lines 83-95, the getRef and createRef API calls lack error handling. If the main branch doesn't exist or branch creation fails, the handler crashes without user feedback. Add separate try-catch blocks for both operations and notify the user of failures before throwing errors.

📍 This suggestion applies to lines 83-95


await context.octokit.rest.repos.createOrUpdateFileContents({
owner,
repo,
path: workflowPath,
message: "ci: add lint workflow",
content: encodedContent,
branch: branchName,
});
Comment on lines +97 to +104

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The createOrUpdateFileContents API call is not wrapped in error handling. If this operation fails (e.g., due to permission issues or API rate limiting), the branch will be left orphaned in the repository with no user notification. This creates resource leaks and confusion.

Confidence: 5/5

Suggested Fix
Suggested change
await context.octokit.rest.repos.createOrUpdateFileContents({
owner,
repo,
path: workflowPath,
message: "ci: add lint workflow",
content: encodedContent,
branch: branchName,
});
try {
await context.octokit.rest.repos.createOrUpdateFileContents({
owner,
repo,
path: workflowPath,
message: "ci: add lint workflow",
content: encodedContent,
branch: branchName,
});
} catch (err) {
// Clean up the orphaned branch
try {
await context.octokit.rest.git.deleteRef({
owner,
repo,
ref: `heads/${branchName}`,
});
} catch (cleanupErr) {
// Log cleanup failure but continue
console.error(`Failed to clean up branch ${branchName}:`, cleanupErr);
}
await context.octokit.rest.issues.createComment(
context.issue({
body: "❌ Failed to create workflow file. Please try again.",
}),
);
throw err;
}

Wrap the file creation in a try-catch block. If it fails, attempt to clean up the orphaned branch before notifying the user.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/events/commands/addLinter.ts around lines 97-104, the createOrUpdateFileContents call lacks error handling. If this fails, the branch is left orphaned. Add a try-catch block that attempts to clean up the branch if file creation fails, and notify the user of the failure.

📍 This suggestion applies to lines 97-104


const { data: pr } = await context.octokit.rest.pulls.create({
owner,
repo,
title: "ci: add lint workflow",
head: branchName,
base: "main",
body: [
"This PR adds a GitHub Actions lint workflow to the repository.",
"",
"It was automatically generated by **devhub-bot** in response to the `!addlinter` command.",
"",
].join("\n"),
});
Comment on lines +106 to +118

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pulls.create API call is not wrapped in error handling. If PR creation fails, the user won't be notified, and they'll be left wondering if the operation succeeded. The branch and file will exist but no PR will be created.

Confidence: 5/5

Suggested Fix
Suggested change
const { data: pr } = await context.octokit.rest.pulls.create({
owner,
repo,
title: "ci: add lint workflow",
head: branchName,
base: "main",
body: [
"This PR adds a GitHub Actions lint workflow to the repository.",
"",
"It was automatically generated by **devhub-bot** in response to the `!addlinter` command.",
"",
].join("\n"),
});
let pr;
try {
const response = await context.octokit.rest.pulls.create({
owner,
repo,
title: "ci: add lint workflow",
head: branchName,
base: "main",
body: [
"This PR adds a GitHub Actions lint workflow to the repository.",
"",
"It was automatically generated by **devhub-bot** in response to the `!addlinter` command.",
"",
].join("\n"),
});
pr = response.data;
} catch (err) {
await context.octokit.rest.issues.createComment(
context.issue({
body: "❌ Failed to create pull request. The branch and workflow file were created, but the PR could not be opened. Please create the PR manually.",
}),
);
throw err;
}

Wrap the PR creation in a try-catch block to handle failures and notify the user appropriately.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/events/commands/addLinter.ts around lines 106-118, the pulls.create call lacks error handling. If PR creation fails, the user receives no feedback. Add a try-catch block to handle failures and notify the user that the branch and file were created but the PR could not be opened.

📍 This suggestion applies to lines 106-118


await context.octokit.rest.issues.createComment(
context.issue({
body: [
"> [!NOTE]",
`> Done! I've opened a PR to add the lint workflow (#${pr.number})`,
].join("\n"),
}),
);
},
});
45 changes: 45 additions & 0 deletions src/events/commands/ping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { defineEvent } from "../../lib/eventHandler.js";

export default defineEvent({
events: ["issue_comment.created"],
callback: async (context) => {
const body = context.payload.comment.body?.trim().toLowerCase();

if (body !== "!ping") return;

const start = Date.now();

const { owner, repo } = context.repo();

const latency = Date.now() - start;
const uptime = process.uptime();
const memory = process.memoryUsage().heapUsed / 1024 / 1024;

const formatUptime = (s: number) => {
const d = Math.floor(s / 86400);
const h = Math.floor((s % 86400) / 3600);
const m = Math.floor((s % 3600) / 60);
const sec = Math.floor(s % 60);
return `${d}d ${h}h ${m}m ${sec}s`;
};

const reply = [
"## 🏓 Pong!",
"| Metric | Value |",
"|--------|-------|",
`| Response Time | ${latency} ms |`,
`| Uptime | ${formatUptime(uptime)} |`,
`| Memory Usage | ${memory.toFixed(2)} MB |`,
`| Node.js | ${process.version} |`,
`| Timestamp | ${new Date().toISOString()} |`,
"",
].join("\n");

await context.octokit.rest.issues.createComment({
owner,
repo,
issue_number: context.payload.issue.number,
body: reply,
});
Comment on lines +38 to +43

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The createComment API call is not wrapped in error handling. If this operation fails (due to permission issues, rate limiting, or network errors), the handler will crash without notifying the user or logging the error. This leaves the user without feedback about whether their command succeeded.

Confidence: 5/5

Suggested Fix
Suggested change
await context.octokit.rest.issues.createComment({
owner,
repo,
issue_number: context.payload.issue.number,
body: reply,
});
try {
await context.octokit.rest.issues.createComment({
owner,
repo,
issue_number: context.payload.issue.number,
body: reply,
});
} catch (err) {
console.error(`Failed to post ping response: ${err}`);
throw err;
}

Wrap the API call in a try-catch block to handle failures gracefully and log the error for debugging purposes.

Prompt for AI

Copy this prompt to your AI IDE to fix this issue locally:

In src/events/commands/ping.ts around lines 38-43, the createComment API call lacks error handling. If the comment posting fails, the user receives no feedback. Add a try-catch block to handle failures and log the error for debugging.

📍 This suggestion applies to lines 38-43

},
});
125 changes: 0 additions & 125 deletions src/events/issue_comment.created/addLinter.ts

This file was deleted.

42 changes: 0 additions & 42 deletions src/events/issue_comment.created/ping.ts

This file was deleted.

Loading
Loading