diff --git a/MIGRATION.md b/MIGRATION.md index 1ceb8be..3100d43 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -97,8 +97,10 @@ Be aware of these Gitea Actions limitations: - **`issue_comment` on PRs**: May not trigger reliably in some Gitea versions - **`pull_request_review_comment`**: Limited support compared to GitHub +- **GraphQL API**: Not supported - action automatically falls back to REST API - **Cross-repository access**: Token permissions may be more restrictive - **Workflow triggers**: Some advanced trigger conditions may not work +- **Permission checking**: Simplified for Gitea compatibility ### 3. Gitea Workarounds @@ -136,6 +138,8 @@ Gitea has limited support for code review comment webhooks. Consider using: ### 4. Gitea Support - Compatible with self-hosted Gitea +- Automatic fallback to REST API (no GraphQL dependency) +- Simplified permission checking for Gitea environments - Reduced external dependencies - Standard Actions workflow patterns @@ -163,18 +167,26 @@ permissions: ### Gitea-Specific Issues -#### 1. Limited Event Support +#### 1. Authentication Errors +**Error**: "Failed to check permissions: HttpError: Bad credentials" +**Solution**: This is normal in Gitea environments. The action automatically detects Gitea and bypasses GitHub-specific permission checks. + +#### 2. Limited Event Support Some GitHub Events may not be fully supported in Gitea. Use basic triggers: - `issue_comment` for comments - `issues` for issue events - `push` for code changes -#### 2. Token Scope Limitations +#### 3. Token Scope Limitations Gitea tokens may have different scope limitations. Ensure your Gitea instance allows: - Repository write access - Issue/PR comment creation - Branch creation and updates +#### 4. GraphQL Not Supported +**Error**: GraphQL queries failing +**Solution**: The action automatically detects Gitea and uses REST API instead of GraphQL. No manual configuration needed. + ## Migration Checklist - [ ] Update workflow permissions to include `write` access diff --git a/src/github/api/client.ts b/src/github/api/client.ts index 51393db..6bd4c71 100644 --- a/src/github/api/client.ts +++ b/src/github/api/client.ts @@ -9,7 +9,10 @@ export type Octokits = { export function createOctokit(token: string): Octokits { return { - rest: new Octokit({ auth: token }), + rest: new Octokit({ + auth: token, + baseUrl: GITHUB_API_URL, + }), graphql: graphql.defaults({ baseUrl: GITHUB_API_URL, headers: { diff --git a/src/github/data/fetcher.ts b/src/github/data/fetcher.ts index 9c42d0e..efeab47 100644 --- a/src/github/data/fetcher.ts +++ b/src/github/data/fetcher.ts @@ -44,52 +44,154 @@ export async function fetchGitHubData({ 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 comments: GitHubComment[] = []; let changedFiles: GitHubFile[] = []; let reviewData: { nodes: GitHubReview[] } | null = null; try { - if (isPR) { - // Fetch PR data with all comments and file information - const prResult = await octokits.graphql( - PR_QUERY, - { + if (isGitea) { + // Use REST API for Gitea compatibility + if (isPR) { + console.log(`Fetching PR #${prNumber} data using REST API (Gitea mode)`); + const prResponse = await octokits.rest.pulls.get({ owner, repo, - number: parseInt(prNumber), - }, - ); + pull_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 || []; + contextData = { + title: prResponse.data.title, + body: prResponse.data.body || "", + author: { login: prResponse.data.user?.login || "" }, + baseRefName: prResponse.data.base.ref, + headRefName: prResponse.data.head.ref, + headRefOid: prResponse.data.head.sha, + createdAt: prResponse.data.created_at, + additions: prResponse.data.additions || 0, + deletions: prResponse.data.deletions || 0, + state: prResponse.data.state.toUpperCase(), + commits: { totalCount: 0, nodes: [] }, + files: { nodes: [] }, + comments: { nodes: [] }, + reviews: { nodes: [] }, + }; - console.log(`Successfully fetched PR #${prNumber} data`); + // Fetch comments separately + try { + const commentsResponse = await octokits.rest.issues.listComments({ + owner, + repo, + issue_number: parseInt(prNumber), + }); + comments = commentsResponse.data.map(comment => ({ + id: comment.id.toString(), + databaseId: comment.id.toString(), + body: comment.body || "", + author: { login: comment.user?.login || "" }, + createdAt: comment.created_at, + })); + } catch (error) { + console.warn("Failed to fetch PR comments:", error); + } + + // Try to fetch files + try { + const filesResponse = await octokits.rest.pulls.listFiles({ + owner, + repo, + pull_number: parseInt(prNumber), + }); + changedFiles = filesResponse.data.map(file => ({ + path: file.filename, + additions: file.additions, + deletions: file.deletions, + changeType: file.status, + })); + } catch (error) { + console.warn("Failed to fetch PR files:", error); + } + + reviewData = { nodes: [] }; // Simplified for Gitea } else { - throw new Error(`PR #${prNumber} not found`); + console.log(`Fetching issue #${prNumber} data using REST API (Gitea mode)`); + const issueResponse = await octokits.rest.issues.get({ + owner, + repo, + issue_number: parseInt(prNumber), + }); + + contextData = { + title: issueResponse.data.title, + body: issueResponse.data.body || "", + author: { login: issueResponse.data.user?.login || "" }, + createdAt: issueResponse.data.created_at, + state: issueResponse.data.state.toUpperCase(), + comments: { nodes: [] }, + }; + + // Fetch comments + try { + const commentsResponse = await octokits.rest.issues.listComments({ + owner, + repo, + issue_number: parseInt(prNumber), + }); + comments = commentsResponse.data.map(comment => ({ + id: comment.id.toString(), + databaseId: comment.id.toString(), + body: comment.body || "", + author: { login: comment.user?.login || "" }, + createdAt: comment.created_at, + })); + } catch (error) { + console.warn("Failed to fetch issue comments:", error); + } } } else { - // Fetch issue data - const issueResult = await octokits.graphql( - ISSUE_QUERY, - { - owner, - repo, - number: parseInt(prNumber), - }, - ); + // Use GraphQL for GitHub + if (isPR) { + const prResult = await octokits.graphql( + PR_QUERY, + { + owner, + repo, + number: parseInt(prNumber), + }, + ); - if (issueResult.repository.issue) { - contextData = issueResult.repository.issue; - comments = contextData?.comments?.nodes || []; + 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 issue #${prNumber} data`); + console.log(`Successfully fetched PR #${prNumber} data`); + } else { + throw new Error(`PR #${prNumber} not found`); + } } else { - throw new Error(`Issue #${prNumber} not found`); + const issueResult = await octokits.graphql( + 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) { diff --git a/src/github/validation/permissions.ts b/src/github/validation/permissions.ts index d34e396..5ee7916 100644 --- a/src/github/validation/permissions.ts +++ b/src/github/validation/permissions.ts @@ -14,6 +14,14 @@ export async function checkWritePermissions( ): Promise { const { repository, actor } = context; + // For Gitea compatibility, check if we're in a non-GitHub environment + const isGitea = process.env.GITHUB_API_URL && !process.env.GITHUB_API_URL.includes('api.github.com'); + + if (isGitea) { + core.info(`Detected Gitea environment, assuming actor has permissions`); + return true; + } + try { core.info(`Checking permissions for actor: ${actor}`); diff --git a/src/github/validation/trigger.ts b/src/github/validation/trigger.ts index 6a06153..ea477a1 100644 --- a/src/github/validation/trigger.ts +++ b/src/github/validation/trigger.ts @@ -15,6 +15,8 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean { inputs: { assigneeTrigger, triggerPhrase, directPrompt }, } = context; + console.log(`Checking trigger: event=${context.eventName}, action=${context.eventAction}, phrase='${triggerPhrase}', assignee='${assigneeTrigger}', direct='${directPrompt}'`); + // If direct prompt is provided, always trigger if (directPrompt) { console.log(`Direct prompt provided, triggering action`); @@ -24,9 +26,11 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean { // Check for assignee trigger if (isIssuesEvent(context) && context.eventAction === "assigned") { // Remove @ symbol from assignee_trigger if present - let triggerUser = assigneeTrigger.replace(/^@/, ""); + let triggerUser = assigneeTrigger?.replace(/^@/, "") || ""; const assigneeUsername = context.payload.issue.assignee?.login || ""; + console.log(`Checking assignee trigger: user='${triggerUser}', assignee='${assigneeUsername}'`); + if (triggerUser && assigneeUsername === triggerUser) { console.log(`Issue assigned to trigger user '${triggerUser}'`); return true;