mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-22 22:44: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.
|
||||
@@ -39,4 +43,54 @@ export const agentMode: Mode = {
|
||||
shouldCreateTrackingComment() {
|
||||
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 { 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.
|
||||
@@ -37,4 +45,82 @@ export const tagMode: Mode = {
|
||||
shouldCreateTrackingComment() {
|
||||
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
|
||||
*/
|
||||
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 { prepareEntityEvent } from "./entity-events";
|
||||
import { prepareAutomationEvent } from "./automation-events";
|
||||
|
||||
const AUTOMATION_EVENTS = ["workflow_dispatch", "schedule"];
|
||||
|
||||
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(`Preparing automation event: ${context.eventName}`);
|
||||
return prepareAutomationEvent(options);
|
||||
}
|
||||
console.log(
|
||||
`Preparing with mode: ${mode.name} for event: ${context.eventName}`,
|
||||
);
|
||||
|
||||
console.log(`Preparing entity-based event: ${context.eventName}`);
|
||||
return prepareEntityEvent(options);
|
||||
// Delegate to the mode's prepare method
|
||||
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