164 lines
4.3 KiB
TypeScript
164 lines
4.3 KiB
TypeScript
import { Gitea } from "@go-gitea/sdk.js";
|
|
import type {
|
|
Comment,
|
|
ChangedFile,
|
|
PullReview,
|
|
Reaction,
|
|
} from "@go-gitea/sdk.js";
|
|
import type { PRContext } from "./types.ts";
|
|
|
|
const client = new Gitea({
|
|
baseUrl: process.env.GITEA_URL,
|
|
auth: process.env.BOT_TOKEN,
|
|
});
|
|
|
|
export async function getPRDiff(pr: PRContext): Promise<string> {
|
|
console.log(`Fetching PR #${pr.prNumber} diff for ${pr.owner}/${pr.repo}`);
|
|
const res = await client.rest.repository.repoDownloadPullDiffOrPatch({
|
|
owner: pr.owner,
|
|
repo: pr.repo,
|
|
index: pr.prNumber,
|
|
diffType: "diff",
|
|
});
|
|
return res.data;
|
|
}
|
|
|
|
export async function getPRFiles(pr: PRContext): Promise<ChangedFile[]> {
|
|
console.log(
|
|
`Fetching PR #${pr.prNumber} changed files for ${pr.owner}/${pr.repo}`,
|
|
);
|
|
const res = await client.rest.repository.repoGetPullRequestFiles({
|
|
owner: pr.owner,
|
|
repo: pr.repo,
|
|
index: pr.prNumber,
|
|
});
|
|
return res.data;
|
|
}
|
|
|
|
export async function postReviewComment(
|
|
pr: PRContext,
|
|
body: string,
|
|
): Promise<Comment> {
|
|
console.log(
|
|
`Posting review comment on PR #${pr.prNumber} for ${pr.owner}/${pr.repo}`,
|
|
);
|
|
const res = await client.rest.issue.issueCreateComment({
|
|
owner: pr.owner,
|
|
repo: pr.repo,
|
|
index: pr.prNumber,
|
|
body: { body },
|
|
});
|
|
return res.data;
|
|
}
|
|
|
|
export async function postInlineComment(
|
|
pr: PRContext,
|
|
file: string,
|
|
line: number,
|
|
body: string,
|
|
): Promise<PullReview> {
|
|
console.log(
|
|
`Posting inline review comment on PR #${pr.prNumber} for ${pr.owner}/${pr.repo} at ${file}:${line}`,
|
|
);
|
|
const res = await client.rest.repository.repoCreatePullReview({
|
|
owner: pr.owner,
|
|
repo: pr.repo,
|
|
index: pr.prNumber,
|
|
body: {
|
|
event: "COMMENT",
|
|
commit_id: pr.headSha,
|
|
comments: [{ path: file, new_position: line, body }],
|
|
},
|
|
});
|
|
return res.data;
|
|
}
|
|
|
|
export async function addReaction(
|
|
pr: PRContext,
|
|
commentId: number,
|
|
reaction: string,
|
|
): Promise<Reaction> {
|
|
const res = await client.rest.issue.issuePostCommentReaction({
|
|
owner: pr.owner,
|
|
repo: pr.repo,
|
|
id: commentId,
|
|
content: { content: reaction },
|
|
});
|
|
return res.data;
|
|
}
|
|
|
|
export async function removeReaction(
|
|
pr: PRContext,
|
|
commentId: number,
|
|
reaction: string,
|
|
): Promise<void> {
|
|
await client.rest.issue.issueDeleteCommentReaction({
|
|
owner: pr.owner,
|
|
repo: pr.repo,
|
|
id: commentId,
|
|
content: { content: reaction },
|
|
});
|
|
}
|
|
|
|
export async function getPR(owner: string, repo: string, prNumber: number) {
|
|
console.log(`Fetching PR #${prNumber} data for ${owner}/${repo}`);
|
|
const res = await client.rest.repository.repoGetPullRequest({
|
|
owner,
|
|
repo,
|
|
index: prNumber,
|
|
});
|
|
return res.data;
|
|
}
|
|
|
|
export async function getFileContents(
|
|
pr: PRContext,
|
|
filePath: string,
|
|
): Promise<string> {
|
|
console.log(
|
|
`Fetching contents of ${filePath} at PR #${pr.prNumber} head for ${pr.owner}/${pr.repo}`,
|
|
);
|
|
const res = await client.rest.repository.repoGetContents({
|
|
owner: pr.owner,
|
|
repo: pr.repo,
|
|
filepath: filePath,
|
|
ref: pr.headSha,
|
|
});
|
|
const entry = Array.isArray(res.data) ? res.data[0] : res.data;
|
|
if (!entry?.content || entry.type !== "file") return "";
|
|
// content is base64-encoded; Buffer handles embedded newlines
|
|
return Buffer.from(entry.content, "base64").toString("utf8");
|
|
}
|
|
|
|
export async function listDir(
|
|
pr: PRContext,
|
|
dirPath: string,
|
|
): Promise<string[]> {
|
|
console.log(
|
|
`Listing directory ${dirPath} at PR #${pr.prNumber} head for ${pr.owner}/${pr.repo}`,
|
|
);
|
|
const res = await client.rest.repository.repoGetContents({
|
|
owner: pr.owner,
|
|
repo: pr.repo,
|
|
filepath: dirPath,
|
|
ref: pr.headSha,
|
|
});
|
|
const entries = Array.isArray(res.data) ? res.data : [res.data];
|
|
return entries.map((e) => e.name ?? "").filter(Boolean);
|
|
}
|
|
|
|
// Gitea REST API v1 has no code search endpoint — returns repo names matching the query.
|
|
// mcp.ts should strongly prefer disk grep (find_symbol) when workspace is available.
|
|
export async function searchCode(
|
|
pr: PRContext,
|
|
query: string,
|
|
): Promise<string[]> {
|
|
console.log(
|
|
`Searching code for "${query}" at PR #${pr.prNumber} head for ${pr.owner}/${pr.repo}`,
|
|
);
|
|
const res = await client.rest.repository.repoSearch({
|
|
q: query,
|
|
limit: 20,
|
|
});
|
|
return (res.data.data ?? []).map((r) => r.full_name ?? "").filter(Boolean);
|
|
}
|