diff --git a/AGENTS.md b/AGENTS.md index bf5188f..269f4bb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -208,7 +208,7 @@ GitHub Organization webhook 수신용 엔드포인트 #### `POST /merge-prs` -열려있는 PR을 일괄 병합합니다. 기본 병합 방식은 `merge`이며 `merge_method` 값으로 `merge | squash | rebase` 중 선택할 수 있습니다. `excludes`로 특정 PR을 제외할 수 있습니다. 승인 리뷰가 없거나 `maintenance` 라벨이 붙은 PR, Draft PR, GitHub `mergeable_state !== "clean"` PR은 스킵되며 `unknown`/`behind` 상태는 최대 1초 후 한 번 더 확인합니다. +열려있는 PR을 일괄 병합합니다. 기본 병합 방식은 `squash`이며 `merge_method` 값으로 `merge | squash | rebase` 중 선택할 수 있습니다. `excludes`로 특정 PR을 제외할 수 있습니다. 승인 리뷰가 없거나 `maintenance` 라벨이 붙은 PR, Draft PR, GitHub `mergeable_state !== "clean"` PR은 스킵되며 `unknown`/`behind` 상태는 최대 1초 후 한 번 더 확인합니다. **Request:** diff --git a/README.md b/README.md index e85e9d6..6f446fb 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ https://github.dalestudy.com ### `POST /merge-prs` -열려있는 PR을 일괄 병합합니다. 기본 병합 방식은 `merge`이며, `merge_method`로 `merge | squash | rebase` 중 선택할 수 있습니다. `excludes` 배열로 특정 PR을 제외할 수 있습니다. 최소 1개의 승인 리뷰가 없거나 Draft/`maintenance` 라벨이 붙은 PR은 스킵되며, GitHub에서 `mergeable_state === "clean"`인 PR만 병합됩니다(`behind`, `dirty`, `unknown` 등은 스킵). `unknown`/`behind` 상태는 최대 1초 후 한 차례 재확인합니다. +열려있는 PR을 일괄 병합합니다. 기본 병합 방식은 `squash`이며, `merge_method`로 `merge | squash | rebase` 중 선택할 수 있습니다. `excludes` 배열로 특정 PR을 제외할 수 있습니다. 최소 1개의 승인 리뷰가 없거나 Draft/`maintenance` 라벨이 붙은 PR은 스킵되며, GitHub에서 `mergeable_state === "clean"`인 PR만 병합됩니다(`behind`, `dirty`, `unknown` 등은 스킵). `unknown`/`behind` 상태는 최대 1초 후 한 차례 재확인합니다. **Request:** diff --git a/handlers/merge_prs.js b/handlers/merge_prs.js index bb14d63..fb2726b 100644 --- a/handlers/merge_prs.js +++ b/handlers/merge_prs.js @@ -28,7 +28,7 @@ export async function mergePrs(request, env) { } const { repoOwner, repoName, week, excludes, rawPayload } = payload.data; - const mergeMethod = (rawPayload.merge_method || "merge").toLowerCase(); + const mergeMethod = (rawPayload.merge_method || "squash").toLowerCase(); if (!ALLOWED_MERGE_METHODS.has(mergeMethod)) { return errorResponse( @@ -150,90 +150,33 @@ export async function mergePrs(request, env) { } async function mergePullRequest(owner, repo, prNumber, mergeMethod, token, sha) { - // 1. PR의 GraphQL node ID 조회 - const nodeId = await getPullRequestNodeId(owner, repo, prNumber, token); - if (!nodeId) { - return { - merged: false, - error: "Failed to get PR node ID", - }; - } - - // 2. Merge method 매핑 (REST → GraphQL) - const graphqlMergeMethod = { - merge: "MERGE", - squash: "SQUASH", - rebase: "REBASE", - }[mergeMethod] || "MERGE"; - - // 3. Auto-merge 활성화 (Merge Queue 사용) - const mutation = ` - mutation { - enablePullRequestAutoMerge(input: { - pullRequestId: "${nodeId}" - mergeMethod: ${graphqlMergeMethod} - }) { - pullRequest { - id - number - autoMergeRequest { - enabledAt - mergeMethod - } - } - } + const response = await fetch( + `https://api.github.com/repos/${owner}/${repo}/pulls/${prNumber}/merge`, + { + method: "PUT", + headers: { + ...getGitHubHeaders(token), + "Content-Type": "application/json", + }, + body: JSON.stringify({ merge_method: mergeMethod, sha }), } - `; - - const response = await fetch("https://api.github.com/graphql", { - method: "POST", - headers: { - ...getGitHubHeaders(token), - "Content-Type": "application/json", - }, - body: JSON.stringify({ query: mutation }), - }); + ); const result = await safeJson(response); - if (response.ok && result.data?.enablePullRequestAutoMerge?.pullRequest) { + if (response.ok && result.merged) { return { merged: true, - autoMergeEnabled: true, - sha: sha, + sha: result.sha, }; } return { merged: false, - error: result.errors?.[0]?.message || "Auto-merge failed", + error: result.message || "Merge failed", }; } -async function getPullRequestNodeId(owner, repo, prNumber, token) { - const query = ` - query { - repository(owner: "${owner}", name: "${repo}") { - pullRequest(number: ${prNumber}) { - id - } - } - } - `; - - const response = await fetch("https://api.github.com/graphql", { - method: "POST", - headers: { - ...getGitHubHeaders(token), - "Content-Type": "application/json", - }, - body: JSON.stringify({ query }), - }); - - const result = await safeJson(response); - return result.data?.repository?.pullRequest?.id || null; -} - async function getMergeableState(owner, repo, prNumber, token) { const prDetails = await getPullRequestDetails(owner, repo, prNumber, token);