mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-01-23 15:04:13 +08:00
Compare commits
1 Commits
v0.0.50
...
testenvvar
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6820ee5d50 |
2
.github/workflows/claude-review.yml
vendored
2
.github/workflows/claude-review.yml
vendored
@@ -30,4 +30,4 @@ jobs:
|
|||||||
|
|
||||||
Be constructive and specific in your feedback. Give inline comments where applicable.
|
Be constructive and specific in your feedback. Give inline comments where applicable.
|
||||||
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
|
||||||
allowed_tools: "mcp__github__create_pending_pull_request_review,mcp__github__add_comment_to_pending_review,mcp__github__submit_pending_pull_request_review,mcp__github__get_pull_request_diff"
|
allowed_tools: "mcp__github__create_pending_pull_request_review,mcp__github__add_pull_request_review_comment_to_pending_review,mcp__github__submit_pending_pull_request_review,mcp__github__get_pull_request_diff"
|
||||||
|
|||||||
128
CLAUDE.md
128
CLAUDE.md
@@ -1,11 +1,10 @@
|
|||||||
# CLAUDE.md
|
# CLAUDE.md
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
This file provides guidance to Claude Code when working with code in this repository.
|
||||||
|
|
||||||
## Development Tools
|
## Development Tools
|
||||||
|
|
||||||
- Runtime: Bun 1.2.11
|
- Runtime: Bun 1.2.11
|
||||||
- TypeScript with strict configuration
|
|
||||||
|
|
||||||
## Common Development Tasks
|
## Common Development Tasks
|
||||||
|
|
||||||
@@ -18,119 +17,42 @@ bun test
|
|||||||
# Formatting
|
# Formatting
|
||||||
bun run format # Format code with prettier
|
bun run format # Format code with prettier
|
||||||
bun run format:check # Check code formatting
|
bun run format:check # Check code formatting
|
||||||
|
|
||||||
# Type checking
|
|
||||||
bun run typecheck # Run TypeScript type checker
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Architecture Overview
|
## Architecture Overview
|
||||||
|
|
||||||
This is a GitHub Action that enables Claude to interact with GitHub PRs and issues. The action operates in two main phases:
|
This is a GitHub Action that enables Claude to interact with GitHub PRs and issues. The action:
|
||||||
|
|
||||||
### Phase 1: Preparation (`src/entrypoints/prepare.ts`)
|
1. **Trigger Detection**: Uses `check-trigger.ts` to determine if Claude should respond based on comment/issue content
|
||||||
|
2. **Context Gathering**: Fetches GitHub data (PRs, issues, comments) via `github-data-fetcher.ts` and formats it using `github-data-formatter.ts`
|
||||||
|
3. **AI Integration**: Supports multiple Claude providers (Anthropic API, AWS Bedrock, Google Vertex AI)
|
||||||
|
4. **Prompt Creation**: Generates context-rich prompts using `create-prompt.ts`
|
||||||
|
5. **MCP Server Integration**: Installs and configures GitHub MCP server for extended functionality
|
||||||
|
|
||||||
1. **Authentication Setup**: Establishes GitHub token via OIDC or GitHub App
|
### Key Components
|
||||||
2. **Permission Validation**: Verifies actor has write permissions
|
|
||||||
3. **Trigger Detection**: Uses mode-specific logic to determine if Claude should respond
|
|
||||||
4. **Context Creation**: Prepares GitHub context and initial tracking comment
|
|
||||||
|
|
||||||
### Phase 2: Execution (`base-action/`)
|
- **Trigger System**: Responds to `/claude` comments or issue assignments
|
||||||
|
- **Authentication**: OIDC-based token exchange for secure GitHub interactions
|
||||||
The `base-action/` directory contains the core Claude Code execution logic, which serves a dual purpose:
|
- **Cloud Integration**: Supports direct Anthropic API, AWS Bedrock, and Google Vertex AI
|
||||||
|
- **GitHub Operations**: Creates branches, posts comments, and manages PRs/issues
|
||||||
- **Standalone Action**: Published separately as `@anthropic-ai/claude-code-base-action` for direct use
|
|
||||||
- **Inner Logic**: Used internally by this GitHub Action after preparation phase completes
|
|
||||||
|
|
||||||
Execution steps:
|
|
||||||
|
|
||||||
1. **MCP Server Setup**: Installs and configures GitHub MCP server for tool access
|
|
||||||
2. **Prompt Generation**: Creates context-rich prompts from GitHub data
|
|
||||||
3. **Claude Integration**: Executes via multiple providers (Anthropic API, AWS Bedrock, Google Vertex AI)
|
|
||||||
4. **Result Processing**: Updates comments and creates branches/PRs as needed
|
|
||||||
|
|
||||||
### Key Architectural Components
|
|
||||||
|
|
||||||
#### Mode System (`src/modes/`)
|
|
||||||
|
|
||||||
- **Tag Mode** (`tag/`): Responds to `@claude` mentions and issue assignments
|
|
||||||
- **Agent Mode** (`agent/`): Automated execution without trigger checking
|
|
||||||
- Extensible registry pattern in `modes/registry.ts`
|
|
||||||
|
|
||||||
#### GitHub Integration (`src/github/`)
|
|
||||||
|
|
||||||
- **Context Parsing** (`context.ts`): Unified GitHub event handling
|
|
||||||
- **Data Fetching** (`data/fetcher.ts`): Retrieves PR/issue data via GraphQL/REST
|
|
||||||
- **Data Formatting** (`data/formatter.ts`): Converts GitHub data to Claude-readable format
|
|
||||||
- **Branch Operations** (`operations/branch.ts`): Handles branch creation and cleanup
|
|
||||||
- **Comment Management** (`operations/comments/`): Creates and updates tracking comments
|
|
||||||
|
|
||||||
#### MCP Server Integration (`src/mcp/`)
|
|
||||||
|
|
||||||
- **GitHub Actions Server** (`github-actions-server.ts`): Workflow and CI access
|
|
||||||
- **GitHub Comment Server** (`github-comment-server.ts`): Comment operations
|
|
||||||
- **GitHub File Operations** (`github-file-ops-server.ts`): File system access
|
|
||||||
- Auto-installation and configuration in `install-mcp-server.ts`
|
|
||||||
|
|
||||||
#### Authentication & Security (`src/github/`)
|
|
||||||
|
|
||||||
- **Token Management** (`token.ts`): OIDC token exchange and GitHub App authentication
|
|
||||||
- **Permission Validation** (`validation/permissions.ts`): Write access verification
|
|
||||||
- **Actor Validation** (`validation/actor.ts`): Human vs bot detection
|
|
||||||
|
|
||||||
### Project Structure
|
### Project Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
├── entrypoints/ # Action entry points
|
├── check-trigger.ts # Determines if Claude should respond
|
||||||
│ ├── prepare.ts # Main preparation logic
|
├── create-prompt.ts # Generates contextual prompts
|
||||||
│ ├── update-comment-link.ts # Post-execution comment updates
|
├── github-data-fetcher.ts # Retrieves GitHub data
|
||||||
│ └── format-turns.ts # Claude conversation formatting
|
├── github-data-formatter.ts # Formats GitHub data for prompts
|
||||||
├── github/ # GitHub integration layer
|
├── install-mcp-server.ts # Sets up GitHub MCP server
|
||||||
│ ├── api/ # REST/GraphQL clients
|
├── update-comment-with-link.ts # Updates comments with job links
|
||||||
│ ├── data/ # Data fetching and formatting
|
└── types/
|
||||||
│ ├── operations/ # Branch, comment, git operations
|
└── github.ts # TypeScript types for GitHub data
|
||||||
│ ├── validation/ # Permission and trigger validation
|
|
||||||
│ └── utils/ # Image downloading, sanitization
|
|
||||||
├── modes/ # Execution modes
|
|
||||||
│ ├── tag/ # @claude mention mode
|
|
||||||
│ ├── agent/ # Automation mode
|
|
||||||
│ └── registry.ts # Mode selection logic
|
|
||||||
├── mcp/ # MCP server implementations
|
|
||||||
├── prepare/ # Preparation orchestration
|
|
||||||
└── utils/ # Shared utilities
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Important Implementation Notes
|
## Important Notes
|
||||||
|
|
||||||
### Authentication Flow
|
- Actions are triggered by `@claude` comments or issue assignment unless a different trigger_phrase is specified
|
||||||
|
- The action creates branches for issues and pushes to PR branches directly
|
||||||
- Uses GitHub OIDC token exchange for secure authentication
|
- All actions create OIDC tokens for secure authentication
|
||||||
- Supports custom GitHub Apps via `APP_ID` and `APP_PRIVATE_KEY`
|
- Progress is tracked through dynamic comment updates with checkboxes
|
||||||
- Falls back to official Claude GitHub App if no custom app provided
|
|
||||||
|
|
||||||
### MCP Server Architecture
|
|
||||||
|
|
||||||
- Each MCP server has specific GitHub API access patterns
|
|
||||||
- Servers are auto-installed in `~/.claude/mcp/github-{type}-server/`
|
|
||||||
- Configuration merged with user-provided MCP config via `mcp_config` input
|
|
||||||
|
|
||||||
### Mode System Design
|
|
||||||
|
|
||||||
- Modes implement `Mode` interface with `shouldTrigger()` and `prepare()` methods
|
|
||||||
- Registry validates mode compatibility with GitHub event types
|
|
||||||
- Agent mode bypasses all trigger checking for automation scenarios
|
|
||||||
|
|
||||||
### Comment Threading
|
|
||||||
|
|
||||||
- Single tracking comment updated throughout execution
|
|
||||||
- Progress indicated via dynamic checkboxes
|
|
||||||
- Links to job runs and created branches/PRs
|
|
||||||
- Sticky comment option for consolidated PR comments
|
|
||||||
|
|
||||||
## Code Conventions
|
|
||||||
|
|
||||||
- Use Bun-specific TypeScript configuration with `moduleResolution: "bundler"`
|
|
||||||
- Strict TypeScript with `noUnusedLocals` and `noUnusedParameters` enabled
|
|
||||||
- Prefer explicit error handling with detailed error messages
|
|
||||||
- Use discriminated unions for GitHub context types
|
|
||||||
- Implement retry logic for GitHub API operations via `utils/retry.ts`
|
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ runs:
|
|||||||
echo "Base-action dependencies installed"
|
echo "Base-action dependencies installed"
|
||||||
cd -
|
cd -
|
||||||
# Install Claude Code globally
|
# Install Claude Code globally
|
||||||
bun install -g @anthropic-ai/claude-code@1.0.64
|
bun install -g @anthropic-ai/claude-code@1.0.63
|
||||||
|
|
||||||
- name: Setup Network Restrictions
|
- name: Setup Network Restrictions
|
||||||
if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != ''
|
if: steps.prepare.outputs.contains_trigger == 'true' && inputs.experimental_allowed_domains != ''
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ runs:
|
|||||||
|
|
||||||
- name: Install Claude Code
|
- name: Install Claude Code
|
||||||
shell: bash
|
shell: bash
|
||||||
run: bun install -g @anthropic-ai/claude-code@1.0.64
|
run: bun install -g @anthropic-ai/claude-code@1.0.63
|
||||||
|
|
||||||
- name: Run Claude Code Action
|
- name: Run Claude Code Action
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -35,4 +35,4 @@ jobs:
|
|||||||
|
|
||||||
Provide constructive feedback with specific suggestions for improvement.
|
Provide constructive feedback with specific suggestions for improvement.
|
||||||
Use inline comments to highlight specific areas of concern.
|
Use inline comments to highlight specific areas of concern.
|
||||||
# allowed_tools: "mcp__github__create_pending_pull_request_review,mcp__github__add_comment_to_pending_review,mcp__github__submit_pending_pull_request_review,mcp__github__get_pull_request_diff"
|
# allowed_tools: "mcp__github__create_pending_pull_request_review,mcp__github__add_pull_request_review_comment_to_pending_review,mcp__github__submit_pending_pull_request_review,mcp__github__get_pull_request_diff"
|
||||||
|
|||||||
@@ -587,28 +587,23 @@ ${formattedBody}
|
|||||||
${formattedComments || "No comments"}
|
${formattedComments || "No comments"}
|
||||||
</comments>
|
</comments>
|
||||||
|
|
||||||
${
|
<review_comments>
|
||||||
eventData.isPR
|
${eventData.isPR ? formattedReviewComments || "No review comments" : ""}
|
||||||
? `<review_comments>
|
</review_comments>
|
||||||
${formattedReviewComments || "No review comments"}
|
|
||||||
</review_comments>`
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
${
|
<changed_files>
|
||||||
eventData.isPR
|
${eventData.isPR ? formattedChangedFiles || "No files changed" : ""}
|
||||||
? `<changed_files>
|
</changed_files>${imagesInfo}
|
||||||
${formattedChangedFiles || "No files changed"}
|
|
||||||
</changed_files>`
|
|
||||||
: ""
|
|
||||||
}${imagesInfo}
|
|
||||||
|
|
||||||
<event_type>${eventType}</event_type>
|
<event_type>${eventType}</event_type>
|
||||||
<is_pr>${eventData.isPR ? "true" : "false"}</is_pr>
|
<is_pr>${eventData.isPR ? "true" : "false"}</is_pr>
|
||||||
<trigger_context>${triggerContext}</trigger_context>
|
<trigger_context>${triggerContext}</trigger_context>
|
||||||
<repository>${context.repository}</repository>
|
<repository>${context.repository}</repository>
|
||||||
${eventData.isPR && eventData.prNumber ? `<pr_number>${eventData.prNumber}</pr_number>` : ""}
|
${
|
||||||
${!eventData.isPR && eventData.issueNumber ? `<issue_number>${eventData.issueNumber}</issue_number>` : ""}
|
eventData.isPR
|
||||||
|
? `<pr_number>${eventData.prNumber}</pr_number>`
|
||||||
|
: `<issue_number>${eventData.issueNumber ?? ""}</issue_number>`
|
||||||
|
}
|
||||||
<claude_comment_id>${context.claudeCommentId}</claude_comment_id>
|
<claude_comment_id>${context.claudeCommentId}</claude_comment_id>
|
||||||
<trigger_username>${context.triggerUsername ?? "Unknown"}</trigger_username>
|
<trigger_username>${context.triggerUsername ?? "Unknown"}</trigger_username>
|
||||||
<trigger_display_name>${githubData.triggerDisplayName ?? context.triggerUsername ?? "Unknown"}</trigger_display_name>
|
<trigger_display_name>${githubData.triggerDisplayName ?? context.triggerUsername ?? "Unknown"}</trigger_display_name>
|
||||||
|
|||||||
@@ -393,7 +393,7 @@ export function formatGroupedContent(groupedContent: GroupedContent[]): string {
|
|||||||
markdown += "---\n\n";
|
markdown += "---\n\n";
|
||||||
} else if (itemType === "final_result") {
|
} else if (itemType === "final_result") {
|
||||||
const data = item.data || {};
|
const data = item.data || {};
|
||||||
const cost = (data as any).total_cost_usd || (data as any).cost_usd || 0;
|
const cost = (data as any).cost_usd || 0;
|
||||||
const duration = (data as any).duration_ms || 0;
|
const duration = (data as any).duration_ms || 0;
|
||||||
const resultText = (data as any).result || "";
|
const resultText = (data as any).result || "";
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ export const PR_QUERY = `
|
|||||||
login
|
login
|
||||||
}
|
}
|
||||||
createdAt
|
createdAt
|
||||||
isMinimized
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reviews(first: 100) {
|
reviews(first: 100) {
|
||||||
@@ -70,7 +69,6 @@ export const PR_QUERY = `
|
|||||||
login
|
login
|
||||||
}
|
}
|
||||||
createdAt
|
createdAt
|
||||||
isMinimized
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -100,7 +98,6 @@ export const ISSUE_QUERY = `
|
|||||||
login
|
login
|
||||||
}
|
}
|
||||||
createdAt
|
createdAt
|
||||||
isMinimized
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ export async function fetchGitHubData({
|
|||||||
|
|
||||||
// Prepare all comments for image processing
|
// Prepare all comments for image processing
|
||||||
const issueComments: CommentWithImages[] = comments
|
const issueComments: CommentWithImages[] = comments
|
||||||
.filter((c) => c.body && !c.isMinimized)
|
.filter((c) => c.body)
|
||||||
.map((c) => ({
|
.map((c) => ({
|
||||||
type: "issue_comment" as const,
|
type: "issue_comment" as const,
|
||||||
id: c.databaseId,
|
id: c.databaseId,
|
||||||
@@ -154,7 +154,7 @@ export async function fetchGitHubData({
|
|||||||
const reviewComments: CommentWithImages[] =
|
const reviewComments: CommentWithImages[] =
|
||||||
reviewData?.nodes
|
reviewData?.nodes
|
||||||
?.flatMap((r) => r.comments?.nodes ?? [])
|
?.flatMap((r) => r.comments?.nodes ?? [])
|
||||||
.filter((c) => c.body && !c.isMinimized)
|
.filter((c) => c.body)
|
||||||
.map((c) => ({
|
.map((c) => ({
|
||||||
type: "review_comment" as const,
|
type: "review_comment" as const,
|
||||||
id: c.databaseId,
|
id: c.databaseId,
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ export function formatComments(
|
|||||||
imageUrlMap?: Map<string, string>,
|
imageUrlMap?: Map<string, string>,
|
||||||
): string {
|
): string {
|
||||||
return comments
|
return comments
|
||||||
.filter((comment) => !comment.isMinimized)
|
|
||||||
.map((comment) => {
|
.map((comment) => {
|
||||||
let body = comment.body;
|
let body = comment.body;
|
||||||
|
|
||||||
@@ -97,7 +96,6 @@ export function formatReviewComments(
|
|||||||
review.comments.nodes.length > 0
|
review.comments.nodes.length > 0
|
||||||
) {
|
) {
|
||||||
const comments = review.comments.nodes
|
const comments = review.comments.nodes
|
||||||
.filter((comment) => !comment.isMinimized)
|
|
||||||
.map((comment) => {
|
.map((comment) => {
|
||||||
let body = comment.body;
|
let body = comment.body;
|
||||||
|
|
||||||
@@ -112,10 +110,8 @@ export function formatReviewComments(
|
|||||||
return ` [Comment on ${comment.path}:${comment.line || "?"}]: ${body}`;
|
return ` [Comment on ${comment.path}:${comment.line || "?"}]: ${body}`;
|
||||||
})
|
})
|
||||||
.join("\n");
|
.join("\n");
|
||||||
if (comments) {
|
|
||||||
reviewOutput += `\n${comments}`;
|
reviewOutput += `\n${comments}`;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return reviewOutput;
|
return reviewOutput;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ export type GitHubComment = {
|
|||||||
body: string;
|
body: string;
|
||||||
author: GitHubAuthor;
|
author: GitHubAuthor;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
isMinimized?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GitHubReviewComment = GitHubComment & {
|
export type GitHubReviewComment = GitHubComment & {
|
||||||
|
|||||||
@@ -252,63 +252,6 @@ describe("formatComments", () => {
|
|||||||
`[user1 at 2023-01-01T00:00:00Z]: Image: `,
|
`[user1 at 2023-01-01T00:00:00Z]: Image: `,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("filters out minimized comments", () => {
|
|
||||||
const comments: GitHubComment[] = [
|
|
||||||
{
|
|
||||||
id: "1",
|
|
||||||
databaseId: "100001",
|
|
||||||
body: "Normal comment",
|
|
||||||
author: { login: "user1" },
|
|
||||||
createdAt: "2023-01-01T00:00:00Z",
|
|
||||||
isMinimized: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "2",
|
|
||||||
databaseId: "100002",
|
|
||||||
body: "Minimized comment",
|
|
||||||
author: { login: "user2" },
|
|
||||||
createdAt: "2023-01-02T00:00:00Z",
|
|
||||||
isMinimized: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "3",
|
|
||||||
databaseId: "100003",
|
|
||||||
body: "Another normal comment",
|
|
||||||
author: { login: "user3" },
|
|
||||||
createdAt: "2023-01-03T00:00:00Z",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const result = formatComments(comments);
|
|
||||||
expect(result).toBe(
|
|
||||||
`[user1 at 2023-01-01T00:00:00Z]: Normal comment\n\n[user3 at 2023-01-03T00:00:00Z]: Another normal comment`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns empty string when all comments are minimized", () => {
|
|
||||||
const comments: GitHubComment[] = [
|
|
||||||
{
|
|
||||||
id: "1",
|
|
||||||
databaseId: "100001",
|
|
||||||
body: "Minimized comment 1",
|
|
||||||
author: { login: "user1" },
|
|
||||||
createdAt: "2023-01-01T00:00:00Z",
|
|
||||||
isMinimized: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "2",
|
|
||||||
databaseId: "100002",
|
|
||||||
body: "Minimized comment 2",
|
|
||||||
author: { login: "user2" },
|
|
||||||
createdAt: "2023-01-02T00:00:00Z",
|
|
||||||
isMinimized: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const result = formatComments(comments);
|
|
||||||
expect(result).toBe("");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("formatReviewComments", () => {
|
describe("formatReviewComments", () => {
|
||||||
@@ -574,159 +517,6 @@ describe("formatReviewComments", () => {
|
|||||||
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\nReview body\n [Comment on src/index.ts:42]: Image: `,
|
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\nReview body\n [Comment on src/index.ts:42]: Image: `,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("filters out minimized review comments", () => {
|
|
||||||
const reviewData = {
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
id: "review1",
|
|
||||||
databaseId: "300001",
|
|
||||||
author: { login: "reviewer1" },
|
|
||||||
body: "Review with mixed comments",
|
|
||||||
state: "APPROVED",
|
|
||||||
submittedAt: "2023-01-01T00:00:00Z",
|
|
||||||
comments: {
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
id: "comment1",
|
|
||||||
databaseId: "200001",
|
|
||||||
body: "Normal review comment",
|
|
||||||
author: { login: "reviewer1" },
|
|
||||||
createdAt: "2023-01-01T00:00:00Z",
|
|
||||||
path: "src/index.ts",
|
|
||||||
line: 42,
|
|
||||||
isMinimized: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "comment2",
|
|
||||||
databaseId: "200002",
|
|
||||||
body: "Minimized review comment",
|
|
||||||
author: { login: "reviewer1" },
|
|
||||||
createdAt: "2023-01-01T00:00:00Z",
|
|
||||||
path: "src/utils.ts",
|
|
||||||
line: 15,
|
|
||||||
isMinimized: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "comment3",
|
|
||||||
databaseId: "200003",
|
|
||||||
body: "Another normal comment",
|
|
||||||
author: { login: "reviewer1" },
|
|
||||||
createdAt: "2023-01-01T00:00:00Z",
|
|
||||||
path: "src/main.ts",
|
|
||||||
line: 10,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = formatReviewComments(reviewData);
|
|
||||||
expect(result).toBe(
|
|
||||||
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\nReview with mixed comments\n [Comment on src/index.ts:42]: Normal review comment\n [Comment on src/main.ts:10]: Another normal comment`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("returns review with only body when all review comments are minimized", () => {
|
|
||||||
const reviewData = {
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
id: "review1",
|
|
||||||
databaseId: "300001",
|
|
||||||
author: { login: "reviewer1" },
|
|
||||||
body: "Review body only",
|
|
||||||
state: "APPROVED",
|
|
||||||
submittedAt: "2023-01-01T00:00:00Z",
|
|
||||||
comments: {
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
id: "comment1",
|
|
||||||
databaseId: "200001",
|
|
||||||
body: "Minimized comment 1",
|
|
||||||
author: { login: "reviewer1" },
|
|
||||||
createdAt: "2023-01-01T00:00:00Z",
|
|
||||||
path: "src/index.ts",
|
|
||||||
line: 42,
|
|
||||||
isMinimized: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "comment2",
|
|
||||||
databaseId: "200002",
|
|
||||||
body: "Minimized comment 2",
|
|
||||||
author: { login: "reviewer1" },
|
|
||||||
createdAt: "2023-01-01T00:00:00Z",
|
|
||||||
path: "src/utils.ts",
|
|
||||||
line: 15,
|
|
||||||
isMinimized: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = formatReviewComments(reviewData);
|
|
||||||
expect(result).toBe(
|
|
||||||
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\nReview body only`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("handles multiple reviews with mixed minimized comments", () => {
|
|
||||||
const reviewData = {
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
id: "review1",
|
|
||||||
databaseId: "300001",
|
|
||||||
author: { login: "reviewer1" },
|
|
||||||
body: "First review",
|
|
||||||
state: "APPROVED",
|
|
||||||
submittedAt: "2023-01-01T00:00:00Z",
|
|
||||||
comments: {
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
id: "comment1",
|
|
||||||
databaseId: "200001",
|
|
||||||
body: "Good comment",
|
|
||||||
author: { login: "reviewer1" },
|
|
||||||
createdAt: "2023-01-01T00:00:00Z",
|
|
||||||
path: "src/index.ts",
|
|
||||||
line: 42,
|
|
||||||
isMinimized: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "review2",
|
|
||||||
databaseId: "300002",
|
|
||||||
author: { login: "reviewer2" },
|
|
||||||
body: "Second review",
|
|
||||||
state: "COMMENTED",
|
|
||||||
submittedAt: "2023-01-02T00:00:00Z",
|
|
||||||
comments: {
|
|
||||||
nodes: [
|
|
||||||
{
|
|
||||||
id: "comment2",
|
|
||||||
databaseId: "200002",
|
|
||||||
body: "Spam comment",
|
|
||||||
author: { login: "reviewer2" },
|
|
||||||
createdAt: "2023-01-02T00:00:00Z",
|
|
||||||
path: "src/utils.ts",
|
|
||||||
line: 15,
|
|
||||||
isMinimized: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
const result = formatReviewComments(reviewData);
|
|
||||||
expect(result).toBe(
|
|
||||||
`[Review by reviewer1 at 2023-01-01T00:00:00Z]: APPROVED\nFirst review\n [Comment on src/index.ts:42]: Good comment\n\n[Review by reviewer2 at 2023-01-02T00:00:00Z]: COMMENTED\nSecond review`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("formatChangedFiles", () => {
|
describe("formatChangedFiles", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user