mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-22 22:44:13 +08:00
refactor: implement discriminated unions for GitHub contexts
Split ParsedGitHubContext into entity-specific and automation contexts: - ParsedGitHubContext: For entity events (issues/PRs) with required entityNumber and isPR - AutomationContext: For workflow_dispatch/schedule events without entity fields - GitHubContext: Union type for all contexts This eliminates ~20 null checks throughout the codebase and provides better type safety. Entity-specific code paths are now guaranteed to have the required fields. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -125,10 +125,8 @@ export function prepareContext(
|
||||
const isPR = context.isPR;
|
||||
|
||||
// Get PR/Issue number from entityNumber
|
||||
const prNumber =
|
||||
isPR && context.entityNumber ? context.entityNumber.toString() : undefined;
|
||||
const issueNumber =
|
||||
!isPR && context.entityNumber ? context.entityNumber.toString() : undefined;
|
||||
const prNumber = isPR ? context.entityNumber.toString() : undefined;
|
||||
const issueNumber = !isPR ? context.entityNumber.toString() : undefined;
|
||||
|
||||
// Extract trigger username and comment data based on event type
|
||||
let triggerUsername: string | undefined;
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as core from "@actions/core";
|
||||
import { setupGitHubToken } from "../github/token";
|
||||
import { checkWritePermissions } from "../github/validation/permissions";
|
||||
import { createOctokit } from "../github/api/client";
|
||||
import { parseGitHubContext } from "../github/context";
|
||||
import { parseGitHubContext, isEntityContext } from "../github/context";
|
||||
import { getMode } from "../modes/registry";
|
||||
import { prepare } from "../prepare";
|
||||
|
||||
@@ -22,15 +22,17 @@ async function run() {
|
||||
// Step 2: Parse GitHub context (once for all operations)
|
||||
const context = parseGitHubContext();
|
||||
|
||||
// Step 3: Check write permissions
|
||||
const hasWritePermissions = await checkWritePermissions(
|
||||
octokit.rest,
|
||||
context,
|
||||
);
|
||||
if (!hasWritePermissions) {
|
||||
throw new Error(
|
||||
"Actor does not have write permissions to the repository",
|
||||
// Step 3: Check write permissions (only for entity contexts)
|
||||
if (isEntityContext(context)) {
|
||||
const hasWritePermissions = await checkWritePermissions(
|
||||
octokit.rest,
|
||||
context,
|
||||
);
|
||||
if (!hasWritePermissions) {
|
||||
throw new Error(
|
||||
"Actor does not have write permissions to the repository",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Get mode and check trigger conditions
|
||||
|
||||
@@ -9,6 +9,8 @@ import {
|
||||
import {
|
||||
parseGitHubContext,
|
||||
isPullRequestReviewCommentEvent,
|
||||
isEntityContext,
|
||||
type ParsedGitHubContext,
|
||||
} from "../github/context";
|
||||
import { GITHUB_SERVER_URL } from "../github/api/config";
|
||||
import { checkAndCommitOrDeleteBranch } from "../github/operations/branch-cleanup";
|
||||
@@ -23,13 +25,15 @@ async function run() {
|
||||
const triggerUsername = process.env.TRIGGER_USERNAME;
|
||||
|
||||
const context = parseGitHubContext();
|
||||
const { owner, repo } = context.repository;
|
||||
|
||||
// This script is only called for entity-based events
|
||||
if (!context.entityNumber) {
|
||||
throw new Error("update-comment-link requires an entity number");
|
||||
if (!isEntityContext(context)) {
|
||||
throw new Error("update-comment-link requires an entity context");
|
||||
}
|
||||
const entityNumber = context.entityNumber;
|
||||
|
||||
// TypeScript needs a type assertion here due to control flow limitations
|
||||
const entityContext = context as ParsedGitHubContext;
|
||||
const { owner, repo } = entityContext.repository;
|
||||
|
||||
const octokit = createOctokit(githubToken);
|
||||
|
||||
@@ -42,7 +46,7 @@ async function run() {
|
||||
try {
|
||||
// GitHub has separate ID namespaces for review comments and issue comments
|
||||
// We need to use the correct API based on the event type
|
||||
if (isPullRequestReviewCommentEvent(context)) {
|
||||
if (isPullRequestReviewCommentEvent(entityContext)) {
|
||||
// For PR review comments, use the pulls API
|
||||
console.log(`Fetching PR review comment ${commentId}`);
|
||||
const { data: prComment } = await octokit.rest.pulls.getReviewComment({
|
||||
@@ -71,16 +75,16 @@ async function run() {
|
||||
// If all attempts fail, try to determine more information about the comment
|
||||
console.error("Failed to fetch comment. Debug info:");
|
||||
console.error(`Comment ID: ${commentId}`);
|
||||
console.error(`Event name: ${context.eventName}`);
|
||||
console.error(`Entity number: ${context.entityNumber}`);
|
||||
console.error(`Repository: ${context.repository.full_name}`);
|
||||
console.error(`Event name: ${entityContext.eventName}`);
|
||||
console.error(`Entity number: ${entityContext.entityNumber}`);
|
||||
console.error(`Repository: ${entityContext.repository.full_name}`);
|
||||
|
||||
// Try to get the PR info to understand the comment structure
|
||||
try {
|
||||
const { data: pr } = await octokit.rest.pulls.get({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: entityNumber,
|
||||
pull_number: entityContext.entityNumber,
|
||||
});
|
||||
console.log(`PR state: ${pr.state}`);
|
||||
console.log(`PR comments count: ${pr.comments}`);
|
||||
@@ -132,12 +136,12 @@ async function run() {
|
||||
comparison.total_commits > 0 ||
|
||||
(comparison.files && comparison.files.length > 0)
|
||||
) {
|
||||
const entityType = context.isPR ? "PR" : "Issue";
|
||||
const entityType = entityContext.isPR ? "PR" : "Issue";
|
||||
const prTitle = encodeURIComponent(
|
||||
`${entityType} #${context.entityNumber}: Changes from Claude`,
|
||||
`${entityType} #${entityContext.entityNumber}: Changes from Claude`,
|
||||
);
|
||||
const prBody = encodeURIComponent(
|
||||
`This PR addresses ${entityType.toLowerCase()} #${context.entityNumber}\n\nGenerated with [Claude Code](https://claude.ai/code)`,
|
||||
`This PR addresses ${entityType.toLowerCase()} #${entityContext.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})`;
|
||||
|
||||
@@ -37,9 +37,9 @@ export type ScheduleEvent = {
|
||||
import type { ModeName } from "../modes/types";
|
||||
import { DEFAULT_MODE, isValidMode } from "../modes/registry";
|
||||
|
||||
export type ParsedGitHubContext = {
|
||||
// Common fields shared by all context types
|
||||
type BaseContext = {
|
||||
runId: string;
|
||||
eventName: string;
|
||||
eventAction?: string;
|
||||
repository: {
|
||||
owner: string;
|
||||
@@ -47,16 +47,6 @@ export type ParsedGitHubContext = {
|
||||
full_name: string;
|
||||
};
|
||||
actor: string;
|
||||
payload:
|
||||
| IssuesEvent
|
||||
| IssueCommentEvent
|
||||
| PullRequestEvent
|
||||
| PullRequestReviewEvent
|
||||
| PullRequestReviewCommentEvent
|
||||
| WorkflowDispatchEvent
|
||||
| ScheduleEvent;
|
||||
entityNumber?: number;
|
||||
isPR?: boolean;
|
||||
inputs: {
|
||||
mode: ModeName;
|
||||
triggerPhrase: string;
|
||||
@@ -75,7 +65,35 @@ export type ParsedGitHubContext = {
|
||||
};
|
||||
};
|
||||
|
||||
export function parseGitHubContext(): ParsedGitHubContext {
|
||||
// Context for entity-based events (issues, PRs, comments)
|
||||
export type ParsedGitHubContext = BaseContext & {
|
||||
eventName:
|
||||
| "issues"
|
||||
| "issue_comment"
|
||||
| "pull_request"
|
||||
| "pull_request_review"
|
||||
| "pull_request_review_comment";
|
||||
payload:
|
||||
| IssuesEvent
|
||||
| IssueCommentEvent
|
||||
| PullRequestEvent
|
||||
| PullRequestReviewEvent
|
||||
| PullRequestReviewCommentEvent;
|
||||
entityNumber: number;
|
||||
isPR: boolean;
|
||||
};
|
||||
|
||||
// Context for automation events (workflow_dispatch, schedule)
|
||||
export type AutomationContext = BaseContext & {
|
||||
eventName: "workflow_dispatch" | "schedule";
|
||||
payload: WorkflowDispatchEvent | ScheduleEvent;
|
||||
// No entityNumber or isPR
|
||||
};
|
||||
|
||||
// Union type for all contexts
|
||||
export type GitHubContext = ParsedGitHubContext | AutomationContext;
|
||||
|
||||
export function parseGitHubContext(): GitHubContext {
|
||||
const context = github.context;
|
||||
|
||||
const modeInput = process.env.MODE ?? DEFAULT_MODE;
|
||||
@@ -85,7 +103,6 @@ export function parseGitHubContext(): ParsedGitHubContext {
|
||||
|
||||
const commonFields = {
|
||||
runId: process.env.GITHUB_RUN_ID!,
|
||||
eventName: context.eventName,
|
||||
eventAction: context.payload.action,
|
||||
repository: {
|
||||
owner: context.repo.owner,
|
||||
@@ -117,60 +134,65 @@ export function parseGitHubContext(): ParsedGitHubContext {
|
||||
case "issues": {
|
||||
return {
|
||||
...commonFields,
|
||||
eventName: "issues" as const,
|
||||
payload: context.payload as IssuesEvent,
|
||||
entityNumber: (context.payload as IssuesEvent).issue.number,
|
||||
isPR: false,
|
||||
};
|
||||
} as ParsedGitHubContext;
|
||||
}
|
||||
case "issue_comment": {
|
||||
return {
|
||||
...commonFields,
|
||||
eventName: "issue_comment" as const,
|
||||
payload: context.payload as IssueCommentEvent,
|
||||
entityNumber: (context.payload as IssueCommentEvent).issue.number,
|
||||
isPR: Boolean(
|
||||
(context.payload as IssueCommentEvent).issue.pull_request,
|
||||
),
|
||||
};
|
||||
} as ParsedGitHubContext;
|
||||
}
|
||||
case "pull_request": {
|
||||
return {
|
||||
...commonFields,
|
||||
eventName: "pull_request" as const,
|
||||
payload: context.payload as PullRequestEvent,
|
||||
entityNumber: (context.payload as PullRequestEvent).pull_request.number,
|
||||
isPR: true,
|
||||
};
|
||||
} as ParsedGitHubContext;
|
||||
}
|
||||
case "pull_request_review": {
|
||||
return {
|
||||
...commonFields,
|
||||
eventName: "pull_request_review" as const,
|
||||
payload: context.payload as PullRequestReviewEvent,
|
||||
entityNumber: (context.payload as PullRequestReviewEvent).pull_request
|
||||
.number,
|
||||
isPR: true,
|
||||
};
|
||||
} as ParsedGitHubContext;
|
||||
}
|
||||
case "pull_request_review_comment": {
|
||||
return {
|
||||
...commonFields,
|
||||
eventName: "pull_request_review_comment" as const,
|
||||
payload: context.payload as PullRequestReviewCommentEvent,
|
||||
entityNumber: (context.payload as PullRequestReviewCommentEvent)
|
||||
.pull_request.number,
|
||||
isPR: true,
|
||||
};
|
||||
} as ParsedGitHubContext;
|
||||
}
|
||||
case "workflow_dispatch": {
|
||||
return {
|
||||
...commonFields,
|
||||
eventName: "workflow_dispatch" as const,
|
||||
payload: context.payload as unknown as WorkflowDispatchEvent,
|
||||
// No entityNumber or isPR for workflow_dispatch
|
||||
};
|
||||
} as AutomationContext;
|
||||
}
|
||||
case "schedule": {
|
||||
return {
|
||||
...commonFields,
|
||||
eventName: "schedule" as const,
|
||||
payload: context.payload as unknown as ScheduleEvent,
|
||||
// No entityNumber or isPR for schedule
|
||||
};
|
||||
} as AutomationContext;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported event type: ${context.eventName}`);
|
||||
@@ -205,37 +227,54 @@ export function parseAdditionalPermissions(s: string): Map<string, string> {
|
||||
}
|
||||
|
||||
export function isIssuesEvent(
|
||||
context: ParsedGitHubContext,
|
||||
context: GitHubContext,
|
||||
): context is ParsedGitHubContext & { payload: IssuesEvent } {
|
||||
return context.eventName === "issues";
|
||||
}
|
||||
|
||||
export function isIssueCommentEvent(
|
||||
context: ParsedGitHubContext,
|
||||
context: GitHubContext,
|
||||
): context is ParsedGitHubContext & { payload: IssueCommentEvent } {
|
||||
return context.eventName === "issue_comment";
|
||||
}
|
||||
|
||||
export function isPullRequestEvent(
|
||||
context: ParsedGitHubContext,
|
||||
context: GitHubContext,
|
||||
): context is ParsedGitHubContext & { payload: PullRequestEvent } {
|
||||
return context.eventName === "pull_request";
|
||||
}
|
||||
|
||||
export function isPullRequestReviewEvent(
|
||||
context: ParsedGitHubContext,
|
||||
context: GitHubContext,
|
||||
): context is ParsedGitHubContext & { payload: PullRequestReviewEvent } {
|
||||
return context.eventName === "pull_request_review";
|
||||
}
|
||||
|
||||
export function isPullRequestReviewCommentEvent(
|
||||
context: ParsedGitHubContext,
|
||||
context: GitHubContext,
|
||||
): context is ParsedGitHubContext & { payload: PullRequestReviewCommentEvent } {
|
||||
return context.eventName === "pull_request_review_comment";
|
||||
}
|
||||
|
||||
export function isIssuesAssignedEvent(
|
||||
context: ParsedGitHubContext,
|
||||
context: GitHubContext,
|
||||
): context is ParsedGitHubContext & { payload: IssuesAssignedEvent } {
|
||||
return isIssuesEvent(context) && context.eventAction === "assigned";
|
||||
}
|
||||
|
||||
// Type guard to check if context is an entity context (has entityNumber and isPR)
|
||||
export function isEntityContext(
|
||||
context: GitHubContext,
|
||||
): context is ParsedGitHubContext {
|
||||
return "entityNumber" in context && "isPR" in context;
|
||||
}
|
||||
|
||||
// Type guard to check if context is an automation context
|
||||
export function isAutomationContext(
|
||||
context: GitHubContext,
|
||||
): context is AutomationContext {
|
||||
return (
|
||||
context.eventName === "workflow_dispatch" ||
|
||||
context.eventName === "schedule"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,12 +22,6 @@ export async function createInitialComment(
|
||||
) {
|
||||
const { owner, repo } = context.repository;
|
||||
|
||||
// This function is only called for entity-based events
|
||||
if (!context.entityNumber) {
|
||||
throw new Error("createInitialComment requires an entity number");
|
||||
}
|
||||
const entityNumber = context.entityNumber;
|
||||
|
||||
const jobRunLink = createJobRunLink(owner, repo, context.runId);
|
||||
const initialBody = createCommentBody(jobRunLink);
|
||||
|
||||
@@ -42,7 +36,7 @@ export async function createInitialComment(
|
||||
const comments = await octokit.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: entityNumber,
|
||||
issue_number: context.entityNumber,
|
||||
});
|
||||
const existingComment = comments.data.find((comment) => {
|
||||
const idMatch = comment.user?.id === CLAUDE_APP_BOT_ID;
|
||||
@@ -65,7 +59,7 @@ export async function createInitialComment(
|
||||
response = await octokit.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: entityNumber,
|
||||
issue_number: context.entityNumber,
|
||||
body: initialBody,
|
||||
});
|
||||
}
|
||||
@@ -74,7 +68,7 @@ export async function createInitialComment(
|
||||
response = await octokit.rest.pulls.createReplyForReviewComment({
|
||||
owner,
|
||||
repo,
|
||||
pull_number: entityNumber,
|
||||
pull_number: context.entityNumber,
|
||||
comment_id: context.payload.comment.id,
|
||||
body: initialBody,
|
||||
});
|
||||
@@ -83,7 +77,7 @@ export async function createInitialComment(
|
||||
response = await octokit.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: entityNumber,
|
||||
issue_number: context.entityNumber,
|
||||
body: initialBody,
|
||||
});
|
||||
}
|
||||
@@ -101,7 +95,7 @@ export async function createInitialComment(
|
||||
const response = await octokit.rest.issues.createComment({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: entityNumber,
|
||||
issue_number: context.entityNumber,
|
||||
body: initialBody,
|
||||
});
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import type { Mode, ModeName } from "./types";
|
||||
import { tagMode } from "./tag";
|
||||
import { agentMode } from "./agent";
|
||||
import type { ParsedGitHubContext } from "../github/context";
|
||||
import type { GitHubContext } from "../github/context";
|
||||
|
||||
export const DEFAULT_MODE = "tag" as const;
|
||||
export const VALID_MODES = ["tag", "agent"] as const;
|
||||
@@ -34,7 +34,7 @@ const modes = {
|
||||
* @returns The requested mode
|
||||
* @throws Error if the mode is not found or cannot handle the event
|
||||
*/
|
||||
export function getMode(name: ModeName, context: ParsedGitHubContext): Mode {
|
||||
export function getMode(name: ModeName, context: GitHubContext): Mode {
|
||||
const mode = modes[name];
|
||||
if (!mode) {
|
||||
const validModes = VALID_MODES.join("', '");
|
||||
|
||||
@@ -8,6 +8,10 @@ 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 {
|
||||
isEntityContext,
|
||||
type ParsedGitHubContext,
|
||||
} from "../../github/context";
|
||||
|
||||
/**
|
||||
* Tag mode implementation.
|
||||
@@ -21,6 +25,10 @@ export const tagMode: Mode = {
|
||||
description: "Traditional implementation mode triggered by @claude mentions",
|
||||
|
||||
shouldTrigger(context) {
|
||||
// Tag mode only handles entity events
|
||||
if (!isEntityContext(context)) {
|
||||
return false;
|
||||
}
|
||||
return checkContainsTrigger(context);
|
||||
},
|
||||
|
||||
@@ -52,34 +60,36 @@ export const tagMode: Mode = {
|
||||
githubToken,
|
||||
}: ModeOptions): Promise<ModeResult> {
|
||||
// Tag mode handles entity-based events (issues, PRs, comments)
|
||||
// We know context is ParsedGitHubContext for tag mode
|
||||
const entityContext = context as ParsedGitHubContext;
|
||||
|
||||
// Check if actor is human
|
||||
await checkHumanActor(octokit.rest, context);
|
||||
await checkHumanActor(octokit.rest, entityContext);
|
||||
|
||||
// Create initial tracking comment
|
||||
const commentData = await createInitialComment(octokit.rest, context);
|
||||
const commentData = await createInitialComment(octokit.rest, entityContext);
|
||||
const commentId = commentData.id;
|
||||
|
||||
// Fetch GitHub data - entity events always have entityNumber and isPR
|
||||
if (!context.entityNumber || context.isPR === undefined) {
|
||||
if (!entityContext.entityNumber || entityContext.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,
|
||||
repository: `${entityContext.repository.owner}/${entityContext.repository.repo}`,
|
||||
prNumber: entityContext.entityNumber.toString(),
|
||||
isPR: entityContext.isPR,
|
||||
triggerUsername: entityContext.actor,
|
||||
});
|
||||
|
||||
// Setup branch
|
||||
const branchInfo = await setupBranch(octokit, githubData, context);
|
||||
const branchInfo = await setupBranch(octokit, githubData, entityContext);
|
||||
|
||||
// Configure git authentication if not using commit signing
|
||||
if (!context.inputs.useCommitSigning) {
|
||||
try {
|
||||
await configureGitAuth(githubToken, context, commentData.user);
|
||||
await configureGitAuth(githubToken, entityContext, commentData.user);
|
||||
} catch (error) {
|
||||
console.error("Failed to configure git authentication:", error);
|
||||
throw error;
|
||||
@@ -87,26 +97,26 @@ export const tagMode: Mode = {
|
||||
}
|
||||
|
||||
// Create prompt file
|
||||
const modeContext = this.prepareContext(context, {
|
||||
const modeContext = this.prepareContext(entityContext, {
|
||||
commentId,
|
||||
baseBranch: branchInfo.baseBranch,
|
||||
claudeBranch: branchInfo.claudeBranch,
|
||||
});
|
||||
|
||||
await createPrompt(tagMode, modeContext, githubData, context);
|
||||
await createPrompt(tagMode, modeContext, githubData, entityContext);
|
||||
|
||||
// Get MCP configuration
|
||||
const additionalMcpConfig = process.env.MCP_CONFIG || "";
|
||||
const mcpConfig = await prepareMcpConfig({
|
||||
githubToken,
|
||||
owner: context.repository.owner,
|
||||
repo: context.repository.repo,
|
||||
owner: entityContext.repository.owner,
|
||||
repo: entityContext.repository.repo,
|
||||
branch: branchInfo.claudeBranch || branchInfo.currentBranch,
|
||||
baseBranch: branchInfo.baseBranch,
|
||||
additionalMcpConfig,
|
||||
claudeCommentId: commentId.toString(),
|
||||
allowedTools: context.inputs.allowedTools,
|
||||
context,
|
||||
allowedTools: entityContext.inputs.allowedTools,
|
||||
context: entityContext,
|
||||
});
|
||||
|
||||
core.setOutput("mcp_config", mcpConfig);
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import type { ParsedGitHubContext } from "../github/context";
|
||||
import type { GitHubContext } from "../github/context";
|
||||
|
||||
export type ModeName = "tag" | "agent";
|
||||
|
||||
export type ModeContext = {
|
||||
mode: ModeName;
|
||||
githubContext: ParsedGitHubContext;
|
||||
githubContext: GitHubContext;
|
||||
commentId?: number;
|
||||
baseBranch?: string;
|
||||
claudeBranch?: string;
|
||||
@@ -32,12 +32,12 @@ export type Mode = {
|
||||
/**
|
||||
* Determines if this mode should trigger based on the GitHub context
|
||||
*/
|
||||
shouldTrigger(context: ParsedGitHubContext): boolean;
|
||||
shouldTrigger(context: GitHubContext): boolean;
|
||||
|
||||
/**
|
||||
* Prepares the mode context with any additional data needed for prompt generation
|
||||
*/
|
||||
prepareContext(context: ParsedGitHubContext, data?: ModeData): ModeContext;
|
||||
prepareContext(context: GitHubContext, data?: ModeData): ModeContext;
|
||||
|
||||
/**
|
||||
* Returns the list of tools that should be allowed for this mode
|
||||
@@ -64,7 +64,7 @@ export type Mode = {
|
||||
|
||||
// Define types for mode prepare method to avoid circular dependencies
|
||||
export type ModeOptions = {
|
||||
context: ParsedGitHubContext;
|
||||
context: GitHubContext;
|
||||
octokit: any; // We'll use any to avoid circular dependency with Octokits
|
||||
githubToken: string;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ParsedGitHubContext } from "../github/context";
|
||||
import type { GitHubContext } from "../github/context";
|
||||
import type { Octokits } from "../github/api/client";
|
||||
import type { Mode } from "../modes/types";
|
||||
|
||||
@@ -13,7 +13,7 @@ export type PrepareResult = {
|
||||
};
|
||||
|
||||
export type PrepareOptions = {
|
||||
context: ParsedGitHubContext;
|
||||
context: GitHubContext;
|
||||
octokit: Octokits;
|
||||
mode: Mode;
|
||||
githubToken: string;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import type { ParsedGitHubContext } from "../src/github/context";
|
||||
import type {
|
||||
ParsedGitHubContext,
|
||||
AutomationContext,
|
||||
} from "../src/github/context";
|
||||
import type {
|
||||
IssuesEvent,
|
||||
IssueCommentEvent,
|
||||
@@ -38,7 +41,7 @@ export const createMockContext = (
|
||||
): ParsedGitHubContext => {
|
||||
const baseContext: ParsedGitHubContext = {
|
||||
runId: "1234567890",
|
||||
eventName: "",
|
||||
eventName: "issue_comment", // Default to a valid entity event
|
||||
eventAction: "",
|
||||
repository: defaultRepository,
|
||||
actor: "test-actor",
|
||||
@@ -55,6 +58,22 @@ export const createMockContext = (
|
||||
return { ...baseContext, ...overrides };
|
||||
};
|
||||
|
||||
export const createMockAutomationContext = (
|
||||
overrides: Partial<AutomationContext> = {},
|
||||
): AutomationContext => {
|
||||
const baseContext: AutomationContext = {
|
||||
runId: "1234567890",
|
||||
eventName: "workflow_dispatch",
|
||||
eventAction: undefined,
|
||||
repository: defaultRepository,
|
||||
actor: "test-actor",
|
||||
payload: {} as any,
|
||||
inputs: defaultInputs,
|
||||
};
|
||||
|
||||
return { ...baseContext, ...overrides };
|
||||
};
|
||||
|
||||
export const mockIssueOpenedContext: ParsedGitHubContext = {
|
||||
runId: "1234567890",
|
||||
eventName: "issues",
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { describe, test, expect, beforeEach } from "bun:test";
|
||||
import { agentMode } from "../../src/modes/agent";
|
||||
import type { ParsedGitHubContext } from "../../src/github/context";
|
||||
import { createMockContext } from "../mockContext";
|
||||
import type { GitHubContext } from "../../src/github/context";
|
||||
import { createMockContext, createMockAutomationContext } from "../mockContext";
|
||||
|
||||
describe("Agent Mode", () => {
|
||||
let mockContext: ParsedGitHubContext;
|
||||
let mockContext: GitHubContext;
|
||||
|
||||
beforeEach(() => {
|
||||
mockContext = createMockContext({
|
||||
mockContext = createMockAutomationContext({
|
||||
eventName: "workflow_dispatch",
|
||||
isPR: false,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,30 +33,26 @@ describe("Agent Mode", () => {
|
||||
|
||||
test("agent mode only triggers for workflow_dispatch and schedule events", () => {
|
||||
// Should trigger for automation events
|
||||
const workflowDispatchContext = createMockContext({
|
||||
const workflowDispatchContext = createMockAutomationContext({
|
||||
eventName: "workflow_dispatch",
|
||||
isPR: false,
|
||||
});
|
||||
expect(agentMode.shouldTrigger(workflowDispatchContext)).toBe(true);
|
||||
|
||||
const scheduleContext = createMockContext({
|
||||
const scheduleContext = createMockAutomationContext({
|
||||
eventName: "schedule",
|
||||
isPR: false,
|
||||
});
|
||||
expect(agentMode.shouldTrigger(scheduleContext)).toBe(true);
|
||||
|
||||
// Should NOT trigger for other events
|
||||
const otherEvents = [
|
||||
"push",
|
||||
"repository_dispatch",
|
||||
// Should NOT trigger for entity events
|
||||
const entityEvents = [
|
||||
"issue_comment",
|
||||
"pull_request",
|
||||
"pull_request_review",
|
||||
"issues",
|
||||
];
|
||||
] as const;
|
||||
|
||||
otherEvents.forEach((eventName) => {
|
||||
const context = createMockContext({ eventName, isPR: false });
|
||||
entityEvents.forEach((eventName) => {
|
||||
const context = createMockContext({ eventName });
|
||||
expect(agentMode.shouldTrigger(context)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,18 +3,18 @@ import { getMode, isValidMode } from "../../src/modes/registry";
|
||||
import type { ModeName } from "../../src/modes/types";
|
||||
import { tagMode } from "../../src/modes/tag";
|
||||
import { agentMode } from "../../src/modes/agent";
|
||||
import { createMockContext } from "../mockContext";
|
||||
import { createMockContext, createMockAutomationContext } from "../mockContext";
|
||||
|
||||
describe("Mode Registry", () => {
|
||||
const mockContext = createMockContext({
|
||||
eventName: "issue_comment",
|
||||
});
|
||||
|
||||
const mockWorkflowDispatchContext = createMockContext({
|
||||
const mockWorkflowDispatchContext = createMockAutomationContext({
|
||||
eventName: "workflow_dispatch",
|
||||
});
|
||||
|
||||
const mockScheduleContext = createMockContext({
|
||||
const mockScheduleContext = createMockAutomationContext({
|
||||
eventName: "schedule",
|
||||
});
|
||||
|
||||
|
||||
@@ -299,15 +299,4 @@ describe("parseEnvVarsWithContext", () => {
|
||||
expect(result.allowedTools).toBe("Tool1,Tool2");
|
||||
});
|
||||
});
|
||||
|
||||
test("should throw error for unsupported event type", () => {
|
||||
process.env = BASE_ENV;
|
||||
const unsupportedContext = createMockContext({
|
||||
eventName: "unsupported_event",
|
||||
eventAction: "whatever",
|
||||
});
|
||||
expect(() => prepareContext(unsupportedContext, "12345")).toThrow(
|
||||
"Unsupported event type: unsupported_event",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -474,17 +474,6 @@ describe("checkContainsTrigger", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("non-matching events", () => {
|
||||
it("should return false for non-matching event type", () => {
|
||||
const context = createMockContext({
|
||||
eventName: "push",
|
||||
eventAction: "created",
|
||||
payload: {} as any,
|
||||
});
|
||||
expect(checkContainsTrigger(context)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("escapeRegExp", () => {
|
||||
|
||||
Reference in New Issue
Block a user