Compare commits
No commits in common. "gitea-v1.0.37" and "v1.0.37" have entirely different histories.
gitea-v1.0
...
v1.0.37
16
README.md
16
README.md
@ -4,21 +4,6 @@
|
|||||||
|
|
||||||
A general-purpose [Claude Code](https://claude.ai/code) action for GitHub PRs and issues that can answer questions and implement code changes. This action intelligently detects when to activate based on your workflow context—whether responding to @claude mentions, issue assignments, or executing automation tasks with explicit prompts. It supports multiple authentication methods including Anthropic direct API, Amazon Bedrock, Google Vertex AI, and Microsoft Foundry.
|
A general-purpose [Claude Code](https://claude.ai/code) action for GitHub PRs and issues that can answer questions and implement code changes. This action intelligently detects when to activate based on your workflow context—whether responding to @claude mentions, issue assignments, or executing automation tasks with explicit prompts. It supports multiple authentication methods including Anthropic direct API, Amazon Bedrock, Google Vertex AI, and Microsoft Foundry.
|
||||||
|
|
||||||
## 🍵 Gitea Support
|
|
||||||
|
|
||||||
This action now supports Gitea through the GITEA API. Key features:
|
|
||||||
|
|
||||||
**Configuration Example for Gitea:**
|
|
||||||
```yaml
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }} # your gitea PAT
|
|
||||||
GITHUB_API_URL: "https://gitea.example.com/api/v1"
|
|
||||||
GITHUB_SERVER_URL: "https://gitea.example.com"
|
|
||||||
USE_GITEA_API: "true"
|
|
||||||
input:
|
|
||||||
allowed_bots: "bot" # comma-separated, or `*`
|
|
||||||
```
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- 🎯 **Intelligent Mode Detection**: Automatically selects the appropriate execution mode based on your workflow context—no configuration needed
|
- 🎯 **Intelligent Mode Detection**: Automatically selects the appropriate execution mode based on your workflow context—no configuration needed
|
||||||
@ -31,7 +16,6 @@ input:
|
|||||||
- 📊 **Structured Outputs**: Get validated JSON results that automatically become GitHub Action outputs for complex automations
|
- 📊 **Structured Outputs**: Get validated JSON results that automatically become GitHub Action outputs for complex automations
|
||||||
- 🏃 **Runs on Your Infrastructure**: The action executes entirely on your own GitHub runner (Anthropic API calls go to your chosen provider)
|
- 🏃 **Runs on Your Infrastructure**: The action executes entirely on your own GitHub runner (Anthropic API calls go to your chosen provider)
|
||||||
- ⚙️ **Simplified Configuration**: Unified `prompt` and `claude_args` inputs provide clean, powerful configuration aligned with Claude Code SDK
|
- ⚙️ **Simplified Configuration**: Unified `prompt` and `claude_args` inputs provide clean, powerful configuration aligned with Claude Code SDK
|
||||||
- 🍵 **Gitea Support**: Support with Gitea through GITEA API.
|
|
||||||
|
|
||||||
## 📦 Upgrading from v0.x?
|
## 📦 Upgrading from v0.x?
|
||||||
|
|
||||||
|
|||||||
@ -197,8 +197,6 @@ runs:
|
|||||||
INCLUDE_COMMENTS_BY_ACTOR: ${{ inputs.include_comments_by_actor }}
|
INCLUDE_COMMENTS_BY_ACTOR: ${{ inputs.include_comments_by_actor }}
|
||||||
EXCLUDE_COMMENTS_BY_ACTOR: ${{ inputs.exclude_comments_by_actor }}
|
EXCLUDE_COMMENTS_BY_ACTOR: ${{ inputs.exclude_comments_by_actor }}
|
||||||
GITHUB_RUN_ID: ${{ github.run_id }}
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||||
GITEA_RUN_NUMBER: ${{ env.GITEA_RUN_NUMBER }}
|
|
||||||
USE_GITEA_API: ${{ env.USE_GITEA_API }}
|
|
||||||
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
|
USE_STICKY_COMMENT: ${{ inputs.use_sticky_comment }}
|
||||||
DEFAULT_WORKFLOW_TOKEN: ${{ github.token }}
|
DEFAULT_WORKFLOW_TOKEN: ${{ github.token }}
|
||||||
USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }}
|
USE_COMMIT_SIGNING: ${{ inputs.use_commit_signing }}
|
||||||
@ -323,9 +321,7 @@ runs:
|
|||||||
REPOSITORY: ${{ github.repository }}
|
REPOSITORY: ${{ github.repository }}
|
||||||
PR_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }}
|
PR_NUMBER: ${{ github.event.issue.number || github.event.pull_request.number }}
|
||||||
CLAUDE_COMMENT_ID: ${{ steps.prepare.outputs.claude_comment_id }}
|
CLAUDE_COMMENT_ID: ${{ steps.prepare.outputs.claude_comment_id }}
|
||||||
GITHUB_RUN_ID: ${{ steps.prepare.outputs.run_id || github.run_id }}
|
GITHUB_RUN_ID: ${{ github.run_id }}
|
||||||
GITEA_RUN_NUMBER: ${{ env.GITEA_RUN_NUMBER }}
|
|
||||||
USE_GITEA_API: ${{ env.USE_GITEA_API }}
|
|
||||||
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
|
||||||
GH_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ steps.prepare.outputs.GITHUB_TOKEN }}
|
||||||
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
GITHUB_EVENT_NAME: ${{ github.event_name }}
|
||||||
|
|||||||
@ -6,7 +6,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
import * as github from "@actions/github";
|
|
||||||
import { setupGitHubToken } from "../github/token";
|
import { setupGitHubToken } from "../github/token";
|
||||||
import { checkWritePermissions } from "../github/validation/permissions";
|
import { checkWritePermissions } from "../github/validation/permissions";
|
||||||
import { createOctokit } from "../github/api/client";
|
import { createOctokit } from "../github/api/client";
|
||||||
@ -14,26 +13,11 @@ import { parseGitHubContext, isEntityContext } from "../github/context";
|
|||||||
import { getMode } from "../modes/registry";
|
import { getMode } from "../modes/registry";
|
||||||
import { prepare } from "../prepare";
|
import { prepare } from "../prepare";
|
||||||
import { collectActionInputsPresence } from "./collect-inputs";
|
import { collectActionInputsPresence } from "./collect-inputs";
|
||||||
import { setupGiteaRunId } from "../github/api/gitea-run-id";
|
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
try {
|
try {
|
||||||
collectActionInputsPresence();
|
collectActionInputsPresence();
|
||||||
|
|
||||||
// Resolve Gitea run ID before parsing context (uses GITEA_RUN_NUMBER if available)
|
|
||||||
// We need a token for this - use either the override token or the default workflow token
|
|
||||||
const earlyToken =
|
|
||||||
process.env.OVERRIDE_GITHUB_TOKEN || process.env.DEFAULT_WORKFLOW_TOKEN;
|
|
||||||
if (earlyToken) {
|
|
||||||
const { owner, repo } = github.context.repo;
|
|
||||||
await setupGiteaRunId(earlyToken, owner, repo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output the resolved run ID for subsequent steps
|
|
||||||
if (process.env.GITHUB_RUN_ID) {
|
|
||||||
core.setOutput("run_id", process.env.GITHUB_RUN_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse GitHub context first to enable mode detection
|
// Parse GitHub context first to enable mode detection
|
||||||
const context = parseGitHubContext();
|
const context = parseGitHubContext();
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
#!/usr/bin/env bun
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
import * as github from "@actions/github";
|
|
||||||
import { createOctokit } from "../github/api/client";
|
import { createOctokit } from "../github/api/client";
|
||||||
import * as fs from "fs/promises";
|
import * as fs from "fs/promises";
|
||||||
import {
|
import {
|
||||||
@ -15,7 +14,6 @@ import {
|
|||||||
import { GITHUB_SERVER_URL } from "../github/api/config";
|
import { GITHUB_SERVER_URL } from "../github/api/config";
|
||||||
import { checkAndCommitOrDeleteBranch } from "../github/operations/branch-cleanup";
|
import { checkAndCommitOrDeleteBranch } from "../github/operations/branch-cleanup";
|
||||||
import { updateClaudeComment } from "../github/operations/comments/update-claude-comment";
|
import { updateClaudeComment } from "../github/operations/comments/update-claude-comment";
|
||||||
import { setupGiteaRunId } from "../github/api/gitea-run-id";
|
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
try {
|
try {
|
||||||
@ -25,10 +23,6 @@ async function run() {
|
|||||||
const baseBranch = process.env.BASE_BRANCH || "main";
|
const baseBranch = process.env.BASE_BRANCH || "main";
|
||||||
const triggerUsername = process.env.TRIGGER_USERNAME;
|
const triggerUsername = process.env.TRIGGER_USERNAME;
|
||||||
|
|
||||||
// Resolve Gitea run ID before parsing context (uses GITEA_RUN_NUMBER if available)
|
|
||||||
const { owner, repo } = github.context.repo;
|
|
||||||
await setupGiteaRunId(githubToken, owner, repo);
|
|
||||||
|
|
||||||
const context = parseGitHubContext();
|
const context = parseGitHubContext();
|
||||||
|
|
||||||
// This script is only called for entity-based events
|
// This script is only called for entity-based events
|
||||||
@ -36,6 +30,8 @@ async function run() {
|
|||||||
throw new Error("update-comment-link requires an entity context");
|
throw new Error("update-comment-link requires an entity context");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { owner, repo } = context.repository;
|
||||||
|
|
||||||
const octokit = createOctokit(githubToken);
|
const octokit = createOctokit(githubToken);
|
||||||
|
|
||||||
const serverUrl = GITHUB_SERVER_URL;
|
const serverUrl = GITHUB_SERVER_URL;
|
||||||
|
|||||||
@ -2,4 +2,3 @@ export const GITHUB_API_URL =
|
|||||||
process.env.GITHUB_API_URL || "https://api.github.com";
|
process.env.GITHUB_API_URL || "https://api.github.com";
|
||||||
export const GITHUB_SERVER_URL =
|
export const GITHUB_SERVER_URL =
|
||||||
process.env.GITHUB_SERVER_URL || "https://github.com";
|
process.env.GITHUB_SERVER_URL || "https://github.com";
|
||||||
export const USE_GITEA_API = process.env.USE_GITEA_API === "true" || process.env.USE_GITEA_API === "1";
|
|
||||||
@ -1,91 +0,0 @@
|
|||||||
import { GITHUB_API_URL, USE_GITEA_API } from "./config";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves the Gitea run ID from the run number.
|
|
||||||
* In Gitea, the environment variable GITHUB_RUN_ID is not available,
|
|
||||||
* so we need to fetch it via the API using GITEA_RUN_NUMBER.
|
|
||||||
*
|
|
||||||
* @param token - GitHub/Gitea token for authentication
|
|
||||||
* @param owner - Repository owner
|
|
||||||
* @param repo - Repository name
|
|
||||||
* @returns The resolved run ID, or undefined if not found
|
|
||||||
*/
|
|
||||||
export async function resolveGiteaRunId(
|
|
||||||
token: string,
|
|
||||||
owner: string,
|
|
||||||
repo: string,
|
|
||||||
): Promise<string | undefined> {
|
|
||||||
if (!USE_GITEA_API) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const runNumber = process.env.GITEA_RUN_NUMBER;
|
|
||||||
if (!runNumber) {
|
|
||||||
console.log("GITEA_RUN_NUMBER not set, cannot resolve Gitea run ID");
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Call Gitea API to get action runs
|
|
||||||
// GET /api/v1/repos/{owner}/{repo}/actions/runs
|
|
||||||
const url = `${GITHUB_API_URL}/repos/${owner}/${repo}/actions/runs`;
|
|
||||||
const response = await fetch(url, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `token ${token}`,
|
|
||||||
Accept: "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
console.error(
|
|
||||||
`Failed to fetch Gitea action runs: ${response.status} ${response.statusText}`,
|
|
||||||
);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = (await response.json()) as {
|
|
||||||
workflow_runs?: Array<{ id: number; run_number: number }>;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Find the run with matching run_number
|
|
||||||
const runs = data.workflow_runs || [];
|
|
||||||
const targetRunNumber = parseInt(runNumber, 10);
|
|
||||||
const matchingRun = runs.find((run) => run.run_number === targetRunNumber);
|
|
||||||
|
|
||||||
if (matchingRun) {
|
|
||||||
console.log(
|
|
||||||
`Resolved Gitea run ID: ${matchingRun.id} from run number: ${runNumber}`,
|
|
||||||
);
|
|
||||||
return String(matchingRun.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`Could not find Gitea run with run_number: ${runNumber} in ${runs.length} runs`,
|
|
||||||
);
|
|
||||||
return undefined;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error resolving Gitea run ID:", error);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the GITHUB_RUN_ID environment variable for Gitea.
|
|
||||||
* This should be called early in the action execution.
|
|
||||||
*/
|
|
||||||
export async function setupGiteaRunId(
|
|
||||||
token: string,
|
|
||||||
owner: string,
|
|
||||||
repo: string,
|
|
||||||
): Promise<void> {
|
|
||||||
// Only proceed if we're in Gitea mode
|
|
||||||
if (!USE_GITEA_API) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const runId = await resolveGiteaRunId(token, owner, repo);
|
|
||||||
if (runId) {
|
|
||||||
process.env.GITHUB_RUN_ID = runId;
|
|
||||||
console.log(`Set GITHUB_RUN_ID to ${runId} from Gitea API`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,292 +0,0 @@
|
|||||||
import type { Octokit } from "@octokit/rest";
|
|
||||||
|
|
||||||
// Type definitions for REST API responses
|
|
||||||
type RestFile = Awaited<
|
|
||||||
ReturnType<Octokit["rest"]["pulls"]["listFiles"]>
|
|
||||||
>["data"][number];
|
|
||||||
type RestComment = Awaited<
|
|
||||||
ReturnType<Octokit["rest"]["issues"]["listComments"]>
|
|
||||||
>["data"][number];
|
|
||||||
type RestCommit = Awaited<
|
|
||||||
ReturnType<Octokit["rest"]["pulls"]["listCommits"]>
|
|
||||||
>["data"][number];
|
|
||||||
type RestReview = Awaited<
|
|
||||||
ReturnType<Octokit["rest"]["pulls"]["listReviews"]>
|
|
||||||
>["data"][number];
|
|
||||||
type RestReviewComment = Awaited<
|
|
||||||
ReturnType<Octokit["rest"]["pulls"]["listReviewComments"]>
|
|
||||||
>["data"][number];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch complete Pull Request data including commits, files, comments, and reviews
|
|
||||||
*/
|
|
||||||
export async function fetchPullRequest(
|
|
||||||
octokit: Octokit,
|
|
||||||
owner: string,
|
|
||||||
repo: string,
|
|
||||||
number: number,
|
|
||||||
) {
|
|
||||||
// Fetch all PR data in parallel for better performance
|
|
||||||
const [prData, prFiles, prComments, prCommits, prReviews] = await Promise.all(
|
|
||||||
[
|
|
||||||
// Basic PR information
|
|
||||||
octokit.rest.pulls.get({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: number,
|
|
||||||
}),
|
|
||||||
// Changed files
|
|
||||||
octokit.rest.pulls.listFiles({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: number,
|
|
||||||
per_page: 100,
|
|
||||||
}),
|
|
||||||
// Issue comments (PR general comments)
|
|
||||||
octokit.rest.issues.listComments({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: number,
|
|
||||||
per_page: 100,
|
|
||||||
}),
|
|
||||||
// PR commits
|
|
||||||
octokit.rest.pulls.listCommits({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: number,
|
|
||||||
per_page: 100,
|
|
||||||
}),
|
|
||||||
// PR reviews
|
|
||||||
octokit.rest.pulls.listReviews({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: number,
|
|
||||||
per_page: 100,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Fetch review comments for each review using Gitea API
|
|
||||||
// Gitea endpoint: GET /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments
|
|
||||||
const reviewsWithComments = await Promise.all(
|
|
||||||
prReviews.data.map(async (review: RestReview) => {
|
|
||||||
try {
|
|
||||||
// Use Gitea-specific endpoint to get comments for each review
|
|
||||||
const response = await octokit.request(
|
|
||||||
"GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/comments",
|
|
||||||
{
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
pull_number: number,
|
|
||||||
review_id: review.id,
|
|
||||||
per_page: 100,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
...review,
|
|
||||||
comments: response.data,
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
// If fetching comments fails, return review with empty comments
|
|
||||||
// @ts-expect-error - console is available at runtime
|
|
||||||
console.warn(
|
|
||||||
`Failed to fetch comments for review ${review.id}:`,
|
|
||||||
error,
|
|
||||||
);
|
|
||||||
return {
|
|
||||||
...review,
|
|
||||||
comments: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Transform REST API response to match GraphQL-like structure
|
|
||||||
return {
|
|
||||||
repository: {
|
|
||||||
pullRequest: {
|
|
||||||
title: prData.data.title,
|
|
||||||
body: prData.data.body || "",
|
|
||||||
author: {
|
|
||||||
login: prData.data.user?.login || "",
|
|
||||||
name: prData.data.user?.name || undefined,
|
|
||||||
},
|
|
||||||
baseRefName: prData.data.base.ref,
|
|
||||||
headRefName: prData.data.head.ref,
|
|
||||||
headRefOid: prData.data.head.sha,
|
|
||||||
createdAt: prData.data.created_at,
|
|
||||||
updatedAt: prData.data.updated_at,
|
|
||||||
lastEditedAt: prData.data.updated_at, // Gitea may not have separate lastEditedAt
|
|
||||||
additions: prData.data.additions || 0,
|
|
||||||
deletions: prData.data.deletions || 0,
|
|
||||||
state: prData.data.state.toUpperCase(), // "open" -> "OPEN"
|
|
||||||
commits: {
|
|
||||||
totalCount: prCommits.data.length,
|
|
||||||
nodes: prCommits.data.map((commit: RestCommit) => ({
|
|
||||||
commit: {
|
|
||||||
oid: commit.sha,
|
|
||||||
message: commit.commit.message,
|
|
||||||
author: {
|
|
||||||
name: commit.commit.author?.name || "",
|
|
||||||
email: commit.commit.author?.email || "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
files: {
|
|
||||||
nodes: prFiles.data.map((file: RestFile) => ({
|
|
||||||
path: file.filename,
|
|
||||||
additions: file.additions,
|
|
||||||
deletions: file.deletions,
|
|
||||||
changeType: mapFileStatus(file.status),
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
comments: {
|
|
||||||
nodes: prComments.data.map((comment: RestComment) => ({
|
|
||||||
id: `comment_${comment.id}`,
|
|
||||||
databaseId: comment.id,
|
|
||||||
body: comment.body || "",
|
|
||||||
author: {
|
|
||||||
login: comment.user?.login || "",
|
|
||||||
},
|
|
||||||
createdAt: comment.created_at,
|
|
||||||
updatedAt: comment.updated_at,
|
|
||||||
lastEditedAt: comment.updated_at,
|
|
||||||
isMinimized: false, // Gitea may not support this
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
reviews: {
|
|
||||||
nodes: reviewsWithComments.map(
|
|
||||||
(review: RestReview & { comments: RestReviewComment[] }) => ({
|
|
||||||
id: `review_${review.id}`,
|
|
||||||
databaseId: review.id,
|
|
||||||
author: {
|
|
||||||
login: review.user?.login || "",
|
|
||||||
},
|
|
||||||
body: review.body || "",
|
|
||||||
state: review.state.toUpperCase(), // "APPROVED", "CHANGES_REQUESTED", etc.
|
|
||||||
submittedAt: review.submitted_at || review.created_at || "",
|
|
||||||
updatedAt: review.submitted_at || review.created_at || "",
|
|
||||||
lastEditedAt: review.submitted_at || review.created_at || "",
|
|
||||||
comments: {
|
|
||||||
nodes: review.comments.map((comment: RestReviewComment) => ({
|
|
||||||
id: `review_comment_${comment.id}`,
|
|
||||||
databaseId: comment.id,
|
|
||||||
body: comment.body || "",
|
|
||||||
path: comment.path,
|
|
||||||
line: comment.line || comment.original_line || null,
|
|
||||||
author: {
|
|
||||||
login: comment.user?.login || "",
|
|
||||||
},
|
|
||||||
createdAt: comment.created_at,
|
|
||||||
updatedAt: comment.updated_at,
|
|
||||||
lastEditedAt: comment.updated_at,
|
|
||||||
isMinimized: false,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch complete Issue data including comments
|
|
||||||
*/
|
|
||||||
export async function fetchIssue(
|
|
||||||
octokit: Octokit,
|
|
||||||
owner: string,
|
|
||||||
repo: string,
|
|
||||||
number: number,
|
|
||||||
) {
|
|
||||||
// Fetch issue data and comments in parallel
|
|
||||||
const [issueData, issueComments] = await Promise.all([
|
|
||||||
octokit.rest.issues.get({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: number,
|
|
||||||
}),
|
|
||||||
octokit.rest.issues.listComments({
|
|
||||||
owner,
|
|
||||||
repo,
|
|
||||||
issue_number: number,
|
|
||||||
per_page: 100,
|
|
||||||
}),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Transform REST API response to match GraphQL-like structure
|
|
||||||
return {
|
|
||||||
repository: {
|
|
||||||
issue: {
|
|
||||||
title: issueData.data.title,
|
|
||||||
body: issueData.data.body || "",
|
|
||||||
author: {
|
|
||||||
login: issueData.data.user?.login || "",
|
|
||||||
},
|
|
||||||
createdAt: issueData.data.created_at,
|
|
||||||
updatedAt: issueData.data.updated_at,
|
|
||||||
lastEditedAt: issueData.data.updated_at,
|
|
||||||
state: issueData.data.state.toUpperCase(),
|
|
||||||
comments: {
|
|
||||||
nodes: issueComments.data.map((comment: RestComment) => ({
|
|
||||||
id: `comment_${comment.id}`,
|
|
||||||
databaseId: comment.id,
|
|
||||||
body: comment.body || "",
|
|
||||||
author: {
|
|
||||||
login: comment.user?.login || "",
|
|
||||||
},
|
|
||||||
createdAt: comment.created_at,
|
|
||||||
updatedAt: comment.updated_at,
|
|
||||||
lastEditedAt: comment.updated_at,
|
|
||||||
isMinimized: false,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch user display name
|
|
||||||
*/
|
|
||||||
export async function fetchUser(octokit: Octokit, login: string) {
|
|
||||||
try {
|
|
||||||
const userData = await octokit.rest.users.getByUsername({
|
|
||||||
username: login,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
user: {
|
|
||||||
name: userData.data.name || userData.data.full_name || null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
// Note: console is available at runtime in Node.js environment
|
|
||||||
// @ts-expect-error - console is not in lib but available at runtime
|
|
||||||
console.warn(`Failed to fetch user ${login}:`, error);
|
|
||||||
return {
|
|
||||||
user: {
|
|
||||||
name: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map Gitea file status to GraphQL changeType format
|
|
||||||
*/
|
|
||||||
function mapFileStatus(status: string): string {
|
|
||||||
const statusMap: Record<string, string> = {
|
|
||||||
added: "ADDED",
|
|
||||||
modified: "MODIFIED",
|
|
||||||
removed: "DELETED",
|
|
||||||
renamed: "RENAMED",
|
|
||||||
copied: "COPIED",
|
|
||||||
changed: "MODIFIED",
|
|
||||||
};
|
|
||||||
|
|
||||||
return statusMap[status] || status.toUpperCase();
|
|
||||||
}
|
|
||||||
@ -1,8 +1,6 @@
|
|||||||
import { execFileSync } from "child_process";
|
import { execFileSync } from "child_process";
|
||||||
import type { Octokits } from "../api/client";
|
import type { Octokits } from "../api/client";
|
||||||
import { USE_GITEA_API } from "../api/config";
|
|
||||||
import { ISSUE_QUERY, PR_QUERY, USER_QUERY } from "../api/queries/github";
|
import { ISSUE_QUERY, PR_QUERY, USER_QUERY } from "../api/queries/github";
|
||||||
import { fetchPullRequest, fetchIssue, fetchUser } from "../api/queries/gitea";
|
|
||||||
import {
|
import {
|
||||||
isIssueCommentEvent,
|
isIssueCommentEvent,
|
||||||
isIssuesEvent,
|
isIssuesEvent,
|
||||||
@ -251,14 +249,12 @@ export async function fetchGitHubData({
|
|||||||
try {
|
try {
|
||||||
if (isPR) {
|
if (isPR) {
|
||||||
// Fetch PR data with all comments and file information
|
// Fetch PR data with all comments and file information
|
||||||
const prResult = USE_GITEA_API
|
const prResult = await octokits.graphql<PullRequestQueryResponse>(
|
||||||
? await fetchPullRequest(octokits.rest, owner, repo, parseInt(prNumber))
|
PR_QUERY,
|
||||||
: await octokits.graphql<PullRequestQueryResponse>(
|
{
|
||||||
PR_QUERY,
|
owner,
|
||||||
{
|
repo,
|
||||||
owner,
|
number: parseInt(prNumber),
|
||||||
repo,
|
|
||||||
number: parseInt(prNumber),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -274,7 +270,7 @@ export async function fetchGitHubData({
|
|||||||
includeCommentsByActor,
|
includeCommentsByActor,
|
||||||
excludeCommentsByActor,
|
excludeCommentsByActor,
|
||||||
);
|
);
|
||||||
reviewData = pullRequest.reviews || (USE_GITEA_API ? null : [])
|
reviewData = pullRequest.reviews || [];
|
||||||
|
|
||||||
console.log(`Successfully fetched PR #${prNumber} data`);
|
console.log(`Successfully fetched PR #${prNumber} data`);
|
||||||
} else {
|
} else {
|
||||||
@ -282,14 +278,12 @@ export async function fetchGitHubData({
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fetch issue data
|
// Fetch issue data
|
||||||
const issueResult = USE_GITEA_API
|
const issueResult = await octokits.graphql<IssueQueryResponse>(
|
||||||
? await fetchIssue(octokits.rest, owner, repo, parseInt(prNumber))
|
ISSUE_QUERY,
|
||||||
: await octokits.graphql<IssueQueryResponse>(
|
{
|
||||||
ISSUE_QUERY,
|
owner,
|
||||||
{
|
repo,
|
||||||
owner,
|
number: parseInt(prNumber),
|
||||||
repo,
|
|
||||||
number: parseInt(prNumber),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -481,11 +475,9 @@ export async function fetchUserDisplayName(
|
|||||||
login: string,
|
login: string,
|
||||||
): Promise<string | null> {
|
): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
const result = USE_GITEA_API
|
const result = await octokits.graphql<UserQueryResponse>(USER_QUERY, {
|
||||||
? await fetchUser(octokits.rest, login)
|
login,
|
||||||
: await octokits.graphql<UserQueryResponse>(USER_QUERY, {
|
});
|
||||||
login,
|
|
||||||
});
|
|
||||||
return result.user.name;
|
return result.user.name;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`Failed to fetch user display name for ${login}:`, error);
|
console.warn(`Failed to fetch user display name for ${login}:`, error);
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import type { Octokits } from "../api/client";
|
import type { Octokits } from "../api/client";
|
||||||
import { GITHUB_SERVER_URL, USE_GITEA_API } from "../api/config";
|
import { GITHUB_SERVER_URL } from "../api/config";
|
||||||
import { createBranchUrl } from "./comments/common";
|
|
||||||
import { $ } from "bun";
|
import { $ } from "bun";
|
||||||
|
|
||||||
export async function checkAndCommitOrDeleteBranch(
|
export async function checkAndCommitOrDeleteBranch(
|
||||||
@ -81,7 +80,7 @@ export async function checkAndCommitOrDeleteBranch(
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Set branch link since we now have commits
|
// Set branch link since we now have commits
|
||||||
const branchUrl = createBranchUrl(owner, repo, claudeBranch);
|
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`;
|
||||||
branchLink = `\n[View branch](${branchUrl})`;
|
branchLink = `\n[View branch](${branchUrl})`;
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
console.log(
|
||||||
@ -92,7 +91,7 @@ export async function checkAndCommitOrDeleteBranch(
|
|||||||
} catch (gitError) {
|
} catch (gitError) {
|
||||||
console.error("Error checking/committing changes:", gitError);
|
console.error("Error checking/committing changes:", gitError);
|
||||||
// If we can't check git status, assume the branch might have changes
|
// If we can't check git status, assume the branch might have changes
|
||||||
const branchUrl = createBranchUrl(owner, repo, claudeBranch);
|
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`;
|
||||||
branchLink = `\n[View branch](${branchUrl})`;
|
branchLink = `\n[View branch](${branchUrl})`;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -103,13 +102,13 @@ export async function checkAndCommitOrDeleteBranch(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Only add branch link if there are commits
|
// Only add branch link if there are commits
|
||||||
const branchUrl = createBranchUrl(owner, repo, claudeBranch);
|
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`;
|
||||||
branchLink = `\n[View branch](${branchUrl})`;
|
branchLink = `\n[View branch](${branchUrl})`;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error comparing commits on Claude branch:", error);
|
console.error("Error comparing commits on Claude branch:", error);
|
||||||
// If we can't compare but the branch exists remotely, include the branch link
|
// If we can't compare but the branch exists remotely, include the branch link
|
||||||
const branchUrl = createBranchUrl(owner, repo, claudeBranch);
|
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`;
|
||||||
branchLink = `\n[View branch](${branchUrl})`;
|
branchLink = `\n[View branch](${branchUrl})`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { GITHUB_SERVER_URL, USE_GITEA_API } from "../api/config";
|
import { GITHUB_SERVER_URL } from "../api/config";
|
||||||
import { createBranchUrl } from "./comments/common";
|
|
||||||
|
|
||||||
export type ExecutionDetails = {
|
export type ExecutionDetails = {
|
||||||
total_cost_usd?: number;
|
total_cost_usd?: number;
|
||||||
@ -158,12 +157,10 @@ export function updateCommentBody(input: CommentUpdateInput): string {
|
|||||||
|
|
||||||
// If we don't have a URL yet but have a branch name, construct it
|
// If we don't have a URL yet but have a branch name, construct it
|
||||||
if (!branchUrl && finalBranchName) {
|
if (!branchUrl && finalBranchName) {
|
||||||
// Extract owner/repo from jobUrl (works for both GitHub and Gitea URLs)
|
// Extract owner/repo from jobUrl
|
||||||
const repoMatch = USE_GITEA_API
|
const repoMatch = jobUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\//);
|
||||||
? jobUrl.match(/\/([^\/]+)\/([^\/]+)\/(?:actions|tree|src)/)
|
|
||||||
: jobUrl.match(/github\.com\/([^\/]+)\/([^\/]+)\//);
|
|
||||||
if (repoMatch) {
|
if (repoMatch) {
|
||||||
branchUrl = createBranchUrl(repoMatch[1], repoMatch[2], finalBranchName);
|
branchUrl = `${GITHUB_SERVER_URL}/${repoMatch[1]}/${repoMatch[2]}/tree/${finalBranchName}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { GITHUB_SERVER_URL, USE_GITEA_API } from "../../api/config";
|
import { GITHUB_SERVER_URL } from "../../api/config";
|
||||||
|
|
||||||
export const SPINNER_HTML =
|
export const SPINNER_HTML =
|
||||||
'<img src="https://github.com/user-attachments/assets/5ac382c7-e004-429b-8e35-7feb3e8f9c6f" width="14px" height="14px" style="vertical-align: middle; margin-left: 4px;" />';
|
'<img src="https://github.com/user-attachments/assets/5ac382c7-e004-429b-8e35-7feb3e8f9c6f" width="14px" height="14px" style="vertical-align: middle; margin-left: 4px;" />';
|
||||||
@ -17,23 +17,10 @@ export function createBranchLink(
|
|||||||
repo: string,
|
repo: string,
|
||||||
branchName: string,
|
branchName: string,
|
||||||
): string {
|
): string {
|
||||||
const branchUrl = createBranchUrl(owner, repo, branchName);
|
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${branchName}`;
|
||||||
return `\n[View branch](${branchUrl})`;
|
return `\n[View branch](${branchUrl})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a branch URL for the current platform
|
|
||||||
*/
|
|
||||||
export function createBranchUrl(
|
|
||||||
owner: string,
|
|
||||||
repo: string,
|
|
||||||
branchName: string,
|
|
||||||
): string {
|
|
||||||
return USE_GITEA_API
|
|
||||||
? `${GITHUB_SERVER_URL}/${owner}/${repo}/src/branch/${branchName}`
|
|
||||||
: `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${branchName}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createCommentBody(
|
export function createCommentBody(
|
||||||
jobRunLink: string,
|
jobRunLink: string,
|
||||||
branchLink: string = "",
|
branchLink: string = "",
|
||||||
|
|||||||
@ -7,25 +7,17 @@
|
|||||||
|
|
||||||
import type { Octokit } from "@octokit/rest";
|
import type { Octokit } from "@octokit/rest";
|
||||||
import type { GitHubContext } from "../context";
|
import type { GitHubContext } from "../context";
|
||||||
import { USE_GITEA_API } from "../api/config.ts";
|
|
||||||
|
|
||||||
export async function checkHumanActor(
|
export async function checkHumanActor(
|
||||||
octokit: Octokit,
|
octokit: Octokit,
|
||||||
githubContext: GitHubContext,
|
githubContext: GitHubContext,
|
||||||
) {
|
) {
|
||||||
|
// Fetch user information from GitHub API
|
||||||
|
const { data: userData } = await octokit.users.getByUsername({
|
||||||
|
username: githubContext.actor,
|
||||||
|
});
|
||||||
|
|
||||||
let actorType: string
|
const actorType = userData.type;
|
||||||
if (USE_GITEA_API) {
|
|
||||||
actorType = "Bot"
|
|
||||||
console.log("Gitea does not provide an API to determine whether a user is a bot. Assuming the user is a bot and verify whether it is listed in allowedBots.")
|
|
||||||
}else{
|
|
||||||
// Fetch user information from GitHub API
|
|
||||||
const { data: userData } = await octokit.users.getByUsername({
|
|
||||||
username: githubContext.actor,
|
|
||||||
});
|
|
||||||
|
|
||||||
actorType = userData.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Actor type: ${actorType}`);
|
console.log(`Actor type: ${actorType}`);
|
||||||
|
|
||||||
|
|||||||
@ -59,7 +59,7 @@ export async function checkWritePermissions(
|
|||||||
const permissionLevel = response.data.permission;
|
const permissionLevel = response.data.permission;
|
||||||
core.info(`Permission level retrieved: ${permissionLevel}`);
|
core.info(`Permission level retrieved: ${permissionLevel}`);
|
||||||
|
|
||||||
if (permissionLevel === "admin" || permissionLevel === "write" || permissionLevel === "owner") {
|
if (permissionLevel === "admin" || permissionLevel === "write") {
|
||||||
core.info(`Actor has write access: ${permissionLevel}`);
|
core.info(`Actor has write access: ${permissionLevel}`);
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -65,7 +65,6 @@ export function detectMode(context: GitHubContext): AutoDetectedMode {
|
|||||||
const supportedActions = [
|
const supportedActions = [
|
||||||
"opened",
|
"opened",
|
||||||
"synchronize",
|
"synchronize",
|
||||||
"synchronized",
|
|
||||||
"ready_for_review",
|
"ready_for_review",
|
||||||
"reopened",
|
"reopened",
|
||||||
];
|
];
|
||||||
@ -113,7 +112,6 @@ function validateTrackProgressEvent(context: GitHubContext): void {
|
|||||||
const validActions = [
|
const validActions = [
|
||||||
"opened",
|
"opened",
|
||||||
"synchronize",
|
"synchronize",
|
||||||
"synchronized",
|
|
||||||
"ready_for_review",
|
"ready_for_review",
|
||||||
"reopened",
|
"reopened",
|
||||||
];
|
];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user