mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-23 06:54:13 +08:00
refactor: simplify prepare logic with mode-specific implementations
This commit is contained in:
@@ -1,4 +1,8 @@
|
|||||||
import type { Mode } from "../types";
|
import * as core from "@actions/core";
|
||||||
|
import { mkdir, writeFile } from "fs/promises";
|
||||||
|
import type { Mode, ModeOptions, ModeResult } from "../types";
|
||||||
|
import { prepareMcpConfig } from "../../mcp/install-mcp-server";
|
||||||
|
import { exportToolEnvironmentVariables } from "../../tools/export";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Agent mode implementation.
|
* Agent mode implementation.
|
||||||
@@ -39,4 +43,54 @@ export const agentMode: Mode = {
|
|||||||
shouldCreateTrackingComment() {
|
shouldCreateTrackingComment() {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async prepare({ context, githubToken }: ModeOptions): Promise<ModeResult> {
|
||||||
|
// Agent mode is designed for automation events (workflow_dispatch, schedule)
|
||||||
|
// and potentially other events where we want full automation without tracking
|
||||||
|
|
||||||
|
// Create prompt directory
|
||||||
|
await mkdir(`${process.env.RUNNER_TEMP}/claude-prompts`, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Write the prompt - either override_prompt, direct_prompt, or a minimal default
|
||||||
|
const promptContent =
|
||||||
|
context.inputs.overridePrompt ||
|
||||||
|
context.inputs.directPrompt ||
|
||||||
|
`Repository: ${context.repository.owner}/${context.repository.repo}`;
|
||||||
|
|
||||||
|
await writeFile(
|
||||||
|
`${process.env.RUNNER_TEMP}/claude-prompts/claude-prompt.txt`,
|
||||||
|
promptContent,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Export tool environment variables
|
||||||
|
exportToolEnvironmentVariables(agentMode, context);
|
||||||
|
|
||||||
|
// Get MCP configuration
|
||||||
|
const additionalMcpConfig = process.env.MCP_CONFIG || "";
|
||||||
|
const mcpConfig = await prepareMcpConfig({
|
||||||
|
githubToken,
|
||||||
|
owner: context.repository.owner,
|
||||||
|
repo: context.repository.repo,
|
||||||
|
branch: "", // No specific branch for agent mode
|
||||||
|
baseBranch: "", // No base branch needed
|
||||||
|
additionalMcpConfig,
|
||||||
|
claudeCommentId: "",
|
||||||
|
allowedTools: context.inputs.allowedTools,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
|
||||||
|
core.setOutput("mcp_config", mcpConfig);
|
||||||
|
|
||||||
|
return {
|
||||||
|
commentId: undefined,
|
||||||
|
branchInfo: {
|
||||||
|
baseBranch: "",
|
||||||
|
currentBranch: "",
|
||||||
|
claudeBranch: undefined,
|
||||||
|
},
|
||||||
|
mcpConfig,
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import type { Mode } from "../types";
|
import * as core from "@actions/core";
|
||||||
|
import type { Mode, ModeOptions, ModeResult } from "../types";
|
||||||
import { checkContainsTrigger } from "../../github/validation/trigger";
|
import { checkContainsTrigger } from "../../github/validation/trigger";
|
||||||
|
import { checkHumanActor } from "../../github/validation/actor";
|
||||||
|
import { createInitialComment } from "../../github/operations/comments/create-initial";
|
||||||
|
import { setupBranch } from "../../github/operations/branch";
|
||||||
|
import { configureGitAuth } from "../../github/operations/git-config";
|
||||||
|
import { prepareMcpConfig } from "../../mcp/install-mcp-server";
|
||||||
|
import { fetchGitHubData } from "../../github/data/fetcher";
|
||||||
|
import { createPrompt } from "../../create-prompt";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tag mode implementation.
|
* Tag mode implementation.
|
||||||
@@ -37,4 +45,82 @@ export const tagMode: Mode = {
|
|||||||
shouldCreateTrackingComment() {
|
shouldCreateTrackingComment() {
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async prepare({
|
||||||
|
context,
|
||||||
|
octokit,
|
||||||
|
githubToken,
|
||||||
|
}: ModeOptions): Promise<ModeResult> {
|
||||||
|
// Tag mode handles entity-based events (issues, PRs, comments)
|
||||||
|
|
||||||
|
// Check if actor is human
|
||||||
|
await checkHumanActor(octokit.rest, context);
|
||||||
|
|
||||||
|
// Create initial tracking comment
|
||||||
|
let commentId: number | undefined;
|
||||||
|
let commentData:
|
||||||
|
| Awaited<ReturnType<typeof createInitialComment>>
|
||||||
|
| undefined;
|
||||||
|
if (this.shouldCreateTrackingComment()) {
|
||||||
|
commentData = await createInitialComment(octokit.rest, context);
|
||||||
|
commentId = commentData.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch GitHub data - entity events always have entityNumber and isPR
|
||||||
|
if (!context.entityNumber || context.isPR === undefined) {
|
||||||
|
throw new Error("Entity events must have entityNumber and isPR defined");
|
||||||
|
}
|
||||||
|
|
||||||
|
const githubData = await fetchGitHubData({
|
||||||
|
octokits: octokit,
|
||||||
|
repository: `${context.repository.owner}/${context.repository.repo}`,
|
||||||
|
prNumber: context.entityNumber.toString(),
|
||||||
|
isPR: context.isPR,
|
||||||
|
triggerUsername: context.actor,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Setup branch
|
||||||
|
const branchInfo = await setupBranch(octokit, githubData, context);
|
||||||
|
|
||||||
|
// Configure git authentication if not using commit signing
|
||||||
|
if (!context.inputs.useCommitSigning) {
|
||||||
|
try {
|
||||||
|
await configureGitAuth(githubToken, context, commentData?.user || null);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to configure git authentication:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create prompt file
|
||||||
|
const modeContext = this.prepareContext(context, {
|
||||||
|
commentId,
|
||||||
|
baseBranch: branchInfo.baseBranch,
|
||||||
|
claudeBranch: branchInfo.claudeBranch,
|
||||||
|
});
|
||||||
|
|
||||||
|
await createPrompt(tagMode, modeContext, githubData, context);
|
||||||
|
|
||||||
|
// Get MCP configuration
|
||||||
|
const additionalMcpConfig = process.env.MCP_CONFIG || "";
|
||||||
|
const mcpConfig = await prepareMcpConfig({
|
||||||
|
githubToken,
|
||||||
|
owner: context.repository.owner,
|
||||||
|
repo: context.repository.repo,
|
||||||
|
branch: branchInfo.claudeBranch || branchInfo.currentBranch,
|
||||||
|
baseBranch: branchInfo.baseBranch,
|
||||||
|
additionalMcpConfig,
|
||||||
|
claudeCommentId: commentId?.toString() || "",
|
||||||
|
allowedTools: context.inputs.allowedTools,
|
||||||
|
context,
|
||||||
|
});
|
||||||
|
|
||||||
|
core.setOutput("mcp_config", mcpConfig);
|
||||||
|
|
||||||
|
return {
|
||||||
|
commentId,
|
||||||
|
branchInfo,
|
||||||
|
mcpConfig,
|
||||||
|
};
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -53,4 +53,28 @@ export type Mode = {
|
|||||||
* Determines if this mode should create a tracking comment
|
* Determines if this mode should create a tracking comment
|
||||||
*/
|
*/
|
||||||
shouldCreateTrackingComment(): boolean;
|
shouldCreateTrackingComment(): boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepares the GitHub environment for this mode.
|
||||||
|
* Each mode decides how to handle different event types.
|
||||||
|
* @returns PrepareResult with commentId, branchInfo, and mcpConfig
|
||||||
|
*/
|
||||||
|
prepare(options: ModeOptions): Promise<ModeResult>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Define types for mode prepare method to avoid circular dependencies
|
||||||
|
export type ModeOptions = {
|
||||||
|
context: ParsedGitHubContext;
|
||||||
|
octokit: any; // We'll use any to avoid circular dependency with Octokits
|
||||||
|
githubToken: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ModeResult = {
|
||||||
|
commentId?: number;
|
||||||
|
branchInfo: {
|
||||||
|
baseBranch: string;
|
||||||
|
claudeBranch?: string;
|
||||||
|
currentBranch: string;
|
||||||
|
};
|
||||||
|
mcpConfig: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
/**
|
|
||||||
* Prepare logic for automation events (workflow_dispatch, schedule)
|
|
||||||
* These events don't have associated GitHub entities and require minimal setup
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as core from "@actions/core";
|
|
||||||
import { prepareMcpConfig } from "../mcp/install-mcp-server";
|
|
||||||
import { createPrompt } from "../create-prompt";
|
|
||||||
import { getDefaultBranch } from "../github/operations/default-branch";
|
|
||||||
import type { PrepareOptions, PrepareResult } from "./types";
|
|
||||||
|
|
||||||
export async function prepareAutomationEvent({
|
|
||||||
context,
|
|
||||||
octokit,
|
|
||||||
mode,
|
|
||||||
githubToken,
|
|
||||||
}: PrepareOptions): Promise<PrepareResult> {
|
|
||||||
// For automation events, we skip:
|
|
||||||
// - Human actor check (it's automation)
|
|
||||||
// - Tracking comment (no issue/PR to comment on)
|
|
||||||
// - GitHub data fetching (no entity to fetch)
|
|
||||||
// - Branch setup (use default branch or current branch)
|
|
||||||
|
|
||||||
// Get the default branch or use the one specified in inputs
|
|
||||||
const baseBranch =
|
|
||||||
context.inputs.baseBranch ||
|
|
||||||
(await getDefaultBranch(
|
|
||||||
octokit.rest,
|
|
||||||
context.repository.owner,
|
|
||||||
context.repository.repo,
|
|
||||||
));
|
|
||||||
|
|
||||||
// For automation events, we stay on the current branch (typically main/master)
|
|
||||||
const branchInfo = {
|
|
||||||
baseBranch,
|
|
||||||
currentBranch: baseBranch,
|
|
||||||
claudeBranch: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create prompt file with minimal context
|
|
||||||
const modeContext = mode.prepareContext(context, {
|
|
||||||
baseBranch: branchInfo.baseBranch,
|
|
||||||
claudeBranch: branchInfo.claudeBranch,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Pass null for githubData since automation events don't have associated entities
|
|
||||||
await createPrompt(mode, modeContext, null, context);
|
|
||||||
|
|
||||||
// Get MCP configuration
|
|
||||||
const additionalMcpConfig = process.env.MCP_CONFIG || "";
|
|
||||||
const mcpConfig = await prepareMcpConfig({
|
|
||||||
githubToken,
|
|
||||||
owner: context.repository.owner,
|
|
||||||
repo: context.repository.repo,
|
|
||||||
branch: branchInfo.currentBranch,
|
|
||||||
baseBranch: branchInfo.baseBranch,
|
|
||||||
additionalMcpConfig,
|
|
||||||
claudeCommentId: "",
|
|
||||||
allowedTools: context.inputs.allowedTools,
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
|
|
||||||
core.setOutput("mcp_config", mcpConfig);
|
|
||||||
|
|
||||||
return {
|
|
||||||
commentId: undefined,
|
|
||||||
branchInfo,
|
|
||||||
mcpConfig,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
/**
|
|
||||||
* Prepare logic for entity-based events (issues, PRs, comments)
|
|
||||||
* These events have associated GitHub entities that need to be fetched and managed
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as core from "@actions/core";
|
|
||||||
import { checkHumanActor } from "../github/validation/actor";
|
|
||||||
import { createInitialComment } from "../github/operations/comments/create-initial";
|
|
||||||
import { setupBranch } from "../github/operations/branch";
|
|
||||||
import { configureGitAuth } from "../github/operations/git-config";
|
|
||||||
import { prepareMcpConfig } from "../mcp/install-mcp-server";
|
|
||||||
import { fetchGitHubData } from "../github/data/fetcher";
|
|
||||||
import { createPrompt } from "../create-prompt";
|
|
||||||
import type { PrepareOptions, PrepareResult } from "./types";
|
|
||||||
|
|
||||||
export async function prepareEntityEvent({
|
|
||||||
context,
|
|
||||||
octokit,
|
|
||||||
mode,
|
|
||||||
githubToken,
|
|
||||||
}: PrepareOptions): Promise<PrepareResult> {
|
|
||||||
// Check if actor is human
|
|
||||||
await checkHumanActor(octokit.rest, context);
|
|
||||||
|
|
||||||
// Create initial tracking comment (mode-aware)
|
|
||||||
let commentId: number | undefined;
|
|
||||||
let commentData: Awaited<ReturnType<typeof createInitialComment>> | undefined;
|
|
||||||
if (mode.shouldCreateTrackingComment()) {
|
|
||||||
commentData = await createInitialComment(octokit.rest, context);
|
|
||||||
commentId = commentData.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch GitHub data - entity events always have entityNumber and isPR
|
|
||||||
if (!context.entityNumber || context.isPR === undefined) {
|
|
||||||
throw new Error("Entity events must have entityNumber and isPR defined");
|
|
||||||
}
|
|
||||||
|
|
||||||
const githubData = await fetchGitHubData({
|
|
||||||
octokits: octokit,
|
|
||||||
repository: `${context.repository.owner}/${context.repository.repo}`,
|
|
||||||
prNumber: context.entityNumber.toString(),
|
|
||||||
isPR: context.isPR,
|
|
||||||
triggerUsername: context.actor,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Setup branch
|
|
||||||
const branchInfo = await setupBranch(octokit, githubData, context);
|
|
||||||
|
|
||||||
// Configure git authentication if not using commit signing
|
|
||||||
if (!context.inputs.useCommitSigning) {
|
|
||||||
try {
|
|
||||||
await configureGitAuth(githubToken, context, commentData?.user || null);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to configure git authentication:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create prompt file
|
|
||||||
const modeContext = mode.prepareContext(context, {
|
|
||||||
commentId,
|
|
||||||
baseBranch: branchInfo.baseBranch,
|
|
||||||
claudeBranch: branchInfo.claudeBranch,
|
|
||||||
});
|
|
||||||
|
|
||||||
await createPrompt(mode, modeContext, githubData, context);
|
|
||||||
|
|
||||||
// Get MCP configuration
|
|
||||||
const additionalMcpConfig = process.env.MCP_CONFIG || "";
|
|
||||||
const mcpConfig = await prepareMcpConfig({
|
|
||||||
githubToken,
|
|
||||||
owner: context.repository.owner,
|
|
||||||
repo: context.repository.repo,
|
|
||||||
branch: branchInfo.claudeBranch || branchInfo.currentBranch,
|
|
||||||
baseBranch: branchInfo.baseBranch,
|
|
||||||
additionalMcpConfig,
|
|
||||||
claudeCommentId: commentId?.toString() || "",
|
|
||||||
allowedTools: context.inputs.allowedTools,
|
|
||||||
context,
|
|
||||||
});
|
|
||||||
|
|
||||||
core.setOutput("mcp_config", mcpConfig);
|
|
||||||
|
|
||||||
return {
|
|
||||||
commentId,
|
|
||||||
branchInfo,
|
|
||||||
mcpConfig,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,21 +1,20 @@
|
|||||||
/**
|
/**
|
||||||
* Main prepare module that routes to appropriate prepare logic based on event type
|
* Main prepare module that delegates to the mode's prepare method
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { PrepareOptions, PrepareResult } from "./types";
|
import type { PrepareOptions, PrepareResult } from "./types";
|
||||||
import { prepareEntityEvent } from "./entity-events";
|
|
||||||
import { prepareAutomationEvent } from "./automation-events";
|
|
||||||
|
|
||||||
const AUTOMATION_EVENTS = ["workflow_dispatch", "schedule"];
|
|
||||||
|
|
||||||
export async function prepare(options: PrepareOptions): Promise<PrepareResult> {
|
export async function prepare(options: PrepareOptions): Promise<PrepareResult> {
|
||||||
const { context } = options;
|
const { mode, context, octokit, githubToken } = options;
|
||||||
|
|
||||||
if (AUTOMATION_EVENTS.includes(context.eventName)) {
|
console.log(
|
||||||
console.log(`Preparing automation event: ${context.eventName}`);
|
`Preparing with mode: ${mode.name} for event: ${context.eventName}`,
|
||||||
return prepareAutomationEvent(options);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Preparing entity-based event: ${context.eventName}`);
|
// Delegate to the mode's prepare method
|
||||||
return prepareEntityEvent(options);
|
return mode.prepare({
|
||||||
|
context,
|
||||||
|
octokit,
|
||||||
|
githubToken,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
49
src/tools/export.ts
Normal file
49
src/tools/export.ts
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
* Handles exporting tool-related environment variables
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as core from "@actions/core";
|
||||||
|
import type { Mode } from "../modes/types";
|
||||||
|
import type { ParsedGitHubContext } from "../github/context";
|
||||||
|
import {
|
||||||
|
buildAllowedToolsString,
|
||||||
|
buildDisallowedToolsString,
|
||||||
|
} from "../create-prompt/index";
|
||||||
|
|
||||||
|
export function exportToolEnvironmentVariables(
|
||||||
|
mode: Mode,
|
||||||
|
context: ParsedGitHubContext,
|
||||||
|
): void {
|
||||||
|
const hasActionsReadPermission =
|
||||||
|
context.inputs.additionalPermissions.get("actions") === "read" &&
|
||||||
|
context.isPR;
|
||||||
|
|
||||||
|
const modeAllowedTools = mode.getAllowedTools();
|
||||||
|
const modeDisallowedTools = mode.getDisallowedTools();
|
||||||
|
|
||||||
|
// Combine with existing allowed tools
|
||||||
|
const combinedAllowedTools = [
|
||||||
|
...context.inputs.allowedTools,
|
||||||
|
...modeAllowedTools,
|
||||||
|
];
|
||||||
|
const combinedDisallowedTools = [
|
||||||
|
...context.inputs.disallowedTools,
|
||||||
|
...modeDisallowedTools,
|
||||||
|
];
|
||||||
|
|
||||||
|
const allAllowedTools = buildAllowedToolsString(
|
||||||
|
combinedAllowedTools,
|
||||||
|
hasActionsReadPermission,
|
||||||
|
context.inputs.useCommitSigning,
|
||||||
|
);
|
||||||
|
const allDisallowedTools = buildDisallowedToolsString(
|
||||||
|
combinedDisallowedTools,
|
||||||
|
combinedAllowedTools,
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(`Allowed tools: ${allAllowedTools}`);
|
||||||
|
console.log(`Disallowed tools: ${allDisallowedTools}`);
|
||||||
|
|
||||||
|
core.exportVariable("ALLOWED_TOOLS", allAllowedTools);
|
||||||
|
core.exportVariable("DISALLOWED_TOOLS", allDisallowedTools);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user