diff --git a/actions/setup/js/create_pull_request.cjs b/actions/setup/js/create_pull_request.cjs index 1479004658e..50d4a08c1fb 100644 --- a/actions/setup/js/create_pull_request.cjs +++ b/actions/setup/js/create_pull_request.cjs @@ -2089,10 +2089,11 @@ ${patchPreview}`; // Return success with PR details return { success: true, - pull_request_number: pullRequest.number, - pull_request_url: pullRequest.html_url, + number: pullRequest.number, + url: pullRequest.html_url, + managedBody: body, branch_name: branchName, - temporary_id: temporaryId, + temporaryId: temporaryId, repo: itemRepo, }; } catch (prError) { diff --git a/actions/setup/js/create_pull_request.test.cjs b/actions/setup/js/create_pull_request.test.cjs index e3235f1666a..6e285b33d9d 100644 --- a/actions/setup/js/create_pull_request.test.cjs +++ b/actions/setup/js/create_pull_request.test.cjs @@ -3048,7 +3048,7 @@ describe("create_pull_request - rate-limit retry", () => { const result = await resultPromise; expect(result.success).toBe(true); - expect(result.pull_request_number).toBe(42); + expect(result.number).toBe(42); // 1 initial (rate-limited) + 1 retry (succeeds) = 2 calls total expect(global.github.rest.pulls.create).toHaveBeenCalledTimes(2); expect(global.core.warning).toHaveBeenCalledWith(expect.stringContaining("create pull request")); diff --git a/actions/setup/js/safe_output_handler_manager.cjs b/actions/setup/js/safe_output_handler_manager.cjs index 484606b52b9..50a30223c04 100644 --- a/actions/setup/js/safe_output_handler_manager.cjs +++ b/actions/setup/js/safe_output_handler_manager.cjs @@ -1032,6 +1032,8 @@ function getContentToCheck(messageType, message, result) { return message.body || ""; case "comment_memory": return result?.managedBody || message.body || ""; + case "create_pull_request": + return result?.managedBody || message.body || ""; default: return null; } @@ -1061,6 +1063,30 @@ async function updateIssueBody(github, context, repo, issueNumber, updatedBody, core.info(`✓ Updated issue ${repo}#${issueNumber}`); } +/** + * Update the body of a pull request with resolved temporary IDs + * @param {any} github - GitHub API client + * @param {any} context - GitHub Actions context + * @param {string} repo - Repository in "owner/repo" format + * @param {number} prNumber - Pull request number to update + * @param {string} updatedBody - Updated body content with resolved temp IDs + * @returns {Promise} + */ +async function updatePullRequestBody(github, context, repo, prNumber, updatedBody, allowedMentionAliases = []) { + const [owner, repoName] = repo.split("/"); + + core.info(`Updating pull request ${repo}#${prNumber} body with resolved temporary IDs`); + + await github.rest.pulls.update({ + owner, + repo: repoName, + pull_number: prNumber, + body: sanitizeContent(updatedBody, { allowedAliases: allowedMentionAliases }), + }); + + core.info(`✓ Updated pull request ${repo}#${prNumber}`); +} + /** * Update the body of a discussion with resolved temporary IDs * @param {any} github - GitHub API client @@ -1228,6 +1254,10 @@ async function processSyntheticUpdates(github, context, trackedOutputs, temporar core.debug(`Skipping synthetic update for comment_memory - comment ID not tracked`); } break; + case "create_pull_request": + await updatePullRequestBody(github, context, tracked.result.repo, tracked.result.number, updatedContent, allowedMentionAliases); + updateCount++; + break; default: core.debug(`Unknown output type: ${tracked.type}`); } diff --git a/actions/setup/js/safe_output_handler_manager.test.cjs b/actions/setup/js/safe_output_handler_manager.test.cjs index fd1260ce773..ae979e863c1 100644 --- a/actions/setup/js/safe_output_handler_manager.test.cjs +++ b/actions/setup/js/safe_output_handler_manager.test.cjs @@ -1047,6 +1047,98 @@ describe("Safe Output Handler Manager", () => { expect(parentTracked.type).toBe("create_issue"); }); + it("should register temporary ID from create_pull_request result", async () => { + const messages = [{ type: "create_pull_request", temporary_id: "aw_pr1", title: "My PR", body: "PR body" }]; + + const prHandler = vi.fn().mockResolvedValue({ + success: true, + number: 42, + url: "https://github.com/owner/repo/pull/42", + temporaryId: "aw_pr1", + repo: "owner/repo", + }); + + const handlers = new Map([["create_pull_request", prHandler]]); + + const result = await processMessages(handlers, messages); + + expect(result.success).toBe(true); + expect(result.temporaryIdMap["aw_pr1"]).toBeDefined(); + expect(result.temporaryIdMap["aw_pr1"].number).toBe(42); + expect(result.temporaryIdMap["aw_pr1"].repo).toBe("owner/repo"); + }); + + it("should resolve #aw_prN in later messages after create_pull_request registers its temp ID", async () => { + const messages = [ + { type: "create_pull_request", temporary_id: "aw_pr1", title: "My PR", body: "PR body" }, + { type: "create_issue", title: "Summary", body: "See #aw_pr1 for the changes" }, + ]; + + const prHandler = vi.fn().mockResolvedValue({ + success: true, + number: 42, + url: "https://github.com/owner/repo/pull/42", + temporaryId: "aw_pr1", + repo: "owner/repo", + }); + + let capturedResolvedIds; + const issueHandler = vi.fn().mockImplementation((message, resolvedTemporaryIds) => { + capturedResolvedIds = resolvedTemporaryIds; + return Promise.resolve({ success: true, number: 100, repo: "owner/repo", temporaryId: undefined }); + }); + + const handlers = new Map([ + ["create_pull_request", prHandler], + ["create_issue", issueHandler], + ]); + + const result = await processMessages(handlers, messages); + + expect(result.success).toBe(true); + // aw_pr1 should be in the resolvedTemporaryIds snapshot passed to the second handler + expect(capturedResolvedIds).toBeDefined(); + expect(capturedResolvedIds["aw_pr1"]).toBeDefined(); + expect(capturedResolvedIds["aw_pr1"].number).toBe(42); + }); + + it("should track create_pull_request with forward temp ID refs for synthetic update", async () => { + const messages = [ + { type: "create_pull_request", temporary_id: "aw_pr1", title: "My PR", body: "Closes #aw_issue1" }, + { type: "create_issue", temporary_id: "aw_issue1", title: "Issue", body: "Issue body" }, + ]; + + const prHandler = vi.fn().mockResolvedValue({ + success: true, + number: 10, + url: "https://github.com/owner/repo/pull/10", + managedBody: "Managed: Closes #aw_issue1\n\n", + temporaryId: "aw_pr1", + repo: "owner/repo", + }); + + const issueHandler = vi.fn().mockResolvedValue({ + success: true, + number: 99, + repo: "owner/repo", + temporaryId: "aw_issue1", + }); + + const handlers = new Map([ + ["create_pull_request", prHandler], + ["create_issue", issueHandler], + ]); + + const result = await processMessages(handlers, messages); + + expect(result.success).toBe(true); + // PR was created with an unresolved forward ref (#aw_issue1 not yet registered) + expect(result.outputsWithUnresolvedIds.length).toBeGreaterThan(0); + const trackedPR = result.outputsWithUnresolvedIds.find(o => o.type === "create_pull_request"); + expect(trackedPR).toBeDefined(); + expect(trackedPR.result.number).toBe(10); + }); + it("should collect missing_tool and missing_data messages and include in result", async () => { const messages = [ { @@ -1497,8 +1589,8 @@ describe("Safe Output Handler Manager", () => { const prHandler = vi.fn().mockResolvedValue({ success: true, - pull_request_number: 5, - pull_request_url: "https://github.com/owner/repo/pull/5", + number: 5, + url: "https://github.com/owner/repo/pull/5", repo: "owner/repo", }); const commentHandler = vi.fn().mockResolvedValue([{ _tracking: null }]); diff --git a/actions/setup/js/safe_outputs_action_outputs.cjs b/actions/setup/js/safe_outputs_action_outputs.cjs index 561bc7cda0d..5d345b0a7e2 100644 --- a/actions/setup/js/safe_outputs_action_outputs.cjs +++ b/actions/setup/js/safe_outputs_action_outputs.cjs @@ -55,13 +55,13 @@ function emitSafeOutputActionOutputs(processingResult) { const firstPRResult = successfulResults.find(r => r.type === "create_pull_request"); if (firstPRResult?.result && !Array.isArray(firstPRResult.result)) { const r = firstPRResult.result; - if (r.pull_request_number != null) { - core.setOutput("created_pr_number", String(r.pull_request_number)); - core.info(`Exported created_pr_number: ${r.pull_request_number}`); + if (r.number != null) { + core.setOutput("created_pr_number", String(r.number)); + core.info(`Exported created_pr_number: ${r.number}`); } - if (r.pull_request_url) { - core.setOutput("created_pr_url", r.pull_request_url); - core.info(`Exported created_pr_url: ${r.pull_request_url}`); + if (r.url) { + core.setOutput("created_pr_url", r.url); + core.info(`Exported created_pr_url: ${r.url}`); } } diff --git a/actions/setup/js/safe_outputs_action_outputs.test.cjs b/actions/setup/js/safe_outputs_action_outputs.test.cjs index e160ce588df..096bb8177e3 100644 --- a/actions/setup/js/safe_outputs_action_outputs.test.cjs +++ b/actions/setup/js/safe_outputs_action_outputs.test.cjs @@ -56,7 +56,7 @@ describe("emitSafeOutputActionOutputs", () => { it("emits created_pr_number and created_pr_url for create_pull_request result", () => { emitSafeOutputActionOutputs({ - results: [{ success: true, type: "create_pull_request", result: { pull_request_number: 7, pull_request_url: "https://github.com/owner/repo/pull/7" } }], + results: [{ success: true, type: "create_pull_request", result: { number: 7, url: "https://github.com/owner/repo/pull/7" } }], }); expect(outputs["created_pr_number"]).toBe("7");