mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-22 22:44:13 +08:00
- Add new progress MCP server for reporting task status via API
- Support repository_dispatch events with task description and progress endpoint
- Introduce isDispatch flag to unify dispatch event handling
- Make GitHub data optional for dispatch events without issues/PRs
- Update prompt generation with dispatch-specific instructions
Enables triggering Claude via repository_dispatch with:
{
"event_type": "claude_task",
"client_payload": {
"description": "Task description",
"progress_endpoint": "https://api.example.com/progress"
}
}
182 lines
6.2 KiB
TypeScript
182 lines
6.2 KiB
TypeScript
#!/usr/bin/env bun
|
|
|
|
/**
|
|
* Setup the appropriate branch based on the event type:
|
|
* - For PRs: Checkout the PR branch
|
|
* - For Issues: Create a new branch
|
|
*/
|
|
|
|
import { $ } from "bun";
|
|
import * as core from "@actions/core";
|
|
import type { GitHubContext } from "../context";
|
|
import type { GitHubPullRequest } from "../types";
|
|
import type { Octokits } from "../api/client";
|
|
import type { FetchDataResult } from "../data/fetcher";
|
|
|
|
export type BranchInfo = {
|
|
baseBranch: string;
|
|
claudeBranch?: string;
|
|
currentBranch: string;
|
|
};
|
|
|
|
export async function setupBranch(
|
|
octokits: Octokits,
|
|
githubData: FetchDataResult | null,
|
|
context: GitHubContext,
|
|
): Promise<BranchInfo> {
|
|
const { owner, repo } = context.repository;
|
|
const entityNumber = context.entityNumber;
|
|
const { baseBranch, branchPrefix } = context.inputs;
|
|
const isPR = context.isPR;
|
|
|
|
if (isPR && githubData) {
|
|
const prData = githubData.contextData as GitHubPullRequest;
|
|
const prState = prData.state;
|
|
|
|
// Check if PR is closed or merged
|
|
if (prState === "CLOSED" || prState === "MERGED") {
|
|
console.log(
|
|
`PR #${entityNumber} is ${prState}, creating new branch from source...`,
|
|
);
|
|
// Fall through to create a new branch like we do for issues
|
|
} else {
|
|
// Handle open PR: Checkout the PR branch
|
|
console.log("This is an open PR, checking out PR branch...");
|
|
|
|
const branchName = prData.headRefName;
|
|
|
|
// Determine optimal fetch depth based on PR commit count, with a minimum of 20
|
|
const commitCount = prData.commits.totalCount;
|
|
const fetchDepth = Math.max(commitCount, 20);
|
|
|
|
console.log(
|
|
`PR #${entityNumber}: ${commitCount} commits, using fetch depth ${fetchDepth}`,
|
|
);
|
|
|
|
// Execute git commands to checkout PR branch (dynamic depth based on PR size)
|
|
await $`git fetch origin --depth=${fetchDepth} ${branchName}`;
|
|
await $`git checkout ${branchName} --`;
|
|
|
|
console.log(`Successfully checked out PR branch for PR #${entityNumber}`);
|
|
|
|
// For open PRs, we need to get the base branch of the PR
|
|
const baseBranch = prData.baseRefName;
|
|
|
|
return {
|
|
baseBranch,
|
|
currentBranch: branchName,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Determine source branch - use baseBranch if provided, otherwise fetch default
|
|
let sourceBranch: string;
|
|
|
|
if (baseBranch) {
|
|
// Use provided base branch for source
|
|
sourceBranch = baseBranch;
|
|
} else {
|
|
// No base branch provided, fetch the default branch to use as source
|
|
const repoResponse = await octokits.rest.repos.get({
|
|
owner,
|
|
repo,
|
|
});
|
|
sourceBranch = repoResponse.data.default_branch;
|
|
}
|
|
|
|
// Generate branch name for either an issue, closed/merged PR, or repository_dispatch event
|
|
let branchName: string;
|
|
|
|
if (context.eventName === "repository_dispatch") {
|
|
// For repository_dispatch events, use run ID for uniqueness since there's no entity number
|
|
const now = new Date();
|
|
const timestamp = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}-${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
|
|
branchName = `${branchPrefix}dispatch-${context.runId}-${timestamp}`;
|
|
} else {
|
|
// For issues and PRs, use the existing logic
|
|
const entityType = isPR ? "pr" : "issue";
|
|
const now = new Date();
|
|
const timestamp = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, "0")}${String(now.getDate()).padStart(2, "0")}-${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
|
|
branchName = `${branchPrefix}${entityType}-${entityNumber}-${timestamp}`;
|
|
}
|
|
|
|
// Ensure branch name is Kubernetes-compatible:
|
|
// - Lowercase only
|
|
// - Alphanumeric with hyphens
|
|
// - No underscores
|
|
// - Max 50 chars (to allow for prefixes)
|
|
const newBranch = branchName.toLowerCase().substring(0, 50);
|
|
|
|
try {
|
|
// Get the SHA of the source branch to verify it exists
|
|
const sourceBranchRef = await octokits.rest.git.getRef({
|
|
owner,
|
|
repo,
|
|
ref: `heads/${sourceBranch}`,
|
|
});
|
|
|
|
const currentSHA = sourceBranchRef.data.object.sha;
|
|
console.log(`Source branch SHA: ${currentSHA}`);
|
|
|
|
// For commit signing, defer branch creation to the file ops server
|
|
if (context.inputs.useCommitSigning) {
|
|
console.log(
|
|
`Branch name generated: ${newBranch} (will be created by file ops server on first commit)`,
|
|
);
|
|
|
|
// Ensure we're on the source branch
|
|
console.log(`Fetching and checking out source branch: ${sourceBranch}`);
|
|
await $`git fetch origin ${sourceBranch} --depth=1`;
|
|
await $`git checkout ${sourceBranch}`;
|
|
|
|
// Set outputs for GitHub Actions
|
|
core.setOutput("CLAUDE_BRANCH", newBranch);
|
|
core.setOutput("BASE_BRANCH", sourceBranch);
|
|
return {
|
|
baseBranch: sourceBranch,
|
|
claudeBranch: newBranch,
|
|
currentBranch: sourceBranch, // Stay on source branch for now
|
|
};
|
|
}
|
|
|
|
// For non-signing case, create and checkout the branch locally only
|
|
const entityType =
|
|
context.eventName === "repository_dispatch"
|
|
? "dispatch"
|
|
: isPR
|
|
? "pr"
|
|
: "issue";
|
|
const entityId =
|
|
context.eventName === "repository_dispatch"
|
|
? context.runId
|
|
: entityNumber!.toString();
|
|
console.log(
|
|
`Creating local branch ${newBranch} for ${entityType} ${entityId} from source branch: ${sourceBranch}...`,
|
|
);
|
|
|
|
// Fetch and checkout the source branch first to ensure we branch from the correct base
|
|
console.log(`Fetching and checking out source branch: ${sourceBranch}`);
|
|
await $`git fetch origin ${sourceBranch} --depth=1`;
|
|
await $`git checkout ${sourceBranch}`;
|
|
|
|
// Create and checkout the new branch from the source branch
|
|
await $`git checkout -b ${newBranch}`;
|
|
|
|
console.log(
|
|
`Successfully created and checked out local branch: ${newBranch}`,
|
|
);
|
|
|
|
// Set outputs for GitHub Actions
|
|
core.setOutput("CLAUDE_BRANCH", newBranch);
|
|
core.setOutput("BASE_BRANCH", sourceBranch);
|
|
return {
|
|
baseBranch: sourceBranch,
|
|
claudeBranch: newBranch,
|
|
currentBranch: newBranch,
|
|
};
|
|
} catch (error) {
|
|
console.error("Error in branch setup:", error);
|
|
process.exit(1);
|
|
}
|
|
}
|