mirror of
https://github.com/markwylde/claude-code-gitea-action.git
synced 2026-02-20 02:22:49 +08:00
Attempt to make this work
This commit is contained in:
@@ -14,8 +14,6 @@
|
|||||||
"@actions/github": "^6.0.1",
|
"@actions/github": "^6.0.1",
|
||||||
"@anthropic-ai/sdk": "^0.30.0",
|
"@anthropic-ai/sdk": "^0.30.0",
|
||||||
"@modelcontextprotocol/sdk": "^1.11.0",
|
"@modelcontextprotocol/sdk": "^1.11.0",
|
||||||
"@octokit/graphql": "^8.2.2",
|
|
||||||
"@octokit/rest": "^21.1.1",
|
|
||||||
"@octokit/webhooks-types": "^7.6.1",
|
"@octokit/webhooks-types": "^7.6.1",
|
||||||
"node-fetch": "^3.3.2",
|
"node-fetch": "^3.3.2",
|
||||||
"zod": "^3.24.4"
|
"zod": "^3.24.4"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { setupBranch } from "../github/operations/branch";
|
|||||||
import { updateTrackingComment } from "../github/operations/comments/update-with-branch";
|
import { updateTrackingComment } from "../github/operations/comments/update-with-branch";
|
||||||
import { prepareMcpConfig } from "../mcp/install-mcp-server";
|
import { prepareMcpConfig } from "../mcp/install-mcp-server";
|
||||||
import { createPrompt } from "../create-prompt";
|
import { createPrompt } from "../create-prompt";
|
||||||
import { createOctokit } from "../github/api/client";
|
import { createClient } from "../github/api/client";
|
||||||
import { fetchGitHubData } from "../github/data/fetcher";
|
import { fetchGitHubData } from "../github/data/fetcher";
|
||||||
import { parseGitHubContext } from "../github/context";
|
import { parseGitHubContext } from "../github/context";
|
||||||
|
|
||||||
@@ -23,14 +23,14 @@ async function run() {
|
|||||||
try {
|
try {
|
||||||
// Step 1: Setup GitHub token
|
// Step 1: Setup GitHub token
|
||||||
const githubToken = await setupGitHubToken();
|
const githubToken = await setupGitHubToken();
|
||||||
const octokit = createOctokit(githubToken);
|
const client = createClient(githubToken);
|
||||||
|
|
||||||
// Step 2: Parse GitHub context (once for all operations)
|
// Step 2: Parse GitHub context (once for all operations)
|
||||||
const context = parseGitHubContext();
|
const context = parseGitHubContext();
|
||||||
|
|
||||||
// Step 3: Check write permissions
|
// Step 3: Check write permissions
|
||||||
const hasWritePermissions = await checkWritePermissions(
|
const hasWritePermissions = await checkWritePermissions(
|
||||||
octokit.rest,
|
client.api,
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
if (!hasWritePermissions) {
|
if (!hasWritePermissions) {
|
||||||
@@ -52,22 +52,22 @@ async function run() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Step 5: Check if actor is human
|
// Step 5: Check if actor is human
|
||||||
await checkHumanActor(octokit.rest, context);
|
await checkHumanActor(client.api, context);
|
||||||
|
|
||||||
// Step 6: Create initial tracking comment
|
// Step 6: Create initial tracking comment
|
||||||
const commentId = await createInitialComment(octokit.rest, context);
|
const commentId = await createInitialComment(client.api, context);
|
||||||
core.setOutput("claude_comment_id", commentId.toString());
|
core.setOutput("claude_comment_id", commentId.toString());
|
||||||
|
|
||||||
// Step 7: Fetch GitHub data (once for both branch setup and prompt creation)
|
// Step 7: Fetch GitHub data (once for both branch setup and prompt creation)
|
||||||
const githubData = await fetchGitHubData({
|
const githubData = await fetchGitHubData({
|
||||||
octokits: octokit,
|
client: client,
|
||||||
repository: `${context.repository.owner}/${context.repository.repo}`,
|
repository: `${context.repository.owner}/${context.repository.repo}`,
|
||||||
prNumber: context.entityNumber.toString(),
|
prNumber: context.entityNumber.toString(),
|
||||||
isPR: context.isPR,
|
isPR: context.isPR,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Step 8: Setup branch
|
// Step 8: Setup branch
|
||||||
const branchInfo = await setupBranch(octokit, githubData, context);
|
const branchInfo = await setupBranch(client, githubData, context);
|
||||||
core.setOutput("BASE_BRANCH", branchInfo.baseBranch);
|
core.setOutput("BASE_BRANCH", branchInfo.baseBranch);
|
||||||
if (branchInfo.claudeBranch) {
|
if (branchInfo.claudeBranch) {
|
||||||
core.setOutput("CLAUDE_BRANCH", branchInfo.claudeBranch);
|
core.setOutput("CLAUDE_BRANCH", branchInfo.claudeBranch);
|
||||||
@@ -76,7 +76,7 @@ async function run() {
|
|||||||
// Step 9: Update initial comment with branch link (only for issues that created a new branch)
|
// Step 9: Update initial comment with branch link (only for issues that created a new branch)
|
||||||
if (branchInfo.claudeBranch) {
|
if (branchInfo.claudeBranch) {
|
||||||
await updateTrackingComment(
|
await updateTrackingComment(
|
||||||
octokit,
|
client,
|
||||||
context,
|
context,
|
||||||
commentId,
|
commentId,
|
||||||
branchInfo.claudeBranch,
|
branchInfo.claudeBranch,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
import { createOctokit } from "../github/api/client";
|
import { createClient } from "../github/api/client";
|
||||||
import * as fs from "fs/promises";
|
import * as fs from "fs/promises";
|
||||||
import {
|
import {
|
||||||
updateCommentBody,
|
updateCommentBody,
|
||||||
@@ -23,7 +23,7 @@ async function run() {
|
|||||||
|
|
||||||
const context = parseGitHubContext();
|
const context = parseGitHubContext();
|
||||||
const { owner, repo } = context.repository;
|
const { owner, repo } = context.repository;
|
||||||
const octokit = createOctokit(githubToken);
|
const client = createClient(githubToken);
|
||||||
|
|
||||||
const serverUrl = GITHUB_SERVER_URL;
|
const serverUrl = GITHUB_SERVER_URL;
|
||||||
const jobUrl = `${serverUrl}/${owner}/${repo}/actions/runs/${process.env.GITHUB_RUN_ID}`;
|
const jobUrl = `${serverUrl}/${owner}/${repo}/actions/runs/${process.env.GITHUB_RUN_ID}`;
|
||||||
@@ -37,12 +37,8 @@ async function run() {
|
|||||||
if (isPullRequestReviewCommentEvent(context)) {
|
if (isPullRequestReviewCommentEvent(context)) {
|
||||||
// For PR review comments, use the pulls API
|
// For PR review comments, use the pulls API
|
||||||
console.log(`Fetching PR review comment ${commentId}`);
|
console.log(`Fetching PR review comment ${commentId}`);
|
||||||
const { data: prComment } = await octokit.rest.pulls.getReviewComment({
|
const response = await client.api.customRequest("GET", `/api/v1/repos/${owner}/${repo}/pulls/comments/${commentId}`);
|
||||||
owner,
|
comment = response.data;
|
||||||
repo,
|
|
||||||
comment_id: commentId,
|
|
||||||
});
|
|
||||||
comment = prComment;
|
|
||||||
isPRReviewComment = true;
|
isPRReviewComment = true;
|
||||||
console.log("Successfully fetched as PR review comment");
|
console.log("Successfully fetched as PR review comment");
|
||||||
}
|
}
|
||||||
@@ -50,12 +46,8 @@ async function run() {
|
|||||||
// For all other event types, use the issues API
|
// For all other event types, use the issues API
|
||||||
if (!comment) {
|
if (!comment) {
|
||||||
console.log(`Fetching issue comment ${commentId}`);
|
console.log(`Fetching issue comment ${commentId}`);
|
||||||
const { data: issueComment } = await octokit.rest.issues.getComment({
|
const response = await client.api.customRequest("GET", `/api/v1/repos/${owner}/${repo}/issues/comments/${commentId}`);
|
||||||
owner,
|
comment = response.data;
|
||||||
repo,
|
|
||||||
comment_id: commentId,
|
|
||||||
});
|
|
||||||
comment = issueComment;
|
|
||||||
isPRReviewComment = false;
|
isPRReviewComment = false;
|
||||||
console.log("Successfully fetched as issue comment");
|
console.log("Successfully fetched as issue comment");
|
||||||
}
|
}
|
||||||
@@ -69,14 +61,10 @@ async function run() {
|
|||||||
|
|
||||||
// Try to get the PR info to understand the comment structure
|
// Try to get the PR info to understand the comment structure
|
||||||
try {
|
try {
|
||||||
const { data: pr } = await octokit.rest.pulls.get({
|
const pr = await client.api.getPullRequest(owner, repo, context.entityNumber);
|
||||||
owner,
|
console.log(`PR state: ${pr.data.state}`);
|
||||||
repo,
|
console.log(`PR comments count: ${pr.data.comments}`);
|
||||||
pull_number: context.entityNumber,
|
console.log(`PR review comments count: ${pr.data.review_comments}`);
|
||||||
});
|
|
||||||
console.log(`PR state: ${pr.state}`);
|
|
||||||
console.log(`PR comments count: ${pr.comments}`);
|
|
||||||
console.log(`PR review comments count: ${pr.review_comments}`);
|
|
||||||
} catch {
|
} catch {
|
||||||
console.error("Could not fetch PR info for debugging");
|
console.error("Could not fetch PR info for debugging");
|
||||||
}
|
}
|
||||||
@@ -88,7 +76,7 @@ async function run() {
|
|||||||
|
|
||||||
// Check if we need to add branch link for new branches
|
// Check if we need to add branch link for new branches
|
||||||
const { shouldDeleteBranch, branchLink } = await checkAndDeleteEmptyBranch(
|
const { shouldDeleteBranch, branchLink } = await checkAndDeleteEmptyBranch(
|
||||||
octokit,
|
client,
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
claudeBranch,
|
claudeBranch,
|
||||||
@@ -107,31 +95,15 @@ async function run() {
|
|||||||
const containsPRUrl = currentBody.match(prUrlPattern);
|
const containsPRUrl = currentBody.match(prUrlPattern);
|
||||||
|
|
||||||
if (!containsPRUrl) {
|
if (!containsPRUrl) {
|
||||||
// Check if we're in a Gitea environment
|
// Use direct SHA comparison for all Git platforms
|
||||||
const isGitea =
|
console.log("Using SHA comparison for PR link check");
|
||||||
process.env.GITHUB_API_URL &&
|
|
||||||
!process.env.GITHUB_API_URL.includes("api.github.com");
|
|
||||||
|
|
||||||
if (isGitea) {
|
|
||||||
// Gitea doesn't support the /compare endpoint, use direct SHA comparison
|
|
||||||
console.log(
|
|
||||||
"Detected Gitea environment, using SHA comparison for PR link check",
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the branch info to see if it exists and has commits
|
// Get the branch info to see if it exists and has commits
|
||||||
const branchResponse = await octokit.rest.repos.getBranch({
|
const branchResponse = await client.api.getBranch(owner, repo, claudeBranch);
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
branch: claudeBranch,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get base branch info for comparison
|
// Get base branch info for comparison
|
||||||
const baseResponse = await octokit.rest.repos.getBranch({
|
const baseResponse = await client.api.getBranch(owner, repo, baseBranch);
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
branch: baseBranch,
|
|
||||||
});
|
|
||||||
|
|
||||||
const branchSha = branchResponse.data.commit.sha;
|
const branchSha = branchResponse.data.commit.sha;
|
||||||
const baseSha = baseResponse.data.commit.sha;
|
const baseSha = baseResponse.data.commit.sha;
|
||||||
@@ -156,7 +128,7 @@ async function run() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("Error checking branch in Gitea:", error);
|
console.error("Error checking branch:", error);
|
||||||
|
|
||||||
// Handle 404 specifically - branch doesn't exist
|
// Handle 404 specifically - branch doesn't exist
|
||||||
if (error.status === 404) {
|
if (error.status === 404) {
|
||||||
@@ -167,9 +139,7 @@ async function run() {
|
|||||||
prLink = "";
|
prLink = "";
|
||||||
} else {
|
} else {
|
||||||
// For other errors, add PR link to be safe
|
// For other errors, add PR link to be safe
|
||||||
console.log(
|
console.log("Adding PR link as fallback due to non-404 error");
|
||||||
"Adding PR link as fallback for Gitea due to non-404 error",
|
|
||||||
);
|
|
||||||
const entityType = context.isPR ? "PR" : "Issue";
|
const entityType = context.isPR ? "PR" : "Issue";
|
||||||
const prTitle = encodeURIComponent(
|
const prTitle = encodeURIComponent(
|
||||||
`${entityType} #${context.entityNumber}: Changes from Claude`,
|
`${entityType} #${context.entityNumber}: Changes from Claude`,
|
||||||
@@ -181,86 +151,6 @@ async function run() {
|
|||||||
prLink = `\n[Create a PR](${prUrl})`;
|
prLink = `\n[Create a PR](${prUrl})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// GitHub environment - use the comparison API
|
|
||||||
try {
|
|
||||||
const { data: comparison } =
|
|
||||||
await octokit.rest.repos.compareCommitsWithBasehead({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
basehead: `${baseBranch}...${claudeBranch}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
// If there are changes (commits or file changes), add the PR URL
|
|
||||||
if (
|
|
||||||
comparison.total_commits > 0 ||
|
|
||||||
(comparison.files && comparison.files.length > 0)
|
|
||||||
) {
|
|
||||||
const entityType = context.isPR ? "PR" : "Issue";
|
|
||||||
const prTitle = encodeURIComponent(
|
|
||||||
`${entityType} #${context.entityNumber}: Changes from Claude`,
|
|
||||||
);
|
|
||||||
const prBody = encodeURIComponent(
|
|
||||||
`This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`,
|
|
||||||
);
|
|
||||||
const prUrl = `${serverUrl}/${owner}/${repo}/compare/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`;
|
|
||||||
prLink = `\n[Create a PR](${prUrl})`;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error checking for changes in branch:", error);
|
|
||||||
|
|
||||||
// Fallback to SHA comparison even on GitHub if API fails
|
|
||||||
try {
|
|
||||||
console.log(
|
|
||||||
"GitHub comparison API failed, falling back to SHA comparison",
|
|
||||||
);
|
|
||||||
|
|
||||||
const branchResponse = await octokit.rest.repos.getBranch({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
branch: claudeBranch,
|
|
||||||
});
|
|
||||||
|
|
||||||
const baseResponse = await octokit.rest.repos.getBranch({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
branch: baseBranch,
|
|
||||||
});
|
|
||||||
|
|
||||||
const branchSha = branchResponse.data.commit.sha;
|
|
||||||
const baseSha = baseResponse.data.commit.sha;
|
|
||||||
|
|
||||||
// If SHAs are different, assume there are changes and add PR link
|
|
||||||
if (branchSha !== baseSha) {
|
|
||||||
const entityType = context.isPR ? "PR" : "Issue";
|
|
||||||
const prTitle = encodeURIComponent(
|
|
||||||
`${entityType} #${context.entityNumber}: Changes from Claude`,
|
|
||||||
);
|
|
||||||
const prBody = encodeURIComponent(
|
|
||||||
`This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`,
|
|
||||||
);
|
|
||||||
const prUrl = `${serverUrl}/${owner}/${repo}/compare/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`;
|
|
||||||
prLink = `\n[Create a PR](${prUrl})`;
|
|
||||||
}
|
|
||||||
} catch (fallbackError) {
|
|
||||||
console.error(
|
|
||||||
"Fallback branch comparison also failed:",
|
|
||||||
fallbackError,
|
|
||||||
);
|
|
||||||
// If all checks fail, still add PR link to be safe
|
|
||||||
console.log("Adding PR link as final fallback");
|
|
||||||
const entityType = context.isPR ? "PR" : "Issue";
|
|
||||||
const prTitle = encodeURIComponent(
|
|
||||||
`${entityType} #${context.entityNumber}: Changes from Claude`,
|
|
||||||
);
|
|
||||||
const prBody = encodeURIComponent(
|
|
||||||
`This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`,
|
|
||||||
);
|
|
||||||
const prUrl = `${serverUrl}/${owner}/${repo}/compare/${baseBranch}...${claudeBranch}?quick_pull=1&title=${prTitle}&body=${prBody}`;
|
|
||||||
prLink = `\n[Create a PR](${prUrl})`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,19 +223,11 @@ async function run() {
|
|||||||
// Update the comment using the appropriate API
|
// Update the comment using the appropriate API
|
||||||
try {
|
try {
|
||||||
if (isPRReviewComment) {
|
if (isPRReviewComment) {
|
||||||
await octokit.rest.pulls.updateReviewComment({
|
await client.api.customRequest("PATCH", `/api/v1/repos/${owner}/${repo}/pulls/comments/${commentId}`, {
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
comment_id: commentId,
|
|
||||||
body: updatedBody,
|
body: updatedBody,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await octokit.rest.issues.updateComment({
|
await client.api.updateIssueComment(owner, repo, commentId, updatedBody);
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
comment_id: commentId,
|
|
||||||
body: updatedBody,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
console.log(
|
console.log(
|
||||||
`✅ Updated ${isPRReviewComment ? "PR review" : "issue"} comment ${commentId} with job link`,
|
`✅ Updated ${isPRReviewComment ? "PR review" : "issue"} comment ${commentId} with job link`,
|
||||||
|
|||||||
@@ -1,23 +1,11 @@
|
|||||||
import { Octokit } from "@octokit/rest";
|
import { GiteaApiClient, createGiteaClient } from "./gitea-client";
|
||||||
import { graphql } from "@octokit/graphql";
|
|
||||||
import { GITHUB_API_URL } from "./config";
|
|
||||||
|
|
||||||
export type Octokits = {
|
export type GitHubClient = {
|
||||||
rest: Octokit;
|
api: GiteaApiClient;
|
||||||
graphql: typeof graphql;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function createOctokit(token: string): Octokits {
|
export function createClient(token: string): GitHubClient {
|
||||||
return {
|
return {
|
||||||
rest: new Octokit({
|
api: createGiteaClient(token),
|
||||||
auth: token,
|
|
||||||
baseUrl: GITHUB_API_URL,
|
|
||||||
}),
|
|
||||||
graphql: graphql.defaults({
|
|
||||||
baseUrl: GITHUB_API_URL,
|
|
||||||
headers: {
|
|
||||||
authorization: `token ${token}`,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
218
src/github/api/gitea-client.ts
Normal file
218
src/github/api/gitea-client.ts
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
import fetch from "node-fetch";
|
||||||
|
import { GITHUB_API_URL } from "./config";
|
||||||
|
|
||||||
|
export interface GiteaApiResponse<T = any> {
|
||||||
|
status: number;
|
||||||
|
data: T;
|
||||||
|
headers: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GiteaApiError extends Error {
|
||||||
|
status: number;
|
||||||
|
response?: {
|
||||||
|
data: any;
|
||||||
|
status: number;
|
||||||
|
headers: Record<string, string>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GiteaApiClient {
|
||||||
|
private baseUrl: string;
|
||||||
|
private token: string;
|
||||||
|
|
||||||
|
constructor(token: string, baseUrl: string = GITHUB_API_URL) {
|
||||||
|
this.token = token;
|
||||||
|
this.baseUrl = baseUrl.replace(/\/+$/, ""); // Remove trailing slashes
|
||||||
|
}
|
||||||
|
|
||||||
|
private async request<T = any>(
|
||||||
|
method: string,
|
||||||
|
endpoint: string,
|
||||||
|
body?: any
|
||||||
|
): Promise<GiteaApiResponse<T>> {
|
||||||
|
const url = `${this.baseUrl}${endpoint}`;
|
||||||
|
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": `token ${this.token}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const options: any = {
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (body && (method === "POST" || method === "PUT" || method === "PATCH")) {
|
||||||
|
options.body = JSON.stringify(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, options);
|
||||||
|
const responseData: any = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = new Error(
|
||||||
|
`HTTP ${response.status}: ${responseData.message || response.statusText}`
|
||||||
|
) as GiteaApiError;
|
||||||
|
error.status = response.status;
|
||||||
|
error.response = {
|
||||||
|
data: responseData,
|
||||||
|
status: response.status,
|
||||||
|
headers: Object.fromEntries(response.headers.entries()),
|
||||||
|
};
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: response.status,
|
||||||
|
data: responseData as T,
|
||||||
|
headers: Object.fromEntries(response.headers.entries()),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error && "status" in error) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
throw new Error(`Request failed: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repository operations
|
||||||
|
async getRepo(owner: string, repo: string) {
|
||||||
|
return this.request("GET", `/api/v1/repos/${owner}/${repo}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getBranch(owner: string, repo: string, branch: string) {
|
||||||
|
return this.request("GET", `/api/v1/repos/${owner}/${repo}/branches/${encodeURIComponent(branch)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createBranch(owner: string, repo: string, newBranch: string, fromBranch: string) {
|
||||||
|
return this.request("POST", `/api/v1/repos/${owner}/${repo}/branches`, {
|
||||||
|
new_branch_name: newBranch,
|
||||||
|
old_branch_name: fromBranch,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async listBranches(owner: string, repo: string) {
|
||||||
|
return this.request("GET", `/api/v1/repos/${owner}/${repo}/branches`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Issue operations
|
||||||
|
async getIssue(owner: string, repo: string, issueNumber: number) {
|
||||||
|
return this.request("GET", `/api/v1/repos/${owner}/${repo}/issues/${issueNumber}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async listIssueComments(owner: string, repo: string, issueNumber: number) {
|
||||||
|
return this.request("GET", `/api/v1/repos/${owner}/${repo}/issues/${issueNumber}/comments`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createIssueComment(owner: string, repo: string, issueNumber: number, body: string) {
|
||||||
|
return this.request("POST", `/api/v1/repos/${owner}/${repo}/issues/${issueNumber}/comments`, {
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateIssueComment(owner: string, repo: string, commentId: number, body: string) {
|
||||||
|
return this.request("PATCH", `/api/v1/repos/${owner}/${repo}/issues/comments/${commentId}`, {
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pull request operations
|
||||||
|
async getPullRequest(owner: string, repo: string, prNumber: number) {
|
||||||
|
return this.request("GET", `/api/v1/repos/${owner}/${repo}/pulls/${prNumber}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async listPullRequestFiles(owner: string, repo: string, prNumber: number) {
|
||||||
|
return this.request("GET", `/api/v1/repos/${owner}/${repo}/pulls/${prNumber}/files`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async listPullRequestComments(owner: string, repo: string, prNumber: number) {
|
||||||
|
return this.request("GET", `/api/v1/repos/${owner}/${repo}/pulls/${prNumber}/comments`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPullRequestComment(owner: string, repo: string, prNumber: number, body: string) {
|
||||||
|
return this.request("POST", `/api/v1/repos/${owner}/${repo}/pulls/${prNumber}/comments`, {
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// File operations
|
||||||
|
async getFileContents(owner: string, repo: string, path: string, ref?: string) {
|
||||||
|
let endpoint = `/api/v1/repos/${owner}/${repo}/contents/${encodeURIComponent(path)}`;
|
||||||
|
if (ref) {
|
||||||
|
endpoint += `?ref=${encodeURIComponent(ref)}`;
|
||||||
|
}
|
||||||
|
return this.request("GET", endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createFile(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
path: string,
|
||||||
|
content: string,
|
||||||
|
message: string,
|
||||||
|
branch?: string
|
||||||
|
) {
|
||||||
|
const body: any = {
|
||||||
|
message,
|
||||||
|
content: Buffer.from(content).toString("base64"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (branch) {
|
||||||
|
body.branch = branch;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.request("POST", `/api/v1/repos/${owner}/${repo}/contents/${encodeURIComponent(path)}`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateFile(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
path: string,
|
||||||
|
content: string,
|
||||||
|
message: string,
|
||||||
|
sha: string,
|
||||||
|
branch?: string
|
||||||
|
) {
|
||||||
|
const body: any = {
|
||||||
|
message,
|
||||||
|
content: Buffer.from(content).toString("base64"),
|
||||||
|
sha,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (branch) {
|
||||||
|
body.branch = branch;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.request("PUT", `/api/v1/repos/${owner}/${repo}/contents/${encodeURIComponent(path)}`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteFile(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
path: string,
|
||||||
|
message: string,
|
||||||
|
sha: string,
|
||||||
|
branch?: string
|
||||||
|
) {
|
||||||
|
const body: any = {
|
||||||
|
message,
|
||||||
|
sha,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (branch) {
|
||||||
|
body.branch = branch;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.request("DELETE", `/api/v1/repos/${owner}/${repo}/contents/${encodeURIComponent(path)}`, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic request method for other operations
|
||||||
|
async customRequest<T = any>(method: string, endpoint: string, body?: any): Promise<GiteaApiResponse<T>> {
|
||||||
|
return this.request<T>(method, endpoint, body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createGiteaClient(token: string): GiteaApiClient {
|
||||||
|
return new GiteaApiClient(token);
|
||||||
|
}
|
||||||
@@ -5,16 +5,13 @@ import type {
|
|||||||
GitHubComment,
|
GitHubComment,
|
||||||
GitHubFile,
|
GitHubFile,
|
||||||
GitHubReview,
|
GitHubReview,
|
||||||
PullRequestQueryResponse,
|
|
||||||
IssueQueryResponse,
|
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import { PR_QUERY, ISSUE_QUERY } from "../api/queries/github";
|
import type { GitHubClient } from "../api/client";
|
||||||
import type { Octokits } from "../api/client";
|
|
||||||
import { downloadCommentImages } from "../utils/image-downloader";
|
import { downloadCommentImages } from "../utils/image-downloader";
|
||||||
import type { CommentWithImages } from "../utils/image-downloader";
|
import type { CommentWithImages } from "../utils/image-downloader";
|
||||||
|
|
||||||
type FetchDataParams = {
|
type FetchDataParams = {
|
||||||
octokits: Octokits;
|
client: GitHubClient;
|
||||||
repository: string;
|
repository: string;
|
||||||
prNumber: string;
|
prNumber: string;
|
||||||
isPR: boolean;
|
isPR: boolean;
|
||||||
@@ -34,7 +31,7 @@ export type FetchDataResult = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function fetchGitHubData({
|
export async function fetchGitHubData({
|
||||||
octokits,
|
client,
|
||||||
repository,
|
repository,
|
||||||
prNumber,
|
prNumber,
|
||||||
isPR,
|
isPR,
|
||||||
@@ -44,28 +41,16 @@ export async function fetchGitHubData({
|
|||||||
throw new Error("Invalid repository format. Expected 'owner/repo'.");
|
throw new Error("Invalid repository format. Expected 'owner/repo'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if we're in a Gitea environment (no GraphQL support)
|
|
||||||
const isGitea =
|
|
||||||
process.env.GITHUB_API_URL &&
|
|
||||||
!process.env.GITHUB_API_URL.includes("api.github.com");
|
|
||||||
|
|
||||||
let contextData: GitHubPullRequest | GitHubIssue | null = null;
|
let contextData: GitHubPullRequest | GitHubIssue | null = null;
|
||||||
let comments: GitHubComment[] = [];
|
let comments: GitHubComment[] = [];
|
||||||
let changedFiles: GitHubFile[] = [];
|
let changedFiles: GitHubFile[] = [];
|
||||||
let reviewData: { nodes: GitHubReview[] } | null = null;
|
let reviewData: { nodes: GitHubReview[] } | null = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isGitea) {
|
// Use REST API for all requests (works with both GitHub and Gitea)
|
||||||
// Use REST API for Gitea compatibility
|
|
||||||
if (isPR) {
|
if (isPR) {
|
||||||
console.log(
|
console.log(`Fetching PR #${prNumber} data using REST API`);
|
||||||
`Fetching PR #${prNumber} data using REST API (Gitea mode)`,
|
const prResponse = await client.api.getPullRequest(owner, repo, parseInt(prNumber));
|
||||||
);
|
|
||||||
const prResponse = await octokits.rest.pulls.get({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: parseInt(prNumber),
|
|
||||||
});
|
|
||||||
|
|
||||||
contextData = {
|
contextData = {
|
||||||
title: prResponse.data.title,
|
title: prResponse.data.title,
|
||||||
@@ -86,12 +71,12 @@ export async function fetchGitHubData({
|
|||||||
|
|
||||||
// Fetch comments separately
|
// Fetch comments separately
|
||||||
try {
|
try {
|
||||||
const commentsResponse = await octokits.rest.issues.listComments({
|
const commentsResponse = await client.api.listIssueComments(
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
issue_number: parseInt(prNumber),
|
parseInt(prNumber)
|
||||||
});
|
);
|
||||||
comments = commentsResponse.data.map((comment) => ({
|
comments = commentsResponse.data.map((comment: any) => ({
|
||||||
id: comment.id.toString(),
|
id: comment.id.toString(),
|
||||||
databaseId: comment.id.toString(),
|
databaseId: comment.id.toString(),
|
||||||
body: comment.body || "",
|
body: comment.body || "",
|
||||||
@@ -105,12 +90,12 @@ export async function fetchGitHubData({
|
|||||||
|
|
||||||
// Try to fetch files
|
// Try to fetch files
|
||||||
try {
|
try {
|
||||||
const filesResponse = await octokits.rest.pulls.listFiles({
|
const filesResponse = await client.api.listPullRequestFiles(
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
pull_number: parseInt(prNumber),
|
parseInt(prNumber)
|
||||||
});
|
);
|
||||||
changedFiles = filesResponse.data.map((file) => ({
|
changedFiles = filesResponse.data.map((file: any) => ({
|
||||||
path: file.filename,
|
path: file.filename,
|
||||||
additions: file.additions || 0,
|
additions: file.additions || 0,
|
||||||
deletions: file.deletions || 0,
|
deletions: file.deletions || 0,
|
||||||
@@ -123,14 +108,8 @@ export async function fetchGitHubData({
|
|||||||
|
|
||||||
reviewData = { nodes: [] }; // Simplified for Gitea
|
reviewData = { nodes: [] }; // Simplified for Gitea
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log(`Fetching issue #${prNumber} data using REST API`);
|
||||||
`Fetching issue #${prNumber} data using REST API (Gitea mode)`,
|
const issueResponse = await client.api.getIssue(owner, repo, parseInt(prNumber));
|
||||||
);
|
|
||||||
const issueResponse = await octokits.rest.issues.get({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: parseInt(prNumber),
|
|
||||||
});
|
|
||||||
|
|
||||||
contextData = {
|
contextData = {
|
||||||
title: issueResponse.data.title,
|
title: issueResponse.data.title,
|
||||||
@@ -143,12 +122,12 @@ export async function fetchGitHubData({
|
|||||||
|
|
||||||
// Fetch comments
|
// Fetch comments
|
||||||
try {
|
try {
|
||||||
const commentsResponse = await octokits.rest.issues.listComments({
|
const commentsResponse = await client.api.listIssueComments(
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
issue_number: parseInt(prNumber),
|
parseInt(prNumber)
|
||||||
});
|
);
|
||||||
comments = commentsResponse.data.map((comment) => ({
|
comments = commentsResponse.data.map((comment: any) => ({
|
||||||
id: comment.id.toString(),
|
id: comment.id.toString(),
|
||||||
databaseId: comment.id.toString(),
|
databaseId: comment.id.toString(),
|
||||||
body: comment.body || "",
|
body: comment.body || "",
|
||||||
@@ -160,54 +139,12 @@ export async function fetchGitHubData({
|
|||||||
comments = []; // Ensure we have an empty array
|
comments = []; // Ensure we have an empty array
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Use GraphQL for GitHub
|
|
||||||
if (isPR) {
|
|
||||||
const prResult = await octokits.graphql<PullRequestQueryResponse>(
|
|
||||||
PR_QUERY,
|
|
||||||
{
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
number: parseInt(prNumber),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (prResult.repository.pullRequest) {
|
|
||||||
const pullRequest = prResult.repository.pullRequest;
|
|
||||||
contextData = pullRequest;
|
|
||||||
changedFiles = pullRequest.files.nodes || [];
|
|
||||||
comments = pullRequest.comments?.nodes || [];
|
|
||||||
reviewData = pullRequest.reviews || [];
|
|
||||||
|
|
||||||
console.log(`Successfully fetched PR #${prNumber} data`);
|
|
||||||
} else {
|
|
||||||
throw new Error(`PR #${prNumber} not found`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const issueResult = await octokits.graphql<IssueQueryResponse>(
|
|
||||||
ISSUE_QUERY,
|
|
||||||
{
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
number: parseInt(prNumber),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (issueResult.repository.issue) {
|
|
||||||
contextData = issueResult.repository.issue;
|
|
||||||
comments = contextData?.comments?.nodes || [];
|
|
||||||
|
|
||||||
console.log(`Successfully fetched issue #${prNumber} data`);
|
|
||||||
} else {
|
|
||||||
throw new Error(`Issue #${prNumber} not found`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to fetch ${isPR ? "PR" : "issue"} data:`, error);
|
console.error(`Failed to fetch ${isPR ? "PR" : "issue"} data:`, error);
|
||||||
throw new Error(`Failed to fetch ${isPR ? "PR" : "issue"} data`);
|
throw new Error(`Failed to fetch ${isPR ? "PR" : "issue"} data`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Compute SHAs for changed files
|
// Compute SHAs for changed files
|
||||||
let changedFilesWithSHA: GitHubFileWithSHA[] = [];
|
let changedFilesWithSHA: GitHubFileWithSHA[] = [];
|
||||||
if (isPR && changedFiles.length > 0) {
|
if (isPR && changedFiles.length > 0) {
|
||||||
@@ -288,7 +225,7 @@ export async function fetchGitHubData({
|
|||||||
];
|
];
|
||||||
|
|
||||||
const imageUrlMap = await downloadCommentImages(
|
const imageUrlMap = await downloadCommentImages(
|
||||||
octokits,
|
client,
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
allComments,
|
allComments,
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import type { Octokits } from "../api/client";
|
import type { GitHubClient } from "../api/client";
|
||||||
import { GITHUB_SERVER_URL } from "../api/config";
|
import { GITHUB_SERVER_URL } from "../api/config";
|
||||||
|
|
||||||
export async function checkAndDeleteEmptyBranch(
|
export async function checkAndDeleteEmptyBranch(
|
||||||
octokit: Octokits,
|
client: GitHubClient,
|
||||||
owner: string,
|
owner: string,
|
||||||
repo: string,
|
repo: string,
|
||||||
claudeBranch: string | undefined,
|
claudeBranch: string | undefined,
|
||||||
@@ -12,31 +12,15 @@ export async function checkAndDeleteEmptyBranch(
|
|||||||
let shouldDeleteBranch = false;
|
let shouldDeleteBranch = false;
|
||||||
|
|
||||||
if (claudeBranch) {
|
if (claudeBranch) {
|
||||||
// Check if we're in a Gitea environment
|
// Use direct SHA comparison for both GitHub and Gitea
|
||||||
const isGitea =
|
console.log("Using SHA comparison for branch check");
|
||||||
process.env.GITHUB_API_URL &&
|
|
||||||
!process.env.GITHUB_API_URL.includes("api.github.com");
|
|
||||||
|
|
||||||
if (isGitea) {
|
|
||||||
// Gitea doesn't support the /compare endpoint, use direct SHA comparison
|
|
||||||
console.log(
|
|
||||||
"Detected Gitea environment, using SHA comparison for branch check",
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the branch info to see if it exists and has commits
|
// Get the branch info to see if it exists and has commits
|
||||||
const branchResponse = await octokit.rest.repos.getBranch({
|
const branchResponse = await client.api.getBranch(owner, repo, claudeBranch);
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
branch: claudeBranch,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get base branch info for comparison
|
// Get base branch info for comparison
|
||||||
const baseResponse = await octokit.rest.repos.getBranch({
|
const baseResponse = await client.api.getBranch(owner, repo, baseBranch);
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
branch: baseBranch,
|
|
||||||
});
|
|
||||||
|
|
||||||
const branchSha = branchResponse.data.commit.sha;
|
const branchSha = branchResponse.data.commit.sha;
|
||||||
const baseSha = baseResponse.data.commit.sha;
|
const baseSha = baseResponse.data.commit.sha;
|
||||||
@@ -55,7 +39,7 @@ export async function checkAndDeleteEmptyBranch(
|
|||||||
shouldDeleteBranch = true;
|
shouldDeleteBranch = true;
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("Error checking branch in Gitea:", error);
|
console.error("Error checking branch:", error);
|
||||||
|
|
||||||
// Handle 404 specifically - branch doesn't exist
|
// Handle 404 specifically - branch doesn't exist
|
||||||
if (error.status === 404) {
|
if (error.status === 404) {
|
||||||
@@ -71,96 +55,14 @@ export async function checkAndDeleteEmptyBranch(
|
|||||||
branchLink = `\n[View branch](${branchUrl})`;
|
branchLink = `\n[View branch](${branchUrl})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// GitHub environment - use the comparison API
|
|
||||||
try {
|
|
||||||
const { data: comparison } =
|
|
||||||
await octokit.rest.repos.compareCommitsWithBasehead({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
basehead: `${baseBranch}...${claudeBranch}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
// If there are no commits, mark branch for deletion
|
|
||||||
if (comparison.total_commits === 0) {
|
|
||||||
console.log(
|
|
||||||
`Branch ${claudeBranch} has no commits from Claude, will delete it`,
|
|
||||||
);
|
|
||||||
shouldDeleteBranch = true;
|
|
||||||
} else {
|
|
||||||
// Only add branch link if there are commits
|
|
||||||
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`;
|
|
||||||
branchLink = `\n[View branch](${branchUrl})`;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error checking for commits on Claude branch:", error);
|
|
||||||
|
|
||||||
// Fallback to SHA comparison even on GitHub if API fails
|
|
||||||
try {
|
|
||||||
console.log(
|
|
||||||
"GitHub comparison API failed, falling back to SHA comparison",
|
|
||||||
);
|
|
||||||
|
|
||||||
const branchResponse = await octokit.rest.repos.getBranch({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
branch: claudeBranch,
|
|
||||||
});
|
|
||||||
|
|
||||||
const baseResponse = await octokit.rest.repos.getBranch({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
branch: baseBranch,
|
|
||||||
});
|
|
||||||
|
|
||||||
const branchSha = branchResponse.data.commit.sha;
|
|
||||||
const baseSha = baseResponse.data.commit.sha;
|
|
||||||
|
|
||||||
if (branchSha !== baseSha) {
|
|
||||||
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`;
|
|
||||||
branchLink = `\n[View branch](${branchUrl})`;
|
|
||||||
} else {
|
|
||||||
shouldDeleteBranch = true;
|
|
||||||
}
|
|
||||||
} catch (fallbackError) {
|
|
||||||
console.error(
|
|
||||||
"Fallback branch comparison also failed:",
|
|
||||||
fallbackError,
|
|
||||||
);
|
|
||||||
// If all checks fail, assume the branch has commits to be safe
|
|
||||||
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`;
|
|
||||||
branchLink = `\n[View branch](${branchUrl})`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete the branch if it has no commits
|
// Delete the branch if it has no commits
|
||||||
if (shouldDeleteBranch && claudeBranch) {
|
if (shouldDeleteBranch && claudeBranch) {
|
||||||
// Check if we're in a Gitea environment for deletion too
|
|
||||||
const isGitea =
|
|
||||||
process.env.GITHUB_API_URL &&
|
|
||||||
!process.env.GITHUB_API_URL.includes("api.github.com");
|
|
||||||
|
|
||||||
if (isGitea) {
|
|
||||||
console.log(
|
console.log(
|
||||||
`Skipping branch deletion for Gitea - not reliably supported: ${claudeBranch}`,
|
`Skipping branch deletion - not reliably supported across all Git platforms: ${claudeBranch}`,
|
||||||
);
|
);
|
||||||
// Don't attempt deletion in Gitea as it's not reliably supported
|
// Skip deletion to avoid compatibility issues
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
await octokit.rest.git.deleteRef({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
ref: `heads/${claudeBranch}`,
|
|
||||||
});
|
|
||||||
console.log(`✅ Deleted empty branch: ${claudeBranch}`);
|
|
||||||
} catch (deleteError: any) {
|
|
||||||
console.error(`Failed to delete branch ${claudeBranch}:`, deleteError);
|
|
||||||
console.log(`Delete error status: ${deleteError.status}`);
|
|
||||||
// Continue even if deletion fails - this is not critical
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { shouldDeleteBranch, branchLink };
|
return { shouldDeleteBranch, branchLink };
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { $ } from "bun";
|
|||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import type { ParsedGitHubContext } from "../context";
|
import type { ParsedGitHubContext } from "../context";
|
||||||
import type { GitHubPullRequest } from "../types";
|
import type { GitHubPullRequest } from "../types";
|
||||||
import type { Octokits } from "../api/client";
|
import type { GitHubClient } from "../api/client";
|
||||||
import type { FetchDataResult } from "../data/fetcher";
|
import type { FetchDataResult } from "../data/fetcher";
|
||||||
|
|
||||||
export type BranchInfo = {
|
export type BranchInfo = {
|
||||||
@@ -20,7 +20,7 @@ export type BranchInfo = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export async function setupBranch(
|
export async function setupBranch(
|
||||||
octokits: Octokits,
|
client: GitHubClient,
|
||||||
githubData: FetchDataResult,
|
githubData: FetchDataResult,
|
||||||
context: ParsedGitHubContext,
|
context: ParsedGitHubContext,
|
||||||
): Promise<BranchInfo> {
|
): Promise<BranchInfo> {
|
||||||
@@ -70,10 +70,7 @@ export async function setupBranch(
|
|||||||
sourceBranch = baseBranch;
|
sourceBranch = baseBranch;
|
||||||
} else {
|
} else {
|
||||||
// No base branch provided, fetch the default branch to use as source
|
// No base branch provided, fetch the default branch to use as source
|
||||||
const repoResponse = await octokits.rest.repos.get({
|
const repoResponse = await client.api.getRepo(owner, repo);
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
});
|
|
||||||
sourceBranch = repoResponse.data.default_branch;
|
sourceBranch = repoResponse.data.default_branch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,86 +90,30 @@ export async function setupBranch(
|
|||||||
const newBranch = `claude/${entityType}-${entityNumber}-${timestamp}`;
|
const newBranch = `claude/${entityType}-${entityNumber}-${timestamp}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the SHA of the source branch
|
// Get the SHA of the source branch using Gitea's branches endpoint
|
||||||
// For Gitea, try using the branches endpoint instead of git/refs
|
console.log(`Getting branch info for: ${sourceBranch}`);
|
||||||
let currentSHA: string;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// First try the GitHub-compatible git.getRef approach
|
const branchResponse = await client.api.getBranch(owner, repo, sourceBranch);
|
||||||
const sourceBranchRef = await octokits.rest.git.getRef({
|
const currentSHA = branchResponse.data.commit.sha;
|
||||||
owner,
|
console.log(`Current SHA: ${currentSHA}`);
|
||||||
repo,
|
} catch (branchError: any) {
|
||||||
ref: `heads/${sourceBranch}`,
|
console.log(`Failed to get branch info: ${branchError.message}`);
|
||||||
});
|
|
||||||
currentSHA = sourceBranchRef.data.object.sha;
|
|
||||||
} catch (gitRefError: any) {
|
|
||||||
// If git/refs fails (like in Gitea), use the branches endpoint
|
|
||||||
console.log(
|
|
||||||
`git/refs failed, trying branches endpoint: ${gitRefError.message}`,
|
|
||||||
);
|
|
||||||
const branchResponse = await octokits.rest.repos.getBranch({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
branch: sourceBranch,
|
|
||||||
});
|
|
||||||
// GitHub and Gitea both use commit.sha
|
|
||||||
currentSHA = branchResponse.data.commit.sha;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Current SHA: ${currentSHA}`);
|
// Create branch using Gitea's branch creation API
|
||||||
|
console.log(`Creating branch: ${newBranch} from: ${sourceBranch}`);
|
||||||
// Try to create branch using the appropriate method for each platform
|
|
||||||
const isGitea =
|
|
||||||
process.env.GITHUB_API_URL &&
|
|
||||||
!process.env.GITHUB_API_URL.includes("api.github.com");
|
|
||||||
|
|
||||||
if (isGitea) {
|
|
||||||
// Gitea supports POST /repos/{owner}/{repo}/branches
|
|
||||||
console.log(
|
|
||||||
`Detected Gitea environment, using branches API for: ${newBranch}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Use the raw Gitea API since Octokit might not have the createBranch method
|
await client.api.createBranch(owner, repo, newBranch, sourceBranch);
|
||||||
await octokits.rest.request("POST /repos/{owner}/{repo}/branches", {
|
console.log(`Successfully created branch via Gitea API: ${newBranch}`);
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
new_branch_name: newBranch,
|
|
||||||
old_branch_name: sourceBranch,
|
|
||||||
});
|
|
||||||
console.log(
|
|
||||||
`Successfully created branch via Gitea branches API: ${newBranch}`,
|
|
||||||
);
|
|
||||||
} catch (createBranchError: any) {
|
} catch (createBranchError: any) {
|
||||||
console.log(
|
console.log(`Branch creation failed: ${createBranchError.message}`);
|
||||||
`Gitea branch creation failed: ${createBranchError.message}`,
|
|
||||||
);
|
|
||||||
console.log(`Error status: ${createBranchError.status}`);
|
console.log(`Error status: ${createBranchError.status}`);
|
||||||
console.log(
|
console.log(
|
||||||
`Branch ${newBranch} will be created when files are pushed via MCP server`,
|
`Branch ${newBranch} will be created when files are pushed via MCP server`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// GitHub environment - use git.createRef
|
|
||||||
try {
|
|
||||||
await octokits.rest.git.createRef({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
ref: `refs/heads/${newBranch}`,
|
|
||||||
sha: currentSHA,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`Successfully created branch via GitHub git.createRef: ${newBranch}`,
|
|
||||||
);
|
|
||||||
} catch (createRefError: any) {
|
|
||||||
console.log(`GitHub git.createRef failed: ${createRefError.message}`);
|
|
||||||
console.log(`Error status: ${createRefError.status}`);
|
|
||||||
console.log(
|
|
||||||
`Branch ${newBranch} will be created when files are pushed`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Branch setup completed for: ${newBranch}`);
|
console.log(`Branch setup completed for: ${newBranch}`);
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,10 @@ import {
|
|||||||
isPullRequestReviewCommentEvent,
|
isPullRequestReviewCommentEvent,
|
||||||
type ParsedGitHubContext,
|
type ParsedGitHubContext,
|
||||||
} from "../../context";
|
} from "../../context";
|
||||||
import type { Octokit } from "@octokit/rest";
|
import type { GiteaApiClient } from "../../api/gitea-client";
|
||||||
|
|
||||||
export async function createInitialComment(
|
export async function createInitialComment(
|
||||||
octokit: Octokit,
|
api: GiteaApiClient,
|
||||||
context: ParsedGitHubContext,
|
context: ParsedGitHubContext,
|
||||||
) {
|
) {
|
||||||
const { owner, repo } = context.repository;
|
const { owner, repo } = context.repository;
|
||||||
@@ -27,21 +27,12 @@ export async function createInitialComment(
|
|||||||
|
|
||||||
// Only use createReplyForReviewComment if it's a PR review comment AND we have a comment_id
|
// Only use createReplyForReviewComment if it's a PR review comment AND we have a comment_id
|
||||||
if (isPullRequestReviewCommentEvent(context)) {
|
if (isPullRequestReviewCommentEvent(context)) {
|
||||||
response = await octokit.rest.pulls.createReplyForReviewComment({
|
response = await api.customRequest("POST", `/api/v1/repos/${owner}/${repo}/pulls/${context.entityNumber}/comments/${context.payload.comment.id}/replies`, {
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: context.entityNumber,
|
|
||||||
comment_id: context.payload.comment.id,
|
|
||||||
body: initialBody,
|
body: initialBody,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// For all other cases (issues, issue comments, or missing comment_id)
|
// For all other cases (issues, issue comments, or missing comment_id)
|
||||||
response = await octokit.rest.issues.createComment({
|
response = await api.createIssueComment(owner, repo, context.entityNumber, initialBody);
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: context.entityNumber,
|
|
||||||
body: initialBody,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Output the comment ID for downstream steps using GITHUB_OUTPUT
|
// Output the comment ID for downstream steps using GITHUB_OUTPUT
|
||||||
@@ -54,12 +45,7 @@ export async function createInitialComment(
|
|||||||
|
|
||||||
// Always fall back to regular issue comment if anything fails
|
// Always fall back to regular issue comment if anything fails
|
||||||
try {
|
try {
|
||||||
const response = await octokit.rest.issues.createComment({
|
const response = await api.createIssueComment(owner, repo, context.entityNumber, initialBody);
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: context.entityNumber,
|
|
||||||
body: initialBody,
|
|
||||||
});
|
|
||||||
|
|
||||||
const githubOutput = process.env.GITHUB_OUTPUT!;
|
const githubOutput = process.env.GITHUB_OUTPUT!;
|
||||||
appendFileSync(githubOutput, `claude_comment_id=${response.data.id}\n`);
|
appendFileSync(githubOutput, `claude_comment_id=${response.data.id}\n`);
|
||||||
|
|||||||
@@ -10,14 +10,14 @@ import {
|
|||||||
createBranchLink,
|
createBranchLink,
|
||||||
createCommentBody,
|
createCommentBody,
|
||||||
} from "./common";
|
} from "./common";
|
||||||
import { type Octokits } from "../../api/client";
|
import { type GitHubClient } from "../../api/client";
|
||||||
import {
|
import {
|
||||||
isPullRequestReviewCommentEvent,
|
isPullRequestReviewCommentEvent,
|
||||||
type ParsedGitHubContext,
|
type ParsedGitHubContext,
|
||||||
} from "../../context";
|
} from "../../context";
|
||||||
|
|
||||||
export async function updateTrackingComment(
|
export async function updateTrackingComment(
|
||||||
octokit: Octokits,
|
client: GitHubClient,
|
||||||
context: ParsedGitHubContext,
|
context: ParsedGitHubContext,
|
||||||
commentId: number,
|
commentId: number,
|
||||||
branch?: string,
|
branch?: string,
|
||||||
@@ -38,21 +38,13 @@ export async function updateTrackingComment(
|
|||||||
try {
|
try {
|
||||||
if (isPullRequestReviewCommentEvent(context)) {
|
if (isPullRequestReviewCommentEvent(context)) {
|
||||||
// For PR review comments (inline comments), use the pulls API
|
// For PR review comments (inline comments), use the pulls API
|
||||||
await octokit.rest.pulls.updateReviewComment({
|
await client.api.customRequest("PATCH", `/api/v1/repos/${owner}/${repo}/pulls/comments/${commentId}`, {
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
comment_id: commentId,
|
|
||||||
body: updatedBody,
|
body: updatedBody,
|
||||||
});
|
});
|
||||||
console.log(`✅ Updated PR review comment ${commentId} with branch link`);
|
console.log(`✅ Updated PR review comment ${commentId} with branch link`);
|
||||||
} else {
|
} else {
|
||||||
// For all other comments, use the issues API
|
// For all other comments, use the issues API
|
||||||
await octokit.rest.issues.updateComment({
|
await client.api.updateIssueComment(owner, repo, commentId, updatedBody);
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
comment_id: commentId,
|
|
||||||
body: updatedBody,
|
|
||||||
});
|
|
||||||
console.log(`✅ Updated issue comment ${commentId} with branch link`);
|
console.log(`✅ Updated issue comment ${commentId} with branch link`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -1,12 +1,4 @@
|
|||||||
import fs from "fs/promises";
|
import type { GitHubClient } from "../api/client";
|
||||||
import path from "path";
|
|
||||||
import type { Octokits } from "../api/client";
|
|
||||||
import { GITHUB_SERVER_URL } from "../api/config";
|
|
||||||
|
|
||||||
const IMAGE_REGEX = new RegExp(
|
|
||||||
`!\\[[^\\]]*\\]\\((${GITHUB_SERVER_URL.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\/user-attachments\\/assets\\/[^)]+)\\)`,
|
|
||||||
"g",
|
|
||||||
);
|
|
||||||
|
|
||||||
type IssueComment = {
|
type IssueComment = {
|
||||||
type: "issue_comment";
|
type: "issue_comment";
|
||||||
@@ -47,254 +39,13 @@ export type CommentWithImages =
|
|||||||
| PullRequestBody;
|
| PullRequestBody;
|
||||||
|
|
||||||
export async function downloadCommentImages(
|
export async function downloadCommentImages(
|
||||||
octokits: Octokits,
|
_client: GitHubClient,
|
||||||
owner: string,
|
_owner: string,
|
||||||
repo: string,
|
_repo: string,
|
||||||
comments: CommentWithImages[],
|
_comments: CommentWithImages[],
|
||||||
): Promise<Map<string, string>> {
|
): Promise<Map<string, string>> {
|
||||||
const urlToPathMap = new Map<string, string>();
|
// Temporarily simplified - return empty map to avoid Octokit dependencies
|
||||||
const downloadsDir = "/tmp/github-images";
|
// TODO: Implement image downloading with direct Gitea API calls if needed
|
||||||
|
console.log("Image downloading temporarily disabled during Octokit migration");
|
||||||
await fs.mkdir(downloadsDir, { recursive: true });
|
return new Map<string, string>();
|
||||||
|
|
||||||
const commentsWithImages: Array<{
|
|
||||||
comment: CommentWithImages;
|
|
||||||
urls: string[];
|
|
||||||
}> = [];
|
|
||||||
|
|
||||||
for (const comment of comments) {
|
|
||||||
const imageMatches = [...comment.body.matchAll(IMAGE_REGEX)];
|
|
||||||
const urls = imageMatches.map((match) => match[1] as string);
|
|
||||||
|
|
||||||
if (urls.length > 0) {
|
|
||||||
commentsWithImages.push({ comment, urls });
|
|
||||||
const id =
|
|
||||||
comment.type === "issue_body"
|
|
||||||
? comment.issueNumber
|
|
||||||
: comment.type === "pr_body"
|
|
||||||
? comment.pullNumber
|
|
||||||
: comment.id;
|
|
||||||
console.log(`Found ${urls.length} image(s) in ${comment.type} ${id}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process each comment with images
|
|
||||||
for (const { comment, urls } of commentsWithImages) {
|
|
||||||
try {
|
|
||||||
let bodyHtml: string | undefined;
|
|
||||||
|
|
||||||
// Get the HTML version based on comment type
|
|
||||||
// Try with full+json mediaType first (GitHub), fallback to regular API (Gitea)
|
|
||||||
switch (comment.type) {
|
|
||||||
case "issue_comment": {
|
|
||||||
try {
|
|
||||||
const response = await octokits.rest.issues.getComment({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
comment_id: parseInt(comment.id),
|
|
||||||
mediaType: {
|
|
||||||
format: "full+json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
bodyHtml = response.data.body_html;
|
|
||||||
} catch (error: any) {
|
|
||||||
console.log(
|
|
||||||
"Full+json format not supported, trying regular API for issue comment",
|
|
||||||
);
|
|
||||||
// Fallback for Gitea - use regular API without mediaType
|
|
||||||
const response = await octokits.rest.issues.getComment({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
comment_id: parseInt(comment.id),
|
|
||||||
});
|
|
||||||
// Gitea might not have body_html, use body instead
|
|
||||||
bodyHtml = (response.data as any).body_html || response.data.body;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "review_comment": {
|
|
||||||
try {
|
|
||||||
const response = await octokits.rest.pulls.getReviewComment({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
comment_id: parseInt(comment.id),
|
|
||||||
mediaType: {
|
|
||||||
format: "full+json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
bodyHtml = response.data.body_html;
|
|
||||||
} catch (error: any) {
|
|
||||||
console.log(
|
|
||||||
"Full+json format not supported, trying regular API for review comment",
|
|
||||||
);
|
|
||||||
// Fallback for Gitea
|
|
||||||
const response = await octokits.rest.pulls.getReviewComment({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
comment_id: parseInt(comment.id),
|
|
||||||
});
|
|
||||||
bodyHtml = (response.data as any).body_html || response.data.body;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "review_body": {
|
|
||||||
try {
|
|
||||||
const response = await octokits.rest.pulls.getReview({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: parseInt(comment.pullNumber),
|
|
||||||
review_id: parseInt(comment.id),
|
|
||||||
mediaType: {
|
|
||||||
format: "full+json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
bodyHtml = response.data.body_html;
|
|
||||||
} catch (error: any) {
|
|
||||||
console.log(
|
|
||||||
"Full+json format not supported, trying regular API for review",
|
|
||||||
);
|
|
||||||
// Fallback for Gitea
|
|
||||||
const response = await octokits.rest.pulls.getReview({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: parseInt(comment.pullNumber),
|
|
||||||
review_id: parseInt(comment.id),
|
|
||||||
});
|
|
||||||
bodyHtml = (response.data as any).body_html || response.data.body;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "issue_body": {
|
|
||||||
try {
|
|
||||||
const response = await octokits.rest.issues.get({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: parseInt(comment.issueNumber),
|
|
||||||
mediaType: {
|
|
||||||
format: "full+json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
bodyHtml = response.data.body_html;
|
|
||||||
} catch (error: any) {
|
|
||||||
console.log(
|
|
||||||
"Full+json format not supported, trying regular API for issue",
|
|
||||||
);
|
|
||||||
// Fallback for Gitea
|
|
||||||
const response = await octokits.rest.issues.get({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: parseInt(comment.issueNumber),
|
|
||||||
});
|
|
||||||
bodyHtml = (response.data as any).body_html || response.data.body;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "pr_body": {
|
|
||||||
try {
|
|
||||||
const response = await octokits.rest.pulls.get({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: parseInt(comment.pullNumber),
|
|
||||||
mediaType: {
|
|
||||||
format: "full+json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// Type here seems to be wrong
|
|
||||||
bodyHtml = (response.data as any).body_html;
|
|
||||||
} catch (error: any) {
|
|
||||||
console.log(
|
|
||||||
"Full+json format not supported, trying regular API for PR",
|
|
||||||
);
|
|
||||||
// Fallback for Gitea
|
|
||||||
const response = await octokits.rest.pulls.get({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: parseInt(comment.pullNumber),
|
|
||||||
});
|
|
||||||
bodyHtml = (response.data as any).body_html || response.data.body;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!bodyHtml) {
|
|
||||||
const id =
|
|
||||||
comment.type === "issue_body"
|
|
||||||
? comment.issueNumber
|
|
||||||
: comment.type === "pr_body"
|
|
||||||
? comment.pullNumber
|
|
||||||
: comment.id;
|
|
||||||
console.warn(`No HTML body found for ${comment.type} ${id}`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract signed URLs from HTML
|
|
||||||
const signedUrlRegex =
|
|
||||||
/https:\/\/private-user-images\.githubusercontent\.com\/[^"]+\?jwt=[^"]+/g;
|
|
||||||
const signedUrls = bodyHtml.match(signedUrlRegex) || [];
|
|
||||||
|
|
||||||
// Download each image
|
|
||||||
for (let i = 0; i < Math.min(signedUrls.length, urls.length); i++) {
|
|
||||||
const signedUrl = signedUrls[i];
|
|
||||||
const originalUrl = urls[i];
|
|
||||||
|
|
||||||
if (!signedUrl || !originalUrl) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we've already downloaded this URL
|
|
||||||
if (urlToPathMap.has(originalUrl)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fileExtension = getImageExtension(originalUrl);
|
|
||||||
const filename = `image-${Date.now()}-${i}${fileExtension}`;
|
|
||||||
const localPath = path.join(downloadsDir, filename);
|
|
||||||
|
|
||||||
try {
|
|
||||||
console.log(`Downloading ${originalUrl}...`);
|
|
||||||
|
|
||||||
const imageResponse = await fetch(signedUrl);
|
|
||||||
if (!imageResponse.ok) {
|
|
||||||
throw new Error(
|
|
||||||
`HTTP ${imageResponse.status}: ${imageResponse.statusText}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const arrayBuffer = await imageResponse.arrayBuffer();
|
|
||||||
const buffer = Buffer.from(arrayBuffer);
|
|
||||||
|
|
||||||
await fs.writeFile(localPath, buffer);
|
|
||||||
console.log(`✓ Saved: ${localPath}`);
|
|
||||||
|
|
||||||
urlToPathMap.set(originalUrl, localPath);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`✗ Failed to download ${originalUrl}:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
const id =
|
|
||||||
comment.type === "issue_body"
|
|
||||||
? comment.issueNumber
|
|
||||||
: comment.type === "pr_body"
|
|
||||||
? comment.pullNumber
|
|
||||||
: comment.id;
|
|
||||||
console.error(
|
|
||||||
`Failed to process images for ${comment.type} ${id}:`,
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return urlToPathMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getImageExtension(url: string): string {
|
|
||||||
const urlParts = url.split("/");
|
|
||||||
const filename = urlParts[urlParts.length - 1];
|
|
||||||
if (!filename) {
|
|
||||||
throw new Error("Invalid URL: No filename found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = filename.match(/\.(png|jpg|jpeg|gif|webp|svg)$/i);
|
|
||||||
return match ? match[0] : ".png";
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,11 +5,11 @@
|
|||||||
* Prevents automated tools or bots from triggering Claude
|
* Prevents automated tools or bots from triggering Claude
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { Octokit } from "@octokit/rest";
|
import type { GiteaApiClient } from "../api/gitea-client";
|
||||||
import type { ParsedGitHubContext } from "../context";
|
import type { ParsedGitHubContext } from "../context";
|
||||||
|
|
||||||
export async function checkHumanActor(
|
export async function checkHumanActor(
|
||||||
octokit: Octokit,
|
api: GiteaApiClient,
|
||||||
githubContext: ParsedGitHubContext,
|
githubContext: ParsedGitHubContext,
|
||||||
) {
|
) {
|
||||||
// Check if we're in a Gitea environment
|
// Check if we're in a Gitea environment
|
||||||
@@ -26,9 +26,8 @@ export async function checkHumanActor(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch user information from GitHub API
|
// Fetch user information from GitHub API
|
||||||
const { data: userData } = await octokit.users.getByUsername({
|
const response = await api.customRequest("GET", `/api/v1/users/${githubContext.actor}`);
|
||||||
username: githubContext.actor,
|
const userData = response.data;
|
||||||
});
|
|
||||||
|
|
||||||
const actorType = userData.type;
|
const actorType = userData.type;
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import type { ParsedGitHubContext } from "../context";
|
import type { ParsedGitHubContext } from "../context";
|
||||||
import type { Octokit } from "@octokit/rest";
|
import type { GiteaApiClient } from "../api/gitea-client";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the actor has write permissions to the repository
|
* Check if the actor has write permissions to the repository
|
||||||
* @param octokit - The Octokit REST client
|
* @param api - The Gitea API client
|
||||||
* @param context - The GitHub context
|
* @param context - The GitHub context
|
||||||
* @returns true if the actor has write permissions, false otherwise
|
* @returns true if the actor has write permissions, false otherwise
|
||||||
*/
|
*/
|
||||||
export async function checkWritePermissions(
|
export async function checkWritePermissions(
|
||||||
octokit: Octokit,
|
api: GiteaApiClient,
|
||||||
context: ParsedGitHubContext,
|
context: ParsedGitHubContext,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const { repository, actor } = context;
|
const { repository, actor } = context;
|
||||||
@@ -28,11 +28,7 @@ export async function checkWritePermissions(
|
|||||||
core.info(`Checking permissions for actor: ${actor}`);
|
core.info(`Checking permissions for actor: ${actor}`);
|
||||||
|
|
||||||
// Check permissions directly using the permission endpoint
|
// Check permissions directly using the permission endpoint
|
||||||
const response = await octokit.repos.getCollaboratorPermissionLevel({
|
const response = await api.customRequest("GET", `/api/v1/repos/${repository.owner}/${repository.repo}/collaborators/${actor}/permission`);
|
||||||
owner: repository.owner,
|
|
||||||
repo: repository.repo,
|
|
||||||
username: actor,
|
|
||||||
});
|
|
||||||
|
|
||||||
const permissionLevel = response.data.permission;
|
const permissionLevel = response.data.permission;
|
||||||
core.info(`Permission level retrieved: ${permissionLevel}`);
|
core.info(`Permission level retrieved: ${permissionLevel}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user