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:
30
MIGRATION.md
30
MIGRATION.md
@@ -5,16 +5,19 @@ This document outlines the changes made to migrate from GitHub App authenticatio
|
|||||||
## What Changed
|
## What Changed
|
||||||
|
|
||||||
### 1. Removed GitHub App Dependencies
|
### 1. Removed GitHub App Dependencies
|
||||||
|
|
||||||
- **Before**: Used OIDC token exchange with Anthropic's GitHub App service
|
- **Before**: Used OIDC token exchange with Anthropic's GitHub App service
|
||||||
- **After**: Uses standard `GITHUB_TOKEN` from workflow environment
|
- **After**: Uses standard `GITHUB_TOKEN` from workflow environment
|
||||||
- **Benefit**: No external dependencies, works with any Git provider
|
- **Benefit**: No external dependencies, works with any Git provider
|
||||||
|
|
||||||
### 2. Self-Contained Implementation
|
### 2. Self-Contained Implementation
|
||||||
|
|
||||||
- **Before**: Depended on external `anthropics/claude-code-base-action`
|
- **Before**: Depended on external `anthropics/claude-code-base-action`
|
||||||
- **After**: Includes built-in Claude execution engine
|
- **After**: Includes built-in Claude execution engine
|
||||||
- **Benefit**: Complete control over functionality, no external action dependencies
|
- **Benefit**: Complete control over functionality, no external action dependencies
|
||||||
|
|
||||||
### 3. Gitea Compatibility
|
### 3. Gitea Compatibility
|
||||||
|
|
||||||
- **Before**: GitHub-specific triggers and authentication
|
- **Before**: GitHub-specific triggers and authentication
|
||||||
- **After**: Compatible with Gitea Actions (with some limitations)
|
- **After**: Compatible with Gitea Actions (with some limitations)
|
||||||
- **Benefit**: Works with self-hosted Gitea instances
|
- **Benefit**: Works with self-hosted Gitea instances
|
||||||
@@ -22,6 +25,7 @@ This document outlines the changes made to migrate from GitHub App authenticatio
|
|||||||
## Required Changes for Existing Users
|
## Required Changes for Existing Users
|
||||||
|
|
||||||
### Workflow Permissions
|
### Workflow Permissions
|
||||||
|
|
||||||
Update your workflow permissions:
|
Update your workflow permissions:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -40,6 +44,7 @@ permissions:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Required Token Input
|
### Required Token Input
|
||||||
|
|
||||||
Now required to explicitly provide a GitHub token:
|
Now required to explicitly provide a GitHub token:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -58,6 +63,7 @@ Now required to explicitly provide a GitHub token:
|
|||||||
## Gitea Setup
|
## Gitea Setup
|
||||||
|
|
||||||
### 1. Basic Gitea Workflow
|
### 1. Basic Gitea Workflow
|
||||||
|
|
||||||
Use the example in `examples/gitea-claude.yml`:
|
Use the example in `examples/gitea-claude.yml`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -86,13 +92,14 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Run Claude Assistant
|
- name: Run Claude Assistant
|
||||||
uses: ./ # Adjust path as needed for your Gitea setup
|
uses: ./ # Adjust path as needed for your Gitea setup
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Gitea Limitations
|
### 2. Gitea Limitations
|
||||||
|
|
||||||
Be aware of these Gitea Actions limitations:
|
Be aware of these Gitea Actions limitations:
|
||||||
|
|
||||||
- **`issue_comment` on PRs**: May not trigger reliably in some Gitea versions
|
- **`issue_comment` on PRs**: May not trigger reliably in some Gitea versions
|
||||||
@@ -105,16 +112,19 @@ Be aware of these Gitea Actions limitations:
|
|||||||
### 3. Gitea Workarounds
|
### 3. Gitea Workarounds
|
||||||
|
|
||||||
#### For PR Comments
|
#### For PR Comments
|
||||||
|
|
||||||
Use `issue_comment` instead of `pull_request_review_comment`:
|
Use `issue_comment` instead of `pull_request_review_comment`:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
on:
|
on:
|
||||||
issue_comment:
|
issue_comment:
|
||||||
types: [created] # This covers both issue and PR comments
|
types: [created] # This covers both issue and PR comments
|
||||||
```
|
```
|
||||||
|
|
||||||
#### For Code Review Comments
|
#### For Code Review Comments
|
||||||
|
|
||||||
Gitea has limited support for code review comment webhooks. Consider using:
|
Gitea has limited support for code review comment webhooks. Consider using:
|
||||||
|
|
||||||
- Regular issue comments on PRs
|
- Regular issue comments on PRs
|
||||||
- Manual trigger via issue assignment
|
- Manual trigger via issue assignment
|
||||||
- Custom webhooks (advanced setup)
|
- Custom webhooks (advanced setup)
|
||||||
@@ -122,21 +132,25 @@ Gitea has limited support for code review comment webhooks. Consider using:
|
|||||||
## Benefits of Migration
|
## Benefits of Migration
|
||||||
|
|
||||||
### 1. Simplified Authentication
|
### 1. Simplified Authentication
|
||||||
|
|
||||||
- No OIDC token setup required
|
- No OIDC token setup required
|
||||||
- Uses standard workflow tokens
|
- Uses standard workflow tokens
|
||||||
- Works with custom GitHub tokens
|
- Works with custom GitHub tokens
|
||||||
|
|
||||||
### 2. Provider Independence
|
### 2. Provider Independence
|
||||||
|
|
||||||
- No dependency on Anthropic's GitHub App service
|
- No dependency on Anthropic's GitHub App service
|
||||||
- Works with any Git provider supporting Actions
|
- Works with any Git provider supporting Actions
|
||||||
- Self-contained functionality
|
- Self-contained functionality
|
||||||
|
|
||||||
### 3. Enhanced Control
|
### 3. Enhanced Control
|
||||||
|
|
||||||
- Direct control over Claude execution
|
- Direct control over Claude execution
|
||||||
- Customizable tool management
|
- Customizable tool management
|
||||||
- Easier debugging and modifications
|
- Easier debugging and modifications
|
||||||
|
|
||||||
### 4. Gitea Support
|
### 4. Gitea Support
|
||||||
|
|
||||||
- Compatible with self-hosted Gitea
|
- Compatible with self-hosted Gitea
|
||||||
- Automatic fallback to REST API (no GraphQL dependency)
|
- Automatic fallback to REST API (no GraphQL dependency)
|
||||||
- Simplified permission checking for Gitea environments
|
- Simplified permission checking for Gitea environments
|
||||||
@@ -148,8 +162,10 @@ Gitea has limited support for code review comment webhooks. Consider using:
|
|||||||
### Common Issues
|
### Common Issues
|
||||||
|
|
||||||
#### 1. Token Permissions
|
#### 1. Token Permissions
|
||||||
|
|
||||||
**Error**: "GitHub token authentication failed"
|
**Error**: "GitHub token authentication failed"
|
||||||
**Solution**: Ensure workflow has required permissions:
|
**Solution**: Ensure workflow has required permissions:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
@@ -158,36 +174,45 @@ permissions:
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### 2. Gitea Trigger Issues
|
#### 2. Gitea Trigger Issues
|
||||||
|
|
||||||
**Error**: Workflow not triggering on PR comments
|
**Error**: Workflow not triggering on PR comments
|
||||||
**Solution**: Use `issue_comment` instead of `pull_request_review_comment`
|
**Solution**: Use `issue_comment` instead of `pull_request_review_comment`
|
||||||
|
|
||||||
#### 3. Missing Dependencies
|
#### 3. Missing Dependencies
|
||||||
|
|
||||||
**Error**: "Module not found" or TypeScript errors
|
**Error**: "Module not found" or TypeScript errors
|
||||||
**Solution**: Run `npm install` or `bun install` to update dependencies
|
**Solution**: Run `npm install` or `bun install` to update dependencies
|
||||||
|
|
||||||
### Gitea-Specific Issues
|
### Gitea-Specific Issues
|
||||||
|
|
||||||
#### 1. Authentication Errors
|
#### 1. Authentication Errors
|
||||||
|
|
||||||
**Error**: "Failed to check permissions: HttpError: Bad credentials"
|
**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.
|
**Solution**: This is normal in Gitea environments. The action automatically detects Gitea and bypasses GitHub-specific permission checks.
|
||||||
|
|
||||||
#### 1a. User Profile API Errors
|
#### 1a. User Profile API Errors
|
||||||
|
|
||||||
**Error**: "Prepare step failed with error: Visit Project" or "GET /users/{username} - 404"
|
**Error**: "Prepare step failed with error: Visit Project" or "GET /users/{username} - 404"
|
||||||
**Solution**: This occurs when Gitea's user profile API differs from GitHub's. The action automatically detects Gitea and skips user type validation.
|
**Solution**: This occurs when Gitea's user profile API differs from GitHub's. The action automatically detects Gitea and skips user type validation.
|
||||||
|
|
||||||
#### 2. Limited Event Support
|
#### 2. Limited Event Support
|
||||||
|
|
||||||
Some GitHub Events may not be fully supported in Gitea. Use basic triggers:
|
Some GitHub Events may not be fully supported in Gitea. Use basic triggers:
|
||||||
|
|
||||||
- `issue_comment` for comments
|
- `issue_comment` for comments
|
||||||
- `issues` for issue events
|
- `issues` for issue events
|
||||||
- `push` for code changes
|
- `push` for code changes
|
||||||
|
|
||||||
#### 3. Token Scope Limitations
|
#### 3. Token Scope Limitations
|
||||||
|
|
||||||
Gitea tokens may have different scope limitations. Ensure your Gitea instance allows:
|
Gitea tokens may have different scope limitations. Ensure your Gitea instance allows:
|
||||||
|
|
||||||
- Repository write access
|
- Repository write access
|
||||||
- Issue/PR comment creation
|
- Issue/PR comment creation
|
||||||
- Branch creation and updates
|
- Branch creation and updates
|
||||||
|
|
||||||
#### 4. GraphQL Not Supported
|
#### 4. GraphQL Not Supported
|
||||||
|
|
||||||
**Error**: GraphQL queries failing
|
**Error**: GraphQL queries failing
|
||||||
**Solution**: The action automatically detects Gitea and uses REST API instead of GraphQL. No manual configuration needed.
|
**Solution**: The action automatically detects Gitea and uses REST API instead of GraphQL. No manual configuration needed.
|
||||||
|
|
||||||
@@ -204,5 +229,6 @@ Gitea tokens may have different scope limitations. Ensure your Gitea instance al
|
|||||||
## Example Workflows
|
## Example Workflows
|
||||||
|
|
||||||
See the `examples/` directory for complete workflow examples:
|
See the `examples/` directory for complete workflow examples:
|
||||||
|
|
||||||
- `claude.yml` - Updated GitHub Actions workflow
|
- `claude.yml` - Updated GitHub Actions workflow
|
||||||
- `gitea-claude.yml` - Gitea-compatible workflow
|
- `gitea-claude.yml` - Gitea-compatible workflow
|
||||||
@@ -163,4 +163,3 @@ runs:
|
|||||||
echo '```json' >> $GITHUB_STEP_SUMMARY
|
echo '```json' >> $GITHUB_STEP_SUMMARY
|
||||||
cat "${{ steps.claude-code.outputs.execution_file }}" >> $GITHUB_STEP_SUMMARY
|
cat "${{ steps.claude-code.outputs.execution_file }}" >> $GITHUB_STEP_SUMMARY
|
||||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Run Claude Assistant
|
- name: Run Claude Assistant
|
||||||
uses: ./ # Use local action (adjust path as needed)
|
uses: ./ # Use local action (adjust path as needed)
|
||||||
with:
|
with:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }} # Use standard workflow token
|
github_token: ${{ secrets.GITHUB_TOKEN }} # Use standard workflow token
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
timeout_minutes: "60"
|
timeout_minutes: "60"
|
||||||
trigger_phrase: "@claude"
|
trigger_phrase: "@claude"
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ export class ClaudeExecutor {
|
|||||||
|
|
||||||
private initializeClient() {
|
private initializeClient() {
|
||||||
if (this.config.useBedrock || this.config.useVertex) {
|
if (this.config.useBedrock || this.config.useVertex) {
|
||||||
throw new Error("Bedrock and Vertex AI not supported in simplified implementation");
|
throw new Error(
|
||||||
|
"Bedrock and Vertex AI not supported in simplified implementation",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.config.apiKey) {
|
if (!this.config.apiKey) {
|
||||||
@@ -63,11 +65,17 @@ export class ClaudeExecutor {
|
|||||||
|
|
||||||
private parseTools(): { allowed: string[]; disallowed: string[] } {
|
private parseTools(): { allowed: string[]; disallowed: string[] } {
|
||||||
const allowed = this.config.allowedTools
|
const allowed = this.config.allowedTools
|
||||||
? this.config.allowedTools.split(",").map(t => t.trim()).filter(Boolean)
|
? this.config.allowedTools
|
||||||
|
.split(",")
|
||||||
|
.map((t) => t.trim())
|
||||||
|
.filter(Boolean)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const disallowed = this.config.disallowedTools
|
const disallowed = this.config.disallowedTools
|
||||||
? this.config.disallowedTools.split(",").map(t => t.trim()).filter(Boolean)
|
? this.config.disallowedTools
|
||||||
|
.split(",")
|
||||||
|
.map((t) => t.trim())
|
||||||
|
.filter(Boolean)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return { allowed, disallowed };
|
return { allowed, disallowed };
|
||||||
@@ -92,7 +100,9 @@ export class ClaudeExecutor {
|
|||||||
const prompt = await this.readPrompt();
|
const prompt = await this.readPrompt();
|
||||||
const tools = this.parseTools();
|
const tools = this.parseTools();
|
||||||
|
|
||||||
console.log(`Executing Claude with model: ${this.config.model || "claude-3-7-sonnet-20250219"}`);
|
console.log(
|
||||||
|
`Executing Claude with model: ${this.config.model || "claude-3-7-sonnet-20250219"}`,
|
||||||
|
);
|
||||||
console.log(`Allowed tools: ${tools.allowed.join(", ") || "none"}`);
|
console.log(`Allowed tools: ${tools.allowed.join(", ") || "none"}`);
|
||||||
console.log(`Disallowed tools: ${tools.disallowed.join(", ") || "none"}`);
|
console.log(`Disallowed tools: ${tools.disallowed.join(", ") || "none"}`);
|
||||||
|
|
||||||
@@ -134,7 +144,9 @@ export class ClaudeExecutor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runClaude(config: ClaudeExecutorConfig): Promise<ClaudeExecutorResult> {
|
export async function runClaude(
|
||||||
|
config: ClaudeExecutorConfig,
|
||||||
|
): Promise<ClaudeExecutorResult> {
|
||||||
const executor = new ClaudeExecutor(config);
|
const executor = new ClaudeExecutor(config);
|
||||||
return await executor.execute();
|
return await executor.execute();
|
||||||
}
|
}
|
||||||
@@ -10,8 +10,12 @@ async function main() {
|
|||||||
model: process.env.ANTHROPIC_MODEL || process.env.MODEL,
|
model: process.env.ANTHROPIC_MODEL || process.env.MODEL,
|
||||||
promptFile: process.env.PROMPT_FILE,
|
promptFile: process.env.PROMPT_FILE,
|
||||||
prompt: process.env.PROMPT,
|
prompt: process.env.PROMPT,
|
||||||
maxTurns: process.env.MAX_TURNS ? parseInt(process.env.MAX_TURNS) : undefined,
|
maxTurns: process.env.MAX_TURNS
|
||||||
timeoutMinutes: process.env.TIMEOUT_MINUTES ? parseInt(process.env.TIMEOUT_MINUTES) : 30,
|
? parseInt(process.env.MAX_TURNS)
|
||||||
|
: undefined,
|
||||||
|
timeoutMinutes: process.env.TIMEOUT_MINUTES
|
||||||
|
? parseInt(process.env.TIMEOUT_MINUTES)
|
||||||
|
: 30,
|
||||||
mcpConfig: process.env.MCP_CONFIG,
|
mcpConfig: process.env.MCP_CONFIG,
|
||||||
allowedTools: process.env.ALLOWED_TOOLS,
|
allowedTools: process.env.ALLOWED_TOOLS,
|
||||||
disallowedTools: process.env.DISALLOWED_TOOLS,
|
disallowedTools: process.env.DISALLOWED_TOOLS,
|
||||||
|
|||||||
@@ -133,7 +133,68 @@ async function run() {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error checking for changes in branch:", error);
|
console.error("Error checking for changes in branch:", error);
|
||||||
// Don't fail the entire update if we can't check for changes
|
|
||||||
|
// For Gitea compatibility, try alternative approach
|
||||||
|
try {
|
||||||
|
console.log(
|
||||||
|
"Trying alternative branch comparison for Gitea compatibility...",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get the branch info to see if it exists and has commits
|
||||||
|
const branchResponse = await octokit.rest.repos.getBranch({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
branch: claudeBranch,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get base branch info for comparison
|
||||||
|
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) {
|
||||||
|
console.log(
|
||||||
|
`Branch ${claudeBranch} appears to have changes (different SHA from base)`,
|
||||||
|
);
|
||||||
|
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})`;
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`Branch ${claudeBranch} has same SHA as base, no PR link needed`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} 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 fallback since we can't determine branch status",
|
||||||
|
);
|
||||||
|
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})`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ export async function fetchGitHubData({
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if we're in a Gitea environment (no GraphQL support)
|
// 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');
|
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[] = [];
|
||||||
@@ -56,7 +58,9 @@ export async function fetchGitHubData({
|
|||||||
if (isGitea) {
|
if (isGitea) {
|
||||||
// Use REST API for Gitea compatibility
|
// Use REST API for Gitea compatibility
|
||||||
if (isPR) {
|
if (isPR) {
|
||||||
console.log(`Fetching PR #${prNumber} data using REST API (Gitea mode)`);
|
console.log(
|
||||||
|
`Fetching PR #${prNumber} data using REST API (Gitea mode)`,
|
||||||
|
);
|
||||||
const prResponse = await octokits.rest.pulls.get({
|
const prResponse = await octokits.rest.pulls.get({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
@@ -87,7 +91,7 @@ export async function fetchGitHubData({
|
|||||||
repo,
|
repo,
|
||||||
issue_number: parseInt(prNumber),
|
issue_number: parseInt(prNumber),
|
||||||
});
|
});
|
||||||
comments = commentsResponse.data.map(comment => ({
|
comments = commentsResponse.data.map((comment) => ({
|
||||||
id: comment.id.toString(),
|
id: comment.id.toString(),
|
||||||
databaseId: comment.id.toString(),
|
databaseId: comment.id.toString(),
|
||||||
body: comment.body || "",
|
body: comment.body || "",
|
||||||
@@ -106,7 +110,7 @@ export async function fetchGitHubData({
|
|||||||
repo,
|
repo,
|
||||||
pull_number: parseInt(prNumber),
|
pull_number: parseInt(prNumber),
|
||||||
});
|
});
|
||||||
changedFiles = filesResponse.data.map(file => ({
|
changedFiles = filesResponse.data.map((file) => ({
|
||||||
path: file.filename,
|
path: file.filename,
|
||||||
additions: file.additions || 0,
|
additions: file.additions || 0,
|
||||||
deletions: file.deletions || 0,
|
deletions: file.deletions || 0,
|
||||||
@@ -119,7 +123,9 @@ export async function fetchGitHubData({
|
|||||||
|
|
||||||
reviewData = { nodes: [] }; // Simplified for Gitea
|
reviewData = { nodes: [] }; // Simplified for Gitea
|
||||||
} else {
|
} else {
|
||||||
console.log(`Fetching issue #${prNumber} data using REST API (Gitea mode)`);
|
console.log(
|
||||||
|
`Fetching issue #${prNumber} data using REST API (Gitea mode)`,
|
||||||
|
);
|
||||||
const issueResponse = await octokits.rest.issues.get({
|
const issueResponse = await octokits.rest.issues.get({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
@@ -142,7 +148,7 @@ export async function fetchGitHubData({
|
|||||||
repo,
|
repo,
|
||||||
issue_number: parseInt(prNumber),
|
issue_number: parseInt(prNumber),
|
||||||
});
|
});
|
||||||
comments = commentsResponse.data.map(comment => ({
|
comments = commentsResponse.data.map((comment) => ({
|
||||||
id: comment.id.toString(),
|
id: comment.id.toString(),
|
||||||
databaseId: comment.id.toString(),
|
databaseId: comment.id.toString(),
|
||||||
body: comment.body || "",
|
body: comment.body || "",
|
||||||
|
|||||||
@@ -34,9 +34,49 @@ export async function checkAndDeleteEmptyBranch(
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error checking for commits on Claude branch:", error);
|
console.error("Error checking for commits on Claude branch:", error);
|
||||||
// If we can't check, assume the branch has commits to be safe
|
|
||||||
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`;
|
// For Gitea compatibility, try alternative approach using branches endpoint
|
||||||
branchLink = `\n[View branch](${branchUrl})`;
|
try {
|
||||||
|
console.log(
|
||||||
|
"Trying alternative branch comparison for Gitea compatibility...",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get the branch info to see if it exists and has commits
|
||||||
|
const branchResponse = await octokit.rest.repos.getBranch({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
branch: claudeBranch,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get base branch info for comparison
|
||||||
|
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 commits
|
||||||
|
if (branchSha !== baseSha) {
|
||||||
|
console.log(
|
||||||
|
`Branch ${claudeBranch} appears to have commits (different SHA from base)`,
|
||||||
|
);
|
||||||
|
const branchUrl = `${GITHUB_SERVER_URL}/${owner}/${repo}/tree/${claudeBranch}`;
|
||||||
|
branchLink = `\n[View branch](${branchUrl})`;
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`Branch ${claudeBranch} has same SHA as base, marking for deletion`,
|
||||||
|
);
|
||||||
|
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})`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,9 +89,18 @@ export async function checkAndDeleteEmptyBranch(
|
|||||||
ref: `heads/${claudeBranch}`,
|
ref: `heads/${claudeBranch}`,
|
||||||
});
|
});
|
||||||
console.log(`✅ Deleted empty branch: ${claudeBranch}`);
|
console.log(`✅ Deleted empty branch: ${claudeBranch}`);
|
||||||
} catch (deleteError) {
|
} catch (deleteError: any) {
|
||||||
console.error(`Failed to delete branch ${claudeBranch}:`, deleteError);
|
console.error(`Failed to delete branch ${claudeBranch}:`, deleteError);
|
||||||
// Continue even if deletion fails
|
console.log(`Delete error status: ${deleteError.status}`);
|
||||||
|
|
||||||
|
// For Gitea, branch deletion might not be supported via API
|
||||||
|
if (deleteError.status === 405 || deleteError.status === 404) {
|
||||||
|
console.log(
|
||||||
|
"Branch deletion not supported or branch doesn't exist remotely - this is expected for Gitea",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue even if deletion fails - this is not critical
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,14 +107,16 @@ export async function setupBranch(
|
|||||||
currentSHA = sourceBranchRef.data.object.sha;
|
currentSHA = sourceBranchRef.data.object.sha;
|
||||||
} catch (gitRefError: any) {
|
} catch (gitRefError: any) {
|
||||||
// If git/refs fails (like in Gitea), use the branches endpoint
|
// If git/refs fails (like in Gitea), use the branches endpoint
|
||||||
console.log(`git/refs failed, trying branches endpoint: ${gitRefError.message}`);
|
console.log(
|
||||||
|
`git/refs failed, trying branches endpoint: ${gitRefError.message}`,
|
||||||
|
);
|
||||||
const branchResponse = await octokits.rest.repos.getBranch({
|
const branchResponse = await octokits.rest.repos.getBranch({
|
||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
branch: sourceBranch,
|
branch: sourceBranch,
|
||||||
});
|
});
|
||||||
// Gitea uses commit.id instead of commit.sha
|
// GitHub and Gitea both use commit.sha
|
||||||
currentSHA = branchResponse.data.commit.sha || branchResponse.data.commit.id;
|
currentSHA = branchResponse.data.commit.sha;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Current SHA: ${currentSHA}`);
|
console.log(`Current SHA: ${currentSHA}`);
|
||||||
@@ -131,14 +133,18 @@ export async function setupBranch(
|
|||||||
console.log(`Successfully created branch via API: ${newBranch}`);
|
console.log(`Successfully created branch via API: ${newBranch}`);
|
||||||
} catch (createRefError: any) {
|
} catch (createRefError: any) {
|
||||||
// If git/refs creation fails (like in Gitea), that's expected
|
// If git/refs creation fails (like in Gitea), that's expected
|
||||||
// We'll create the branch when we push files later
|
// Log the error details but continue - the branch will be created when we push files
|
||||||
console.log(`git createRef failed (expected for Gitea): ${createRefError.message}`);
|
console.log(
|
||||||
|
`git createRef failed (expected for Gitea): ${createRefError.message}`,
|
||||||
|
);
|
||||||
|
console.log(`Error status: ${createRefError.status}`);
|
||||||
console.log(`Branch ${newBranch} will be created when files are pushed`);
|
console.log(`Branch ${newBranch} will be created when files are pushed`);
|
||||||
|
|
||||||
|
// For Gitea, we can still proceed since the MCP server will create the branch on first push
|
||||||
|
// This is actually the preferred method for Gitea
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(`Branch setup completed for: ${newBranch}`);
|
||||||
`Branch setup completed for: ${newBranch}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Set outputs for GitHub Actions
|
// Set outputs for GitHub Actions
|
||||||
core.setOutput("CLAUDE_BRANCH", newBranch);
|
core.setOutput("CLAUDE_BRANCH", newBranch);
|
||||||
|
|||||||
@@ -2,9 +2,6 @@
|
|||||||
|
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function setupGitHubToken(): Promise<string> {
|
export async function setupGitHubToken(): Promise<string> {
|
||||||
try {
|
try {
|
||||||
// Check if GitHub token was provided as override
|
// Check if GitHub token was provided as override
|
||||||
@@ -25,7 +22,9 @@ export async function setupGitHubToken(): Promise<string> {
|
|||||||
return workflowToken;
|
return workflowToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error("No GitHub token available. Please provide a github_token input or ensure GITHUB_TOKEN is available in the workflow environment.");
|
throw new Error(
|
||||||
|
"No GitHub token available. Please provide a github_token input or ensure GITHUB_TOKEN is available in the workflow environment.",
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
core.setFailed(
|
core.setFailed(
|
||||||
`Failed to setup GitHub token: ${error}.\n\nPlease provide a \`github_token\` in the \`with\` section of the action in your workflow yml file, or ensure the workflow has access to the default GITHUB_TOKEN.`,
|
`Failed to setup GitHub token: ${error}.\n\nPlease provide a \`github_token\` in the \`with\` section of the action in your workflow yml file, or ensure the workflow has access to the default GITHUB_TOKEN.`,
|
||||||
|
|||||||
@@ -84,67 +84,135 @@ export async function downloadCommentImages(
|
|||||||
let bodyHtml: string | undefined;
|
let bodyHtml: string | undefined;
|
||||||
|
|
||||||
// Get the HTML version based on comment type
|
// Get the HTML version based on comment type
|
||||||
|
// Try with full+json mediaType first (GitHub), fallback to regular API (Gitea)
|
||||||
switch (comment.type) {
|
switch (comment.type) {
|
||||||
case "issue_comment": {
|
case "issue_comment": {
|
||||||
const response = await octokits.rest.issues.getComment({
|
try {
|
||||||
owner,
|
const response = await octokits.rest.issues.getComment({
|
||||||
repo,
|
owner,
|
||||||
comment_id: parseInt(comment.id),
|
repo,
|
||||||
mediaType: {
|
comment_id: parseInt(comment.id),
|
||||||
format: "full+json",
|
mediaType: {
|
||||||
},
|
format: "full+json",
|
||||||
});
|
},
|
||||||
bodyHtml = response.data.body_html;
|
});
|
||||||
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case "review_comment": {
|
case "review_comment": {
|
||||||
const response = await octokits.rest.pulls.getReviewComment({
|
try {
|
||||||
owner,
|
const response = await octokits.rest.pulls.getReviewComment({
|
||||||
repo,
|
owner,
|
||||||
comment_id: parseInt(comment.id),
|
repo,
|
||||||
mediaType: {
|
comment_id: parseInt(comment.id),
|
||||||
format: "full+json",
|
mediaType: {
|
||||||
},
|
format: "full+json",
|
||||||
});
|
},
|
||||||
bodyHtml = response.data.body_html;
|
});
|
||||||
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case "review_body": {
|
case "review_body": {
|
||||||
const response = await octokits.rest.pulls.getReview({
|
try {
|
||||||
owner,
|
const response = await octokits.rest.pulls.getReview({
|
||||||
repo,
|
owner,
|
||||||
pull_number: parseInt(comment.pullNumber),
|
repo,
|
||||||
review_id: parseInt(comment.id),
|
pull_number: parseInt(comment.pullNumber),
|
||||||
mediaType: {
|
review_id: parseInt(comment.id),
|
||||||
format: "full+json",
|
mediaType: {
|
||||||
},
|
format: "full+json",
|
||||||
});
|
},
|
||||||
bodyHtml = response.data.body_html;
|
});
|
||||||
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case "issue_body": {
|
case "issue_body": {
|
||||||
const response = await octokits.rest.issues.get({
|
try {
|
||||||
owner,
|
const response = await octokits.rest.issues.get({
|
||||||
repo,
|
owner,
|
||||||
issue_number: parseInt(comment.issueNumber),
|
repo,
|
||||||
mediaType: {
|
issue_number: parseInt(comment.issueNumber),
|
||||||
format: "full+json",
|
mediaType: {
|
||||||
},
|
format: "full+json",
|
||||||
});
|
},
|
||||||
bodyHtml = response.data.body_html;
|
});
|
||||||
|
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;
|
break;
|
||||||
}
|
}
|
||||||
case "pr_body": {
|
case "pr_body": {
|
||||||
const response = await octokits.rest.pulls.get({
|
try {
|
||||||
owner,
|
const response = await octokits.rest.pulls.get({
|
||||||
repo,
|
owner,
|
||||||
pull_number: parseInt(comment.pullNumber),
|
repo,
|
||||||
mediaType: {
|
pull_number: parseInt(comment.pullNumber),
|
||||||
format: "full+json",
|
mediaType: {
|
||||||
},
|
format: "full+json",
|
||||||
});
|
},
|
||||||
// Type here seems to be wrong
|
});
|
||||||
bodyHtml = (response.data as any).body_html;
|
// 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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,14 @@ export async function checkHumanActor(
|
|||||||
githubContext: ParsedGitHubContext,
|
githubContext: ParsedGitHubContext,
|
||||||
) {
|
) {
|
||||||
// Check if we're in a Gitea environment
|
// Check if we're in a Gitea environment
|
||||||
const isGitea = process.env.GITHUB_API_URL && !process.env.GITHUB_API_URL.includes('api.github.com');
|
const isGitea =
|
||||||
|
process.env.GITHUB_API_URL &&
|
||||||
|
!process.env.GITHUB_API_URL.includes("api.github.com");
|
||||||
|
|
||||||
if (isGitea) {
|
if (isGitea) {
|
||||||
console.log(`Detected Gitea environment, skipping actor type validation for: ${githubContext.actor}`);
|
console.log(
|
||||||
|
`Detected Gitea environment, skipping actor type validation for: ${githubContext.actor}`,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,9 +42,14 @@ export async function checkHumanActor(
|
|||||||
|
|
||||||
console.log(`Verified human actor: ${githubContext.actor}`);
|
console.log(`Verified human actor: ${githubContext.actor}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`Failed to check actor type for ${githubContext.actor}:`, error);
|
console.warn(
|
||||||
|
`Failed to check actor type for ${githubContext.actor}:`,
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
|
||||||
// For compatibility, assume human actor if API call fails
|
// For compatibility, assume human actor if API call fails
|
||||||
console.log(`Assuming human actor due to API failure: ${githubContext.actor}`);
|
console.log(
|
||||||
|
`Assuming human actor due to API failure: ${githubContext.actor}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ export async function checkWritePermissions(
|
|||||||
const { repository, actor } = context;
|
const { repository, actor } = context;
|
||||||
|
|
||||||
// For Gitea compatibility, check if we're in a non-GitHub environment
|
// 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');
|
const isGitea =
|
||||||
|
process.env.GITHUB_API_URL &&
|
||||||
|
!process.env.GITHUB_API_URL.includes("api.github.com");
|
||||||
|
|
||||||
if (isGitea) {
|
if (isGitea) {
|
||||||
core.info(`Detected Gitea environment, assuming actor has permissions`);
|
core.info(`Detected Gitea environment, assuming actor has permissions`);
|
||||||
|
|||||||
@@ -15,7 +15,9 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean {
|
|||||||
inputs: { assigneeTrigger, triggerPhrase, directPrompt },
|
inputs: { assigneeTrigger, triggerPhrase, directPrompt },
|
||||||
} = context;
|
} = context;
|
||||||
|
|
||||||
console.log(`Checking trigger: event=${context.eventName}, action=${context.eventAction}, phrase='${triggerPhrase}', assignee='${assigneeTrigger}', direct='${directPrompt}'`);
|
console.log(
|
||||||
|
`Checking trigger: event=${context.eventName}, action=${context.eventAction}, phrase='${triggerPhrase}', assignee='${assigneeTrigger}', direct='${directPrompt}'`,
|
||||||
|
);
|
||||||
|
|
||||||
// If direct prompt is provided, always trigger
|
// If direct prompt is provided, always trigger
|
||||||
if (directPrompt) {
|
if (directPrompt) {
|
||||||
@@ -29,7 +31,9 @@ export function checkContainsTrigger(context: ParsedGitHubContext): boolean {
|
|||||||
let triggerUser = assigneeTrigger?.replace(/^@/, "") || "";
|
let triggerUser = assigneeTrigger?.replace(/^@/, "") || "";
|
||||||
const assigneeUsername = context.payload.issue.assignee?.login || "";
|
const assigneeUsername = context.payload.issue.assignee?.login || "";
|
||||||
|
|
||||||
console.log(`Checking assignee trigger: user='${triggerUser}', assignee='${assigneeUsername}'`);
|
console.log(
|
||||||
|
`Checking assignee trigger: user='${triggerUser}', assignee='${assigneeUsername}'`,
|
||||||
|
);
|
||||||
|
|
||||||
if (triggerUser && assigneeUsername === triggerUser) {
|
if (triggerUser && assigneeUsername === triggerUser) {
|
||||||
console.log(`Issue assigned to trigger user '${triggerUser}'`);
|
console.log(`Issue assigned to trigger user '${triggerUser}'`);
|
||||||
|
|||||||
Reference in New Issue
Block a user