diff --git a/.github/workflows/daily-doc-updater.lock.yml b/.github/workflows/daily-doc-updater.lock.yml index cc9965c014..b37c08be82 100644 --- a/.github/workflows/daily-doc-updater.lock.yml +++ b/.github/workflows/daily-doc-updater.lock.yml @@ -3703,11 +3703,104 @@ jobs: GH_AW_PR_DRAFT: "false" GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const fs = require("fs"); const crypto = require("crypto"); + async function updateActivationComment(github, context, core, pullRequestUrl, pullRequestNumber) { + const commentId = process.env.GH_AW_COMMENT_ID; + const commentRepo = process.env.GH_AW_COMMENT_REPO; + if (!commentId) { + core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); + return; + } + core.info(`Updating activation comment ${commentId} with PR link`); + let repoOwner = context.repo.owner; + let repoName = context.repo.repo; + if (commentRepo) { + const parts = commentRepo.split("/"); + if (parts.length === 2) { + repoOwner = parts[0]; + repoName = parts[1]; + } else { + core.warning(`Invalid comment repo format: ${commentRepo}, expected "owner/repo". Falling back to context.repo.`); + } + } + core.info(`Updating comment in ${repoOwner}/${repoName}`); + const prLinkMessage = `\n\n✅ Pull request created: [#${pullRequestNumber}](${pullRequestUrl})`; + const isDiscussionComment = commentId.startsWith("DC_"); + try { + if (isDiscussionComment) { + const currentComment = await github.graphql( + ` + query($commentId: ID!) { + node(id: $commentId) { + ... on DiscussionComment { + body + } + } + }`, + { commentId: commentId } + ); + if (!currentComment?.node?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted or is inaccessible"); + return; + } + const currentBody = currentComment.node.body; + const updatedBody = currentBody + prLinkMessage; + const result = await github.graphql( + ` + mutation($commentId: ID!, $body: String!) { + updateDiscussionComment(input: { commentId: $commentId, body: $body }) { + comment { + id + url + } + } + }`, + { commentId: commentId, body: updatedBody } + ); + const comment = result.updateDiscussionComment.comment; + core.info(`Successfully updated discussion comment with PR link`); + core.info(`Comment ID: ${comment.id}`); + core.info(`Comment URL: ${comment.url}`); + } else { + const currentComment = await github.request("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + headers: { + Accept: "application/vnd.github+json", + }, + }); + if (!currentComment?.data?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted"); + return; + } + const currentBody = currentComment.data.body; + const updatedBody = currentBody + prLinkMessage; + const response = await github.request("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + body: updatedBody, + headers: { + Accept: "application/vnd.github+json", + }, + }); + core.info(`Successfully updated comment with PR link`); + core.info(`Comment ID: ${response.data.id}`); + core.info(`Comment URL: ${response.data.html_url}`); + } + } catch (error) { + core.warning(`Failed to update activation comment: ${error instanceof Error ? error.message : String(error)}`); + } + } + updateActivationComment, + }; function generatePatchPreview(patchContent) { if (!patchContent || !patchContent.trim()) { return ""; @@ -4077,6 +4170,7 @@ jobs: core.setOutput("pull_request_number", pullRequest.number); core.setOutput("pull_request_url", pullRequest.html_url); core.setOutput("branch_name", branchName); + await updateActivationComment(github, context, core, pullRequest.html_url, pullRequest.number); await core.summary .addRaw( ` diff --git a/.github/workflows/daily-perf-improver.lock.yml b/.github/workflows/daily-perf-improver.lock.yml index d12918e56a..1a4b508e4e 100644 --- a/.github/workflows/daily-perf-improver.lock.yml +++ b/.github/workflows/daily-perf-improver.lock.yml @@ -4660,11 +4660,104 @@ jobs: GH_AW_PR_DRAFT: "true" GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const fs = require("fs"); const crypto = require("crypto"); + async function updateActivationComment(github, context, core, pullRequestUrl, pullRequestNumber) { + const commentId = process.env.GH_AW_COMMENT_ID; + const commentRepo = process.env.GH_AW_COMMENT_REPO; + if (!commentId) { + core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); + return; + } + core.info(`Updating activation comment ${commentId} with PR link`); + let repoOwner = context.repo.owner; + let repoName = context.repo.repo; + if (commentRepo) { + const parts = commentRepo.split("/"); + if (parts.length === 2) { + repoOwner = parts[0]; + repoName = parts[1]; + } else { + core.warning(`Invalid comment repo format: ${commentRepo}, expected "owner/repo". Falling back to context.repo.`); + } + } + core.info(`Updating comment in ${repoOwner}/${repoName}`); + const prLinkMessage = `\n\n✅ Pull request created: [#${pullRequestNumber}](${pullRequestUrl})`; + const isDiscussionComment = commentId.startsWith("DC_"); + try { + if (isDiscussionComment) { + const currentComment = await github.graphql( + ` + query($commentId: ID!) { + node(id: $commentId) { + ... on DiscussionComment { + body + } + } + }`, + { commentId: commentId } + ); + if (!currentComment?.node?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted or is inaccessible"); + return; + } + const currentBody = currentComment.node.body; + const updatedBody = currentBody + prLinkMessage; + const result = await github.graphql( + ` + mutation($commentId: ID!, $body: String!) { + updateDiscussionComment(input: { commentId: $commentId, body: $body }) { + comment { + id + url + } + } + }`, + { commentId: commentId, body: updatedBody } + ); + const comment = result.updateDiscussionComment.comment; + core.info(`Successfully updated discussion comment with PR link`); + core.info(`Comment ID: ${comment.id}`); + core.info(`Comment URL: ${comment.url}`); + } else { + const currentComment = await github.request("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + headers: { + Accept: "application/vnd.github+json", + }, + }); + if (!currentComment?.data?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted"); + return; + } + const currentBody = currentComment.data.body; + const updatedBody = currentBody + prLinkMessage; + const response = await github.request("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + body: updatedBody, + headers: { + Accept: "application/vnd.github+json", + }, + }); + core.info(`Successfully updated comment with PR link`); + core.info(`Comment ID: ${response.data.id}`); + core.info(`Comment URL: ${response.data.html_url}`); + } + } catch (error) { + core.warning(`Failed to update activation comment: ${error instanceof Error ? error.message : String(error)}`); + } + } + updateActivationComment, + }; function generatePatchPreview(patchContent) { if (!patchContent || !patchContent.trim()) { return ""; @@ -5034,6 +5127,7 @@ jobs: core.setOutput("pull_request_number", pullRequest.number); core.setOutput("pull_request_url", pullRequest.html_url); core.setOutput("branch_name", branchName); + await updateActivationComment(github, context, core, pullRequest.html_url, pullRequest.number); await core.summary .addRaw( ` diff --git a/.github/workflows/daily-test-improver.lock.yml b/.github/workflows/daily-test-improver.lock.yml index 719c407b8f..c1c1ef5ed9 100644 --- a/.github/workflows/daily-test-improver.lock.yml +++ b/.github/workflows/daily-test-improver.lock.yml @@ -4634,11 +4634,104 @@ jobs: GH_AW_PR_DRAFT: "true" GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const fs = require("fs"); const crypto = require("crypto"); + async function updateActivationComment(github, context, core, pullRequestUrl, pullRequestNumber) { + const commentId = process.env.GH_AW_COMMENT_ID; + const commentRepo = process.env.GH_AW_COMMENT_REPO; + if (!commentId) { + core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); + return; + } + core.info(`Updating activation comment ${commentId} with PR link`); + let repoOwner = context.repo.owner; + let repoName = context.repo.repo; + if (commentRepo) { + const parts = commentRepo.split("/"); + if (parts.length === 2) { + repoOwner = parts[0]; + repoName = parts[1]; + } else { + core.warning(`Invalid comment repo format: ${commentRepo}, expected "owner/repo". Falling back to context.repo.`); + } + } + core.info(`Updating comment in ${repoOwner}/${repoName}`); + const prLinkMessage = `\n\n✅ Pull request created: [#${pullRequestNumber}](${pullRequestUrl})`; + const isDiscussionComment = commentId.startsWith("DC_"); + try { + if (isDiscussionComment) { + const currentComment = await github.graphql( + ` + query($commentId: ID!) { + node(id: $commentId) { + ... on DiscussionComment { + body + } + } + }`, + { commentId: commentId } + ); + if (!currentComment?.node?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted or is inaccessible"); + return; + } + const currentBody = currentComment.node.body; + const updatedBody = currentBody + prLinkMessage; + const result = await github.graphql( + ` + mutation($commentId: ID!, $body: String!) { + updateDiscussionComment(input: { commentId: $commentId, body: $body }) { + comment { + id + url + } + } + }`, + { commentId: commentId, body: updatedBody } + ); + const comment = result.updateDiscussionComment.comment; + core.info(`Successfully updated discussion comment with PR link`); + core.info(`Comment ID: ${comment.id}`); + core.info(`Comment URL: ${comment.url}`); + } else { + const currentComment = await github.request("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + headers: { + Accept: "application/vnd.github+json", + }, + }); + if (!currentComment?.data?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted"); + return; + } + const currentBody = currentComment.data.body; + const updatedBody = currentBody + prLinkMessage; + const response = await github.request("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + body: updatedBody, + headers: { + Accept: "application/vnd.github+json", + }, + }); + core.info(`Successfully updated comment with PR link`); + core.info(`Comment ID: ${response.data.id}`); + core.info(`Comment URL: ${response.data.html_url}`); + } + } catch (error) { + core.warning(`Failed to update activation comment: ${error instanceof Error ? error.message : String(error)}`); + } + } + updateActivationComment, + }; function generatePatchPreview(patchContent) { if (!patchContent || !patchContent.trim()) { return ""; @@ -5008,6 +5101,7 @@ jobs: core.setOutput("pull_request_number", pullRequest.number); core.setOutput("pull_request_url", pullRequest.html_url); core.setOutput("branch_name", branchName); + await updateActivationComment(github, context, core, pullRequest.html_url, pullRequest.number); await core.summary .addRaw( ` diff --git a/.github/workflows/developer-docs-consolidator.lock.yml b/.github/workflows/developer-docs-consolidator.lock.yml index 7e6f23e291..db7b3e5e99 100644 --- a/.github/workflows/developer-docs-consolidator.lock.yml +++ b/.github/workflows/developer-docs-consolidator.lock.yml @@ -4508,11 +4508,104 @@ jobs: GH_AW_PR_DRAFT: "false" GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const fs = require("fs"); const crypto = require("crypto"); + async function updateActivationComment(github, context, core, pullRequestUrl, pullRequestNumber) { + const commentId = process.env.GH_AW_COMMENT_ID; + const commentRepo = process.env.GH_AW_COMMENT_REPO; + if (!commentId) { + core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); + return; + } + core.info(`Updating activation comment ${commentId} with PR link`); + let repoOwner = context.repo.owner; + let repoName = context.repo.repo; + if (commentRepo) { + const parts = commentRepo.split("/"); + if (parts.length === 2) { + repoOwner = parts[0]; + repoName = parts[1]; + } else { + core.warning(`Invalid comment repo format: ${commentRepo}, expected "owner/repo". Falling back to context.repo.`); + } + } + core.info(`Updating comment in ${repoOwner}/${repoName}`); + const prLinkMessage = `\n\n✅ Pull request created: [#${pullRequestNumber}](${pullRequestUrl})`; + const isDiscussionComment = commentId.startsWith("DC_"); + try { + if (isDiscussionComment) { + const currentComment = await github.graphql( + ` + query($commentId: ID!) { + node(id: $commentId) { + ... on DiscussionComment { + body + } + } + }`, + { commentId: commentId } + ); + if (!currentComment?.node?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted or is inaccessible"); + return; + } + const currentBody = currentComment.node.body; + const updatedBody = currentBody + prLinkMessage; + const result = await github.graphql( + ` + mutation($commentId: ID!, $body: String!) { + updateDiscussionComment(input: { commentId: $commentId, body: $body }) { + comment { + id + url + } + } + }`, + { commentId: commentId, body: updatedBody } + ); + const comment = result.updateDiscussionComment.comment; + core.info(`Successfully updated discussion comment with PR link`); + core.info(`Comment ID: ${comment.id}`); + core.info(`Comment URL: ${comment.url}`); + } else { + const currentComment = await github.request("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + headers: { + Accept: "application/vnd.github+json", + }, + }); + if (!currentComment?.data?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted"); + return; + } + const currentBody = currentComment.data.body; + const updatedBody = currentBody + prLinkMessage; + const response = await github.request("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + body: updatedBody, + headers: { + Accept: "application/vnd.github+json", + }, + }); + core.info(`Successfully updated comment with PR link`); + core.info(`Comment ID: ${response.data.id}`); + core.info(`Comment URL: ${response.data.html_url}`); + } + } catch (error) { + core.warning(`Failed to update activation comment: ${error instanceof Error ? error.message : String(error)}`); + } + } + updateActivationComment, + }; function generatePatchPreview(patchContent) { if (!patchContent || !patchContent.trim()) { return ""; @@ -4882,6 +4975,7 @@ jobs: core.setOutput("pull_request_number", pullRequest.number); core.setOutput("pull_request_url", pullRequest.html_url); core.setOutput("branch_name", branchName); + await updateActivationComment(github, context, core, pullRequest.html_url, pullRequest.number); await core.summary .addRaw( ` diff --git a/.github/workflows/dictation-prompt.lock.yml b/.github/workflows/dictation-prompt.lock.yml index 17b1a2fed8..021327266a 100644 --- a/.github/workflows/dictation-prompt.lock.yml +++ b/.github/workflows/dictation-prompt.lock.yml @@ -3875,11 +3875,104 @@ jobs: GH_AW_PR_DRAFT: "false" GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const fs = require("fs"); const crypto = require("crypto"); + async function updateActivationComment(github, context, core, pullRequestUrl, pullRequestNumber) { + const commentId = process.env.GH_AW_COMMENT_ID; + const commentRepo = process.env.GH_AW_COMMENT_REPO; + if (!commentId) { + core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); + return; + } + core.info(`Updating activation comment ${commentId} with PR link`); + let repoOwner = context.repo.owner; + let repoName = context.repo.repo; + if (commentRepo) { + const parts = commentRepo.split("/"); + if (parts.length === 2) { + repoOwner = parts[0]; + repoName = parts[1]; + } else { + core.warning(`Invalid comment repo format: ${commentRepo}, expected "owner/repo". Falling back to context.repo.`); + } + } + core.info(`Updating comment in ${repoOwner}/${repoName}`); + const prLinkMessage = `\n\n✅ Pull request created: [#${pullRequestNumber}](${pullRequestUrl})`; + const isDiscussionComment = commentId.startsWith("DC_"); + try { + if (isDiscussionComment) { + const currentComment = await github.graphql( + ` + query($commentId: ID!) { + node(id: $commentId) { + ... on DiscussionComment { + body + } + } + }`, + { commentId: commentId } + ); + if (!currentComment?.node?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted or is inaccessible"); + return; + } + const currentBody = currentComment.node.body; + const updatedBody = currentBody + prLinkMessage; + const result = await github.graphql( + ` + mutation($commentId: ID!, $body: String!) { + updateDiscussionComment(input: { commentId: $commentId, body: $body }) { + comment { + id + url + } + } + }`, + { commentId: commentId, body: updatedBody } + ); + const comment = result.updateDiscussionComment.comment; + core.info(`Successfully updated discussion comment with PR link`); + core.info(`Comment ID: ${comment.id}`); + core.info(`Comment URL: ${comment.url}`); + } else { + const currentComment = await github.request("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + headers: { + Accept: "application/vnd.github+json", + }, + }); + if (!currentComment?.data?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted"); + return; + } + const currentBody = currentComment.data.body; + const updatedBody = currentBody + prLinkMessage; + const response = await github.request("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + body: updatedBody, + headers: { + Accept: "application/vnd.github+json", + }, + }); + core.info(`Successfully updated comment with PR link`); + core.info(`Comment ID: ${response.data.id}`); + core.info(`Comment URL: ${response.data.html_url}`); + } + } catch (error) { + core.warning(`Failed to update activation comment: ${error instanceof Error ? error.message : String(error)}`); + } + } + updateActivationComment, + }; function generatePatchPreview(patchContent) { if (!patchContent || !patchContent.trim()) { return ""; @@ -4249,6 +4342,7 @@ jobs: core.setOutput("pull_request_number", pullRequest.number); core.setOutput("pull_request_url", pullRequest.html_url); core.setOutput("branch_name", branchName); + await updateActivationComment(github, context, core, pullRequest.html_url, pullRequest.number); await core.summary .addRaw( ` diff --git a/.github/workflows/github-mcp-tools-report.lock.yml b/.github/workflows/github-mcp-tools-report.lock.yml index 463c868c67..d2726474c4 100644 --- a/.github/workflows/github-mcp-tools-report.lock.yml +++ b/.github/workflows/github-mcp-tools-report.lock.yml @@ -4330,11 +4330,104 @@ jobs: GH_AW_PR_DRAFT: "false" GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const fs = require("fs"); const crypto = require("crypto"); + async function updateActivationComment(github, context, core, pullRequestUrl, pullRequestNumber) { + const commentId = process.env.GH_AW_COMMENT_ID; + const commentRepo = process.env.GH_AW_COMMENT_REPO; + if (!commentId) { + core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); + return; + } + core.info(`Updating activation comment ${commentId} with PR link`); + let repoOwner = context.repo.owner; + let repoName = context.repo.repo; + if (commentRepo) { + const parts = commentRepo.split("/"); + if (parts.length === 2) { + repoOwner = parts[0]; + repoName = parts[1]; + } else { + core.warning(`Invalid comment repo format: ${commentRepo}, expected "owner/repo". Falling back to context.repo.`); + } + } + core.info(`Updating comment in ${repoOwner}/${repoName}`); + const prLinkMessage = `\n\n✅ Pull request created: [#${pullRequestNumber}](${pullRequestUrl})`; + const isDiscussionComment = commentId.startsWith("DC_"); + try { + if (isDiscussionComment) { + const currentComment = await github.graphql( + ` + query($commentId: ID!) { + node(id: $commentId) { + ... on DiscussionComment { + body + } + } + }`, + { commentId: commentId } + ); + if (!currentComment?.node?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted or is inaccessible"); + return; + } + const currentBody = currentComment.node.body; + const updatedBody = currentBody + prLinkMessage; + const result = await github.graphql( + ` + mutation($commentId: ID!, $body: String!) { + updateDiscussionComment(input: { commentId: $commentId, body: $body }) { + comment { + id + url + } + } + }`, + { commentId: commentId, body: updatedBody } + ); + const comment = result.updateDiscussionComment.comment; + core.info(`Successfully updated discussion comment with PR link`); + core.info(`Comment ID: ${comment.id}`); + core.info(`Comment URL: ${comment.url}`); + } else { + const currentComment = await github.request("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + headers: { + Accept: "application/vnd.github+json", + }, + }); + if (!currentComment?.data?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted"); + return; + } + const currentBody = currentComment.data.body; + const updatedBody = currentBody + prLinkMessage; + const response = await github.request("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + body: updatedBody, + headers: { + Accept: "application/vnd.github+json", + }, + }); + core.info(`Successfully updated comment with PR link`); + core.info(`Comment ID: ${response.data.id}`); + core.info(`Comment URL: ${response.data.html_url}`); + } + } catch (error) { + core.warning(`Failed to update activation comment: ${error instanceof Error ? error.message : String(error)}`); + } + } + updateActivationComment, + }; function generatePatchPreview(patchContent) { if (!patchContent || !patchContent.trim()) { return ""; @@ -4704,6 +4797,7 @@ jobs: core.setOutput("pull_request_number", pullRequest.number); core.setOutput("pull_request_url", pullRequest.html_url); core.setOutput("branch_name", branchName); + await updateActivationComment(github, context, core, pullRequest.html_url, pullRequest.number); await core.summary .addRaw( ` diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index ffbca628bf..eb286bfc78 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -3822,11 +3822,104 @@ jobs: GH_AW_PR_DRAFT: "false" GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const fs = require("fs"); const crypto = require("crypto"); + async function updateActivationComment(github, context, core, pullRequestUrl, pullRequestNumber) { + const commentId = process.env.GH_AW_COMMENT_ID; + const commentRepo = process.env.GH_AW_COMMENT_REPO; + if (!commentId) { + core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); + return; + } + core.info(`Updating activation comment ${commentId} with PR link`); + let repoOwner = context.repo.owner; + let repoName = context.repo.repo; + if (commentRepo) { + const parts = commentRepo.split("/"); + if (parts.length === 2) { + repoOwner = parts[0]; + repoName = parts[1]; + } else { + core.warning(`Invalid comment repo format: ${commentRepo}, expected "owner/repo". Falling back to context.repo.`); + } + } + core.info(`Updating comment in ${repoOwner}/${repoName}`); + const prLinkMessage = `\n\n✅ Pull request created: [#${pullRequestNumber}](${pullRequestUrl})`; + const isDiscussionComment = commentId.startsWith("DC_"); + try { + if (isDiscussionComment) { + const currentComment = await github.graphql( + ` + query($commentId: ID!) { + node(id: $commentId) { + ... on DiscussionComment { + body + } + } + }`, + { commentId: commentId } + ); + if (!currentComment?.node?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted or is inaccessible"); + return; + } + const currentBody = currentComment.node.body; + const updatedBody = currentBody + prLinkMessage; + const result = await github.graphql( + ` + mutation($commentId: ID!, $body: String!) { + updateDiscussionComment(input: { commentId: $commentId, body: $body }) { + comment { + id + url + } + } + }`, + { commentId: commentId, body: updatedBody } + ); + const comment = result.updateDiscussionComment.comment; + core.info(`Successfully updated discussion comment with PR link`); + core.info(`Comment ID: ${comment.id}`); + core.info(`Comment URL: ${comment.url}`); + } else { + const currentComment = await github.request("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + headers: { + Accept: "application/vnd.github+json", + }, + }); + if (!currentComment?.data?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted"); + return; + } + const currentBody = currentComment.data.body; + const updatedBody = currentBody + prLinkMessage; + const response = await github.request("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + body: updatedBody, + headers: { + Accept: "application/vnd.github+json", + }, + }); + core.info(`Successfully updated comment with PR link`); + core.info(`Comment ID: ${response.data.id}`); + core.info(`Comment URL: ${response.data.html_url}`); + } + } catch (error) { + core.warning(`Failed to update activation comment: ${error instanceof Error ? error.message : String(error)}`); + } + } + updateActivationComment, + }; function generatePatchPreview(patchContent) { if (!patchContent || !patchContent.trim()) { return ""; @@ -4196,6 +4289,7 @@ jobs: core.setOutput("pull_request_number", pullRequest.number); core.setOutput("pull_request_url", pullRequest.html_url); core.setOutput("branch_name", branchName); + await updateActivationComment(github, context, core, pullRequest.html_url, pullRequest.number); await core.summary .addRaw( ` diff --git a/.github/workflows/instructions-janitor.lock.yml b/.github/workflows/instructions-janitor.lock.yml index bd622f16eb..af87ac1141 100644 --- a/.github/workflows/instructions-janitor.lock.yml +++ b/.github/workflows/instructions-janitor.lock.yml @@ -3701,11 +3701,104 @@ jobs: GH_AW_PR_DRAFT: "false" GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const fs = require("fs"); const crypto = require("crypto"); + async function updateActivationComment(github, context, core, pullRequestUrl, pullRequestNumber) { + const commentId = process.env.GH_AW_COMMENT_ID; + const commentRepo = process.env.GH_AW_COMMENT_REPO; + if (!commentId) { + core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); + return; + } + core.info(`Updating activation comment ${commentId} with PR link`); + let repoOwner = context.repo.owner; + let repoName = context.repo.repo; + if (commentRepo) { + const parts = commentRepo.split("/"); + if (parts.length === 2) { + repoOwner = parts[0]; + repoName = parts[1]; + } else { + core.warning(`Invalid comment repo format: ${commentRepo}, expected "owner/repo". Falling back to context.repo.`); + } + } + core.info(`Updating comment in ${repoOwner}/${repoName}`); + const prLinkMessage = `\n\n✅ Pull request created: [#${pullRequestNumber}](${pullRequestUrl})`; + const isDiscussionComment = commentId.startsWith("DC_"); + try { + if (isDiscussionComment) { + const currentComment = await github.graphql( + ` + query($commentId: ID!) { + node(id: $commentId) { + ... on DiscussionComment { + body + } + } + }`, + { commentId: commentId } + ); + if (!currentComment?.node?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted or is inaccessible"); + return; + } + const currentBody = currentComment.node.body; + const updatedBody = currentBody + prLinkMessage; + const result = await github.graphql( + ` + mutation($commentId: ID!, $body: String!) { + updateDiscussionComment(input: { commentId: $commentId, body: $body }) { + comment { + id + url + } + } + }`, + { commentId: commentId, body: updatedBody } + ); + const comment = result.updateDiscussionComment.comment; + core.info(`Successfully updated discussion comment with PR link`); + core.info(`Comment ID: ${comment.id}`); + core.info(`Comment URL: ${comment.url}`); + } else { + const currentComment = await github.request("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + headers: { + Accept: "application/vnd.github+json", + }, + }); + if (!currentComment?.data?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted"); + return; + } + const currentBody = currentComment.data.body; + const updatedBody = currentBody + prLinkMessage; + const response = await github.request("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + body: updatedBody, + headers: { + Accept: "application/vnd.github+json", + }, + }); + core.info(`Successfully updated comment with PR link`); + core.info(`Comment ID: ${response.data.id}`); + core.info(`Comment URL: ${response.data.html_url}`); + } + } catch (error) { + core.warning(`Failed to update activation comment: ${error instanceof Error ? error.message : String(error)}`); + } + } + updateActivationComment, + }; function generatePatchPreview(patchContent) { if (!patchContent || !patchContent.trim()) { return ""; @@ -4075,6 +4168,7 @@ jobs: core.setOutput("pull_request_number", pullRequest.number); core.setOutput("pull_request_url", pullRequest.html_url); core.setOutput("branch_name", branchName); + await updateActivationComment(github, context, core, pullRequest.html_url, pullRequest.number); await core.summary .addRaw( ` diff --git a/.github/workflows/poem-bot.lock.yml b/.github/workflows/poem-bot.lock.yml index 86d589f361..3e1e01ee8f 100644 --- a/.github/workflows/poem-bot.lock.yml +++ b/.github/workflows/poem-bot.lock.yml @@ -5900,12 +5900,105 @@ jobs: GH_AW_PR_DRAFT: "false" GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} GH_AW_SAFE_OUTPUTS_STAGED: "true" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const fs = require("fs"); const crypto = require("crypto"); + async function updateActivationComment(github, context, core, pullRequestUrl, pullRequestNumber) { + const commentId = process.env.GH_AW_COMMENT_ID; + const commentRepo = process.env.GH_AW_COMMENT_REPO; + if (!commentId) { + core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); + return; + } + core.info(`Updating activation comment ${commentId} with PR link`); + let repoOwner = context.repo.owner; + let repoName = context.repo.repo; + if (commentRepo) { + const parts = commentRepo.split("/"); + if (parts.length === 2) { + repoOwner = parts[0]; + repoName = parts[1]; + } else { + core.warning(`Invalid comment repo format: ${commentRepo}, expected "owner/repo". Falling back to context.repo.`); + } + } + core.info(`Updating comment in ${repoOwner}/${repoName}`); + const prLinkMessage = `\n\n✅ Pull request created: [#${pullRequestNumber}](${pullRequestUrl})`; + const isDiscussionComment = commentId.startsWith("DC_"); + try { + if (isDiscussionComment) { + const currentComment = await github.graphql( + ` + query($commentId: ID!) { + node(id: $commentId) { + ... on DiscussionComment { + body + } + } + }`, + { commentId: commentId } + ); + if (!currentComment?.node?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted or is inaccessible"); + return; + } + const currentBody = currentComment.node.body; + const updatedBody = currentBody + prLinkMessage; + const result = await github.graphql( + ` + mutation($commentId: ID!, $body: String!) { + updateDiscussionComment(input: { commentId: $commentId, body: $body }) { + comment { + id + url + } + } + }`, + { commentId: commentId, body: updatedBody } + ); + const comment = result.updateDiscussionComment.comment; + core.info(`Successfully updated discussion comment with PR link`); + core.info(`Comment ID: ${comment.id}`); + core.info(`Comment URL: ${comment.url}`); + } else { + const currentComment = await github.request("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + headers: { + Accept: "application/vnd.github+json", + }, + }); + if (!currentComment?.data?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted"); + return; + } + const currentBody = currentComment.data.body; + const updatedBody = currentBody + prLinkMessage; + const response = await github.request("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + body: updatedBody, + headers: { + Accept: "application/vnd.github+json", + }, + }); + core.info(`Successfully updated comment with PR link`); + core.info(`Comment ID: ${response.data.id}`); + core.info(`Comment URL: ${response.data.html_url}`); + } + } catch (error) { + core.warning(`Failed to update activation comment: ${error instanceof Error ? error.message : String(error)}`); + } + } + updateActivationComment, + }; function generatePatchPreview(patchContent) { if (!patchContent || !patchContent.trim()) { return ""; @@ -6275,6 +6368,7 @@ jobs: core.setOutput("pull_request_number", pullRequest.number); core.setOutput("pull_request_url", pullRequest.html_url); core.setOutput("branch_name", branchName); + await updateActivationComment(github, context, core, pullRequest.html_url, pullRequest.number); await core.summary .addRaw( ` diff --git a/.github/workflows/q.lock.yml b/.github/workflows/q.lock.yml index 279a1950bd..ccf786f87e 100644 --- a/.github/workflows/q.lock.yml +++ b/.github/workflows/q.lock.yml @@ -5287,11 +5287,104 @@ jobs: GH_AW_PR_DRAFT: "false" GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const fs = require("fs"); const crypto = require("crypto"); + async function updateActivationComment(github, context, core, pullRequestUrl, pullRequestNumber) { + const commentId = process.env.GH_AW_COMMENT_ID; + const commentRepo = process.env.GH_AW_COMMENT_REPO; + if (!commentId) { + core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); + return; + } + core.info(`Updating activation comment ${commentId} with PR link`); + let repoOwner = context.repo.owner; + let repoName = context.repo.repo; + if (commentRepo) { + const parts = commentRepo.split("/"); + if (parts.length === 2) { + repoOwner = parts[0]; + repoName = parts[1]; + } else { + core.warning(`Invalid comment repo format: ${commentRepo}, expected "owner/repo". Falling back to context.repo.`); + } + } + core.info(`Updating comment in ${repoOwner}/${repoName}`); + const prLinkMessage = `\n\n✅ Pull request created: [#${pullRequestNumber}](${pullRequestUrl})`; + const isDiscussionComment = commentId.startsWith("DC_"); + try { + if (isDiscussionComment) { + const currentComment = await github.graphql( + ` + query($commentId: ID!) { + node(id: $commentId) { + ... on DiscussionComment { + body + } + } + }`, + { commentId: commentId } + ); + if (!currentComment?.node?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted or is inaccessible"); + return; + } + const currentBody = currentComment.node.body; + const updatedBody = currentBody + prLinkMessage; + const result = await github.graphql( + ` + mutation($commentId: ID!, $body: String!) { + updateDiscussionComment(input: { commentId: $commentId, body: $body }) { + comment { + id + url + } + } + }`, + { commentId: commentId, body: updatedBody } + ); + const comment = result.updateDiscussionComment.comment; + core.info(`Successfully updated discussion comment with PR link`); + core.info(`Comment ID: ${comment.id}`); + core.info(`Comment URL: ${comment.url}`); + } else { + const currentComment = await github.request("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + headers: { + Accept: "application/vnd.github+json", + }, + }); + if (!currentComment?.data?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted"); + return; + } + const currentBody = currentComment.data.body; + const updatedBody = currentBody + prLinkMessage; + const response = await github.request("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + body: updatedBody, + headers: { + Accept: "application/vnd.github+json", + }, + }); + core.info(`Successfully updated comment with PR link`); + core.info(`Comment ID: ${response.data.id}`); + core.info(`Comment URL: ${response.data.html_url}`); + } + } catch (error) { + core.warning(`Failed to update activation comment: ${error instanceof Error ? error.message : String(error)}`); + } + } + updateActivationComment, + }; function generatePatchPreview(patchContent) { if (!patchContent || !patchContent.trim()) { return ""; @@ -5661,6 +5754,7 @@ jobs: core.setOutput("pull_request_number", pullRequest.number); core.setOutput("pull_request_url", pullRequest.html_url); core.setOutput("branch_name", branchName); + await updateActivationComment(github, context, core, pullRequest.html_url, pullRequest.number); await core.summary .addRaw( ` diff --git a/.github/workflows/security-fix-pr.lock.yml b/.github/workflows/security-fix-pr.lock.yml index 003891d897..950d769cad 100644 --- a/.github/workflows/security-fix-pr.lock.yml +++ b/.github/workflows/security-fix-pr.lock.yml @@ -3649,11 +3649,104 @@ jobs: GH_AW_PR_DRAFT: "true" GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const fs = require("fs"); const crypto = require("crypto"); + async function updateActivationComment(github, context, core, pullRequestUrl, pullRequestNumber) { + const commentId = process.env.GH_AW_COMMENT_ID; + const commentRepo = process.env.GH_AW_COMMENT_REPO; + if (!commentId) { + core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); + return; + } + core.info(`Updating activation comment ${commentId} with PR link`); + let repoOwner = context.repo.owner; + let repoName = context.repo.repo; + if (commentRepo) { + const parts = commentRepo.split("/"); + if (parts.length === 2) { + repoOwner = parts[0]; + repoName = parts[1]; + } else { + core.warning(`Invalid comment repo format: ${commentRepo}, expected "owner/repo". Falling back to context.repo.`); + } + } + core.info(`Updating comment in ${repoOwner}/${repoName}`); + const prLinkMessage = `\n\n✅ Pull request created: [#${pullRequestNumber}](${pullRequestUrl})`; + const isDiscussionComment = commentId.startsWith("DC_"); + try { + if (isDiscussionComment) { + const currentComment = await github.graphql( + ` + query($commentId: ID!) { + node(id: $commentId) { + ... on DiscussionComment { + body + } + } + }`, + { commentId: commentId } + ); + if (!currentComment?.node?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted or is inaccessible"); + return; + } + const currentBody = currentComment.node.body; + const updatedBody = currentBody + prLinkMessage; + const result = await github.graphql( + ` + mutation($commentId: ID!, $body: String!) { + updateDiscussionComment(input: { commentId: $commentId, body: $body }) { + comment { + id + url + } + } + }`, + { commentId: commentId, body: updatedBody } + ); + const comment = result.updateDiscussionComment.comment; + core.info(`Successfully updated discussion comment with PR link`); + core.info(`Comment ID: ${comment.id}`); + core.info(`Comment URL: ${comment.url}`); + } else { + const currentComment = await github.request("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + headers: { + Accept: "application/vnd.github+json", + }, + }); + if (!currentComment?.data?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted"); + return; + } + const currentBody = currentComment.data.body; + const updatedBody = currentBody + prLinkMessage; + const response = await github.request("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + body: updatedBody, + headers: { + Accept: "application/vnd.github+json", + }, + }); + core.info(`Successfully updated comment with PR link`); + core.info(`Comment ID: ${response.data.id}`); + core.info(`Comment URL: ${response.data.html_url}`); + } + } catch (error) { + core.warning(`Failed to update activation comment: ${error instanceof Error ? error.message : String(error)}`); + } + } + updateActivationComment, + }; function generatePatchPreview(patchContent) { if (!patchContent || !patchContent.trim()) { return ""; @@ -4023,6 +4116,7 @@ jobs: core.setOutput("pull_request_number", pullRequest.number); core.setOutput("pull_request_url", pullRequest.html_url); core.setOutput("branch_name", branchName); + await updateActivationComment(github, context, core, pullRequest.html_url, pullRequest.number); await core.summary .addRaw( ` diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index fbfec2a0c2..9870fb36ec 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -5018,11 +5018,104 @@ jobs: GH_AW_PR_DRAFT: "false" GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const fs = require("fs"); const crypto = require("crypto"); + async function updateActivationComment(github, context, core, pullRequestUrl, pullRequestNumber) { + const commentId = process.env.GH_AW_COMMENT_ID; + const commentRepo = process.env.GH_AW_COMMENT_REPO; + if (!commentId) { + core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); + return; + } + core.info(`Updating activation comment ${commentId} with PR link`); + let repoOwner = context.repo.owner; + let repoName = context.repo.repo; + if (commentRepo) { + const parts = commentRepo.split("/"); + if (parts.length === 2) { + repoOwner = parts[0]; + repoName = parts[1]; + } else { + core.warning(`Invalid comment repo format: ${commentRepo}, expected "owner/repo". Falling back to context.repo.`); + } + } + core.info(`Updating comment in ${repoOwner}/${repoName}`); + const prLinkMessage = `\n\n✅ Pull request created: [#${pullRequestNumber}](${pullRequestUrl})`; + const isDiscussionComment = commentId.startsWith("DC_"); + try { + if (isDiscussionComment) { + const currentComment = await github.graphql( + ` + query($commentId: ID!) { + node(id: $commentId) { + ... on DiscussionComment { + body + } + } + }`, + { commentId: commentId } + ); + if (!currentComment?.node?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted or is inaccessible"); + return; + } + const currentBody = currentComment.node.body; + const updatedBody = currentBody + prLinkMessage; + const result = await github.graphql( + ` + mutation($commentId: ID!, $body: String!) { + updateDiscussionComment(input: { commentId: $commentId, body: $body }) { + comment { + id + url + } + } + }`, + { commentId: commentId, body: updatedBody } + ); + const comment = result.updateDiscussionComment.comment; + core.info(`Successfully updated discussion comment with PR link`); + core.info(`Comment ID: ${comment.id}`); + core.info(`Comment URL: ${comment.url}`); + } else { + const currentComment = await github.request("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + headers: { + Accept: "application/vnd.github+json", + }, + }); + if (!currentComment?.data?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted"); + return; + } + const currentBody = currentComment.data.body; + const updatedBody = currentBody + prLinkMessage; + const response = await github.request("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + body: updatedBody, + headers: { + Accept: "application/vnd.github+json", + }, + }); + core.info(`Successfully updated comment with PR link`); + core.info(`Comment ID: ${response.data.id}`); + core.info(`Comment URL: ${response.data.html_url}`); + } + } catch (error) { + core.warning(`Failed to update activation comment: ${error instanceof Error ? error.message : String(error)}`); + } + } + updateActivationComment, + }; function generatePatchPreview(patchContent) { if (!patchContent || !patchContent.trim()) { return ""; @@ -5392,6 +5485,7 @@ jobs: core.setOutput("pull_request_number", pullRequest.number); core.setOutput("pull_request_url", pullRequest.html_url); core.setOutput("branch_name", branchName); + await updateActivationComment(github, context, core, pullRequest.html_url, pullRequest.number); await core.summary .addRaw( ` diff --git a/.github/workflows/tidy.lock.yml b/.github/workflows/tidy.lock.yml index a1132699b3..685dbf6193 100644 --- a/.github/workflows/tidy.lock.yml +++ b/.github/workflows/tidy.lock.yml @@ -4239,11 +4239,104 @@ jobs: GH_AW_PR_DRAFT: "false" GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const fs = require("fs"); const crypto = require("crypto"); + async function updateActivationComment(github, context, core, pullRequestUrl, pullRequestNumber) { + const commentId = process.env.GH_AW_COMMENT_ID; + const commentRepo = process.env.GH_AW_COMMENT_REPO; + if (!commentId) { + core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); + return; + } + core.info(`Updating activation comment ${commentId} with PR link`); + let repoOwner = context.repo.owner; + let repoName = context.repo.repo; + if (commentRepo) { + const parts = commentRepo.split("/"); + if (parts.length === 2) { + repoOwner = parts[0]; + repoName = parts[1]; + } else { + core.warning(`Invalid comment repo format: ${commentRepo}, expected "owner/repo". Falling back to context.repo.`); + } + } + core.info(`Updating comment in ${repoOwner}/${repoName}`); + const prLinkMessage = `\n\n✅ Pull request created: [#${pullRequestNumber}](${pullRequestUrl})`; + const isDiscussionComment = commentId.startsWith("DC_"); + try { + if (isDiscussionComment) { + const currentComment = await github.graphql( + ` + query($commentId: ID!) { + node(id: $commentId) { + ... on DiscussionComment { + body + } + } + }`, + { commentId: commentId } + ); + if (!currentComment?.node?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted or is inaccessible"); + return; + } + const currentBody = currentComment.node.body; + const updatedBody = currentBody + prLinkMessage; + const result = await github.graphql( + ` + mutation($commentId: ID!, $body: String!) { + updateDiscussionComment(input: { commentId: $commentId, body: $body }) { + comment { + id + url + } + } + }`, + { commentId: commentId, body: updatedBody } + ); + const comment = result.updateDiscussionComment.comment; + core.info(`Successfully updated discussion comment with PR link`); + core.info(`Comment ID: ${comment.id}`); + core.info(`Comment URL: ${comment.url}`); + } else { + const currentComment = await github.request("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + headers: { + Accept: "application/vnd.github+json", + }, + }); + if (!currentComment?.data?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted"); + return; + } + const currentBody = currentComment.data.body; + const updatedBody = currentBody + prLinkMessage; + const response = await github.request("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + body: updatedBody, + headers: { + Accept: "application/vnd.github+json", + }, + }); + core.info(`Successfully updated comment with PR link`); + core.info(`Comment ID: ${response.data.id}`); + core.info(`Comment URL: ${response.data.html_url}`); + } + } catch (error) { + core.warning(`Failed to update activation comment: ${error instanceof Error ? error.message : String(error)}`); + } + } + updateActivationComment, + }; function generatePatchPreview(patchContent) { if (!patchContent || !patchContent.trim()) { return ""; @@ -4613,6 +4706,7 @@ jobs: core.setOutput("pull_request_number", pullRequest.number); core.setOutput("pull_request_url", pullRequest.html_url); core.setOutput("branch_name", branchName); + await updateActivationComment(github, context, core, pullRequest.html_url, pullRequest.number); await core.summary .addRaw( ` diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 166dfe49df..65a64775ee 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -4720,11 +4720,104 @@ jobs: GH_AW_PR_DRAFT: "true" GH_AW_PR_IF_NO_CHANGES: "warn" GH_AW_MAX_PATCH_SIZE: 1024 + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | const fs = require("fs"); const crypto = require("crypto"); + async function updateActivationComment(github, context, core, pullRequestUrl, pullRequestNumber) { + const commentId = process.env.GH_AW_COMMENT_ID; + const commentRepo = process.env.GH_AW_COMMENT_REPO; + if (!commentId) { + core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); + return; + } + core.info(`Updating activation comment ${commentId} with PR link`); + let repoOwner = context.repo.owner; + let repoName = context.repo.repo; + if (commentRepo) { + const parts = commentRepo.split("/"); + if (parts.length === 2) { + repoOwner = parts[0]; + repoName = parts[1]; + } else { + core.warning(`Invalid comment repo format: ${commentRepo}, expected "owner/repo". Falling back to context.repo.`); + } + } + core.info(`Updating comment in ${repoOwner}/${repoName}`); + const prLinkMessage = `\n\n✅ Pull request created: [#${pullRequestNumber}](${pullRequestUrl})`; + const isDiscussionComment = commentId.startsWith("DC_"); + try { + if (isDiscussionComment) { + const currentComment = await github.graphql( + ` + query($commentId: ID!) { + node(id: $commentId) { + ... on DiscussionComment { + body + } + } + }`, + { commentId: commentId } + ); + if (!currentComment?.node?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted or is inaccessible"); + return; + } + const currentBody = currentComment.node.body; + const updatedBody = currentBody + prLinkMessage; + const result = await github.graphql( + ` + mutation($commentId: ID!, $body: String!) { + updateDiscussionComment(input: { commentId: $commentId, body: $body }) { + comment { + id + url + } + } + }`, + { commentId: commentId, body: updatedBody } + ); + const comment = result.updateDiscussionComment.comment; + core.info(`Successfully updated discussion comment with PR link`); + core.info(`Comment ID: ${comment.id}`); + core.info(`Comment URL: ${comment.url}`); + } else { + const currentComment = await github.request("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + headers: { + Accept: "application/vnd.github+json", + }, + }); + if (!currentComment?.data?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted"); + return; + } + const currentBody = currentComment.data.body; + const updatedBody = currentBody + prLinkMessage; + const response = await github.request("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + body: updatedBody, + headers: { + Accept: "application/vnd.github+json", + }, + }); + core.info(`Successfully updated comment with PR link`); + core.info(`Comment ID: ${response.data.id}`); + core.info(`Comment URL: ${response.data.html_url}`); + } + } catch (error) { + core.warning(`Failed to update activation comment: ${error instanceof Error ? error.message : String(error)}`); + } + } + updateActivationComment, + }; function generatePatchPreview(patchContent) { if (!patchContent || !patchContent.trim()) { return ""; @@ -5094,6 +5187,7 @@ jobs: core.setOutput("pull_request_number", pullRequest.number); core.setOutput("pull_request_url", pullRequest.html_url); core.setOutput("branch_name", branchName); + await updateActivationComment(github, context, core, pullRequest.html_url, pullRequest.number); await core.summary .addRaw( ` diff --git a/docs/src/content/docs/status.mdx b/docs/src/content/docs/status.mdx index 554b69f91d..3a9e124766 100644 --- a/docs/src/content/docs/status.mdx +++ b/docs/src/content/docs/status.mdx @@ -66,7 +66,7 @@ Status of all agentic workflows. [Browse source files](https://github.com/github | [Smoke Copilot](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/smoke-copilot.md) | copilot | [![Smoke Copilot](https://github.com/githubnext/gh-aw/actions/workflows/smoke-copilot.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/smoke-copilot.lock.yml) | `0 0,6,12,18 * * *` | - | | [Smoke Detector - Smoke Test Failure Investigator](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/smoke-detector.md) | claude | [![Smoke Detector - Smoke Test Failure Investigator](https://github.com/githubnext/gh-aw/actions/workflows/smoke-detector.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/smoke-detector.lock.yml) | - | - | | [Static Analysis Report](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/static-analysis-report.md) | claude | [![Static Analysis Report](https://github.com/githubnext/gh-aw/actions/workflows/static-analysis-report.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/static-analysis-report.lock.yml) | `0 9 * * *` | - | -| [Super Linter Report](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/super-linter-report.md) | copilot | [![Super Linter Report](https://github.com/githubnext/gh-aw/actions/workflows/super-linter-report.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/super-linter-report.lock.yml) | `0 14 * * 1-5` | - | +| [Super Linter Report](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/super-linter.md) | copilot | [![Super Linter Report](https://github.com/githubnext/gh-aw/actions/workflows/super-linter.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/super-linter.lock.yml) | `0 14 * * 1-5` | - | | [Technical Doc Writer](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/technical-doc-writer.md) | copilot | [![Technical Doc Writer](https://github.com/githubnext/gh-aw/actions/workflows/technical-doc-writer.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/technical-doc-writer.lock.yml) | - | - | | [Test jqschema](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/test-jqschema.md) | copilot | [![Test jqschema](https://github.com/githubnext/gh-aw/actions/workflows/test-jqschema.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/test-jqschema.lock.yml) | - | - | | [Test Manual Approval Workflow](https://github.com/githubnext/gh-aw/blob/main/.github/workflows/test-manual-approval.md) | copilot | [![Test Manual Approval Workflow](https://github.com/githubnext/gh-aw/actions/workflows/test-manual-approval.lock.yml/badge.svg)](https://github.com/githubnext/gh-aw/actions/workflows/test-manual-approval.lock.yml) | - | - | diff --git a/pkg/workflow/create_pull_request.go b/pkg/workflow/create_pull_request.go index f1816d579c..f691405993 100644 --- a/pkg/workflow/create_pull_request.go +++ b/pkg/workflow/create_pull_request.go @@ -3,6 +3,8 @@ package workflow import ( "fmt" "strings" + + "github.com/githubnext/gh-aw/pkg/constants" ) // CreatePullRequestsConfig holds configuration for creating GitHub pull requests from agent output @@ -75,6 +77,10 @@ func (c *Compiler) buildCreateOutputPullRequestJob(data *WorkflowData, mainJobNa } customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_MAX_PATCH_SIZE: %d\n", maxPatchSize)) + // Pass activation comment information if available (for updating the comment with PR link) + customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_COMMENT_ID: ${{ needs.%s.outputs.comment_id }}\n", constants.ActivationJobName)) + customEnvVars = append(customEnvVars, fmt.Sprintf(" GH_AW_COMMENT_REPO: ${{ needs.%s.outputs.comment_repo }}\n", constants.ActivationJobName)) + // Add common safe output job environment variables (staged/target repo) customEnvVars = append(customEnvVars, buildSafeOutputJobEnvVars( c.trialMode, @@ -120,7 +126,7 @@ func (c *Compiler) buildCreateOutputPullRequestJob(data *WorkflowData, mainJobNa StepID: "create_pull_request", MainJobName: mainJobName, CustomEnvVars: customEnvVars, - Script: createPullRequestScript, + Script: getCreatePullRequestScript(), Permissions: NewPermissionsContentsWriteIssuesWritePRWrite(), Outputs: outputs, PreSteps: preSteps, diff --git a/pkg/workflow/js.go b/pkg/workflow/js.go index 8c77f5b1c7..c10d06b278 100644 --- a/pkg/workflow/js.go +++ b/pkg/workflow/js.go @@ -6,9 +6,6 @@ import ( "strings" ) -//go:embed js/create_pull_request.cjs -var createPullRequestScript string - //go:embed js/create_agent_task.cjs var createAgentTaskScript string @@ -72,17 +69,21 @@ var stagedPreviewScript string //go:embed js/is_truthy.cjs var isTruthyScript string +//go:embed js/update_activation_comment.cjs +var updateActivationCommentScript string + // GetJavaScriptSources returns a map of all embedded JavaScript sources // The keys are the relative paths from the js directory func GetJavaScriptSources() map[string]string { return map[string]string{ - "sanitize_content.cjs": sanitizeContentScript, - "sanitize_label_content.cjs": sanitizeLabelContentScript, - "sanitize_workflow_name.cjs": sanitizeWorkflowNameScript, - "load_agent_output.cjs": loadAgentOutputScript, - "staged_preview.cjs": stagedPreviewScript, - "is_truthy.cjs": isTruthyScript, - "log_parser_bootstrap.cjs": logParserBootstrapScript, + "sanitize_content.cjs": sanitizeContentScript, + "sanitize_label_content.cjs": sanitizeLabelContentScript, + "sanitize_workflow_name.cjs": sanitizeWorkflowNameScript, + "load_agent_output.cjs": loadAgentOutputScript, + "staged_preview.cjs": stagedPreviewScript, + "is_truthy.cjs": isTruthyScript, + "log_parser_bootstrap.cjs": logParserBootstrapScript, + "update_activation_comment.cjs": updateActivationCommentScript, } } diff --git a/pkg/workflow/js/create_pull_request.cjs b/pkg/workflow/js/create_pull_request.cjs index 55ae2efdd5..e249f07530 100644 --- a/pkg/workflow/js/create_pull_request.cjs +++ b/pkg/workflow/js/create_pull_request.cjs @@ -5,6 +5,7 @@ const fs = require("fs"); /** @type {typeof import("crypto")} */ const crypto = require("crypto"); +const { updateActivationComment } = require("./update_activation_comment.cjs"); /** * Generate a patch preview with max 500 lines and 2000 chars for issue body @@ -517,6 +518,9 @@ ${patchPreview}`; core.setOutput("pull_request_url", pullRequest.html_url); core.setOutput("branch_name", branchName); + // Update the activation comment with PR link (if a comment was created) + await updateActivationComment(github, context, core, pullRequest.html_url, pullRequest.number); + // Write summary to GitHub Actions summary await core.summary .addRaw( diff --git a/pkg/workflow/js/create_pull_request.test.cjs b/pkg/workflow/js/create_pull_request.test.cjs index aab963960f..bd29edc267 100644 --- a/pkg/workflow/js/create_pull_request.test.cjs +++ b/pkg/workflow/js/create_pull_request.test.cjs @@ -15,10 +15,11 @@ const createTestableFunction = scriptContent => { // Remove const declarations for fs and crypto since they'll be provided as parameters scriptBody = scriptBody.replace(/\/\*\* @type \{typeof import\("fs"\)\} \*\/\s*const fs = require\("fs"\);?\s*/g, ""); scriptBody = scriptBody.replace(/\/\*\* @type \{typeof import\("crypto"\)\} \*\/\s*const crypto = require\("crypto"\);?\s*/g, ""); + scriptBody = scriptBody.replace(/const \{ updateActivationComment \} = require\("\.\/update_activation_comment\.cjs"\);?\s*/g, ""); // Create a testable function that has the same logic but can be called with dependencies return new Function(` - const { fs, crypto, github, core, context, process, console } = arguments[0]; + const { fs, crypto, github, core, context, process, console, updateActivationComment } = arguments[0]; ${scriptBody} @@ -154,6 +155,7 @@ describe("create_pull_request.cjs", () => { console: { log: vi.fn(), }, + updateActivationComment: vi.fn(), }; }); @@ -1598,4 +1600,165 @@ describe("create_pull_request.cjs", () => { expect(mockDependencies.core.setFailed).toHaveBeenCalledWith("Failed to apply patch"); }); }); + + describe("activation comment update", () => { + it("should update activation comment with PR link when comment_id is provided", async () => { + mockDependencies.process.env.GH_AW_WORKFLOW_ID = "test-workflow"; + mockDependencies.process.env.GH_AW_BASE_BRANCH = "main"; + mockDependencies.process.env.GH_AW_COMMENT_ID = "123456"; + mockDependencies.process.env.GH_AW_COMMENT_REPO = "testowner/testrepo"; + mockDependencies.process.env.GH_AW_AGENT_OUTPUT = JSON.stringify({ + items: [ + { + type: "create_pull_request", + title: "Test PR", + body: "Test PR body", + }, + ], + }); + + const mockPullRequest = { + number: 42, + html_url: "https://github.com/testowner/testrepo/pull/42", + }; + + mockDependencies.github.rest.pulls.create.mockResolvedValue({ + data: mockPullRequest, + }); + + const mainFunction = createMainFunction(mockDependencies); + await mainFunction(); + + // Verify PR was created + expect(mockDependencies.github.rest.pulls.create).toHaveBeenCalled(); + + // Verify updateActivationComment was called with correct parameters + expect(mockDependencies.updateActivationComment).toHaveBeenCalledWith( + mockDependencies.github, + mockDependencies.context, + mockDependencies.core, + "https://github.com/testowner/testrepo/pull/42", + 42 + ); + }); + + it("should update discussion comment with PR link when comment_id starts with DC_", async () => { + mockDependencies.process.env.GH_AW_WORKFLOW_ID = "test-workflow"; + mockDependencies.process.env.GH_AW_BASE_BRANCH = "main"; + mockDependencies.process.env.GH_AW_COMMENT_ID = "DC_kwDOABCDEF4ABCDEF"; + mockDependencies.process.env.GH_AW_COMMENT_REPO = "testowner/testrepo"; + mockDependencies.process.env.GH_AW_AGENT_OUTPUT = JSON.stringify({ + items: [ + { + type: "create_pull_request", + title: "Test PR", + body: "Test PR body", + }, + ], + }); + + const mockPullRequest = { + number: 42, + html_url: "https://github.com/testowner/testrepo/pull/42", + }; + + mockDependencies.github.rest.pulls.create.mockResolvedValue({ + data: mockPullRequest, + }); + + const mainFunction = createMainFunction(mockDependencies); + await mainFunction(); + + // Verify PR was created + expect(mockDependencies.github.rest.pulls.create).toHaveBeenCalled(); + + // Verify updateActivationComment was called with correct parameters + expect(mockDependencies.updateActivationComment).toHaveBeenCalledWith( + mockDependencies.github, + mockDependencies.context, + mockDependencies.core, + "https://github.com/testowner/testrepo/pull/42", + 42 + ); + }); + + it("should skip updating comment when GH_AW_COMMENT_ID is not set", async () => { + mockDependencies.process.env.GH_AW_WORKFLOW_ID = "test-workflow"; + mockDependencies.process.env.GH_AW_BASE_BRANCH = "main"; + // No GH_AW_COMMENT_ID set + mockDependencies.process.env.GH_AW_AGENT_OUTPUT = JSON.stringify({ + items: [ + { + type: "create_pull_request", + title: "Test PR", + body: "Test PR body", + }, + ], + }); + + const mockPullRequest = { + number: 42, + html_url: "https://github.com/testowner/testrepo/pull/42", + }; + + mockDependencies.github.rest.pulls.create.mockResolvedValue({ + data: mockPullRequest, + }); + + const mainFunction = createMainFunction(mockDependencies); + await mainFunction(); + + // Verify PR was created + expect(mockDependencies.github.rest.pulls.create).toHaveBeenCalled(); + + // Verify updateActivationComment was still called (it will check internally if comment_id is set) + expect(mockDependencies.updateActivationComment).toHaveBeenCalledWith( + mockDependencies.github, + mockDependencies.context, + mockDependencies.core, + "https://github.com/testowner/testrepo/pull/42", + 42 + ); + }); + + it("should not fail workflow if comment update fails", async () => { + mockDependencies.process.env.GH_AW_WORKFLOW_ID = "test-workflow"; + mockDependencies.process.env.GH_AW_BASE_BRANCH = "main"; + mockDependencies.process.env.GH_AW_COMMENT_ID = "123456"; + mockDependencies.process.env.GH_AW_COMMENT_REPO = "testowner/testrepo"; + mockDependencies.process.env.GH_AW_AGENT_OUTPUT = JSON.stringify({ + items: [ + { + type: "create_pull_request", + title: "Test PR", + body: "Test PR body", + }, + ], + }); + + const mockPullRequest = { + number: 42, + html_url: "https://github.com/testowner/testrepo/pull/42", + }; + + mockDependencies.github.rest.pulls.create.mockResolvedValue({ + data: mockPullRequest, + }); + + const mainFunction = createMainFunction(mockDependencies); + await mainFunction(); + + // Verify PR was created successfully + expect(mockDependencies.github.rest.pulls.create).toHaveBeenCalled(); + + // Verify updateActivationComment was called (error handling is tested in update_activation_comment.test.cjs) + expect(mockDependencies.updateActivationComment).toHaveBeenCalledWith( + mockDependencies.github, + mockDependencies.context, + mockDependencies.core, + "https://github.com/testowner/testrepo/pull/42", + 42 + ); + }); + }); }); diff --git a/pkg/workflow/js/update_activation_comment.cjs b/pkg/workflow/js/update_activation_comment.cjs new file mode 100644 index 0000000000..ae21a7a8a2 --- /dev/null +++ b/pkg/workflow/js/update_activation_comment.cjs @@ -0,0 +1,126 @@ +// @ts-check +/// + +/** + * Update the activation comment with a link to the created pull request + * @param {any} github - GitHub REST API instance + * @param {any} context - GitHub Actions context + * @param {any} core - GitHub Actions core + * @param {string} pullRequestUrl - URL of the created pull request + * @param {number} pullRequestNumber - Number of the pull request + */ +async function updateActivationComment(github, context, core, pullRequestUrl, pullRequestNumber) { + const commentId = process.env.GH_AW_COMMENT_ID; + const commentRepo = process.env.GH_AW_COMMENT_REPO; + + // If no comment was created in activation, skip updating + if (!commentId) { + core.info("No activation comment to update (GH_AW_COMMENT_ID not set)"); + return; + } + + core.info(`Updating activation comment ${commentId} with PR link`); + + // Parse comment repo (format: "owner/repo") with validation + let repoOwner = context.repo.owner; + let repoName = context.repo.repo; + if (commentRepo) { + const parts = commentRepo.split("/"); + if (parts.length === 2) { + repoOwner = parts[0]; + repoName = parts[1]; + } else { + core.warning(`Invalid comment repo format: ${commentRepo}, expected "owner/repo". Falling back to context.repo.`); + } + } + + core.info(`Updating comment in ${repoOwner}/${repoName}`); + + // Prepare the message to append + const prLinkMessage = `\n\n✅ Pull request created: [#${pullRequestNumber}](${pullRequestUrl})`; + + // Check if this is a discussion comment (GraphQL node ID format) + const isDiscussionComment = commentId.startsWith("DC_"); + + try { + if (isDiscussionComment) { + // Get current comment body using GraphQL + const currentComment = await github.graphql( + ` + query($commentId: ID!) { + node(id: $commentId) { + ... on DiscussionComment { + body + } + } + }`, + { commentId: commentId } + ); + + if (!currentComment?.node?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted or is inaccessible"); + return; + } + const currentBody = currentComment.node.body; + const updatedBody = currentBody + prLinkMessage; + + // Update discussion comment using GraphQL + const result = await github.graphql( + ` + mutation($commentId: ID!, $body: String!) { + updateDiscussionComment(input: { commentId: $commentId, body: $body }) { + comment { + id + url + } + } + }`, + { commentId: commentId, body: updatedBody } + ); + + const comment = result.updateDiscussionComment.comment; + core.info(`Successfully updated discussion comment with PR link`); + core.info(`Comment ID: ${comment.id}`); + core.info(`Comment URL: ${comment.url}`); + } else { + // Get current comment body using REST API + const currentComment = await github.request("GET /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + headers: { + Accept: "application/vnd.github+json", + }, + }); + + if (!currentComment?.data?.body) { + core.warning("Unable to fetch current comment body, comment may have been deleted"); + return; + } + const currentBody = currentComment.data.body; + const updatedBody = currentBody + prLinkMessage; + + // Update issue/PR comment using REST API + const response = await github.request("PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", { + owner: repoOwner, + repo: repoName, + comment_id: parseInt(commentId, 10), + body: updatedBody, + headers: { + Accept: "application/vnd.github+json", + }, + }); + + core.info(`Successfully updated comment with PR link`); + core.info(`Comment ID: ${response.data.id}`); + core.info(`Comment URL: ${response.data.html_url}`); + } + } catch (error) { + // Don't fail the workflow if we can't update the comment - just log a warning + core.warning(`Failed to update activation comment: ${error instanceof Error ? error.message : String(error)}`); + } +} + +module.exports = { + updateActivationComment, +}; diff --git a/pkg/workflow/js/update_activation_comment.test.cjs b/pkg/workflow/js/update_activation_comment.test.cjs new file mode 100644 index 0000000000..e681664308 --- /dev/null +++ b/pkg/workflow/js/update_activation_comment.test.cjs @@ -0,0 +1,416 @@ +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { readFileSync } from "fs"; +import path from "path"; + +// Create testable function +const createTestableFunction = scriptContent => { + const beforeMainCall = scriptContent.match(/^([\s\S]*?)\s*module\.exports\s*=\s*{[\s\S]*?};?\s*$/); + if (!beforeMainCall) { + throw new Error("Could not extract script content before module.exports"); + } + + let scriptBody = beforeMainCall[1]; + + return new Function(` + const { github, core, context, process } = arguments[0]; + + ${scriptBody} + + return { updateActivationComment }; + `); +}; + +describe("update_activation_comment.cjs", () => { + let createFunctionFromScript; + let mockDependencies; + + beforeEach(() => { + // Read the script content + const scriptPath = path.join(process.cwd(), "update_activation_comment.cjs"); + const scriptContent = readFileSync(scriptPath, "utf8"); + + // Create testable function + createFunctionFromScript = createTestableFunction(scriptContent); + + // Set up mock dependencies + mockDependencies = { + github: { + graphql: vi.fn(), + request: vi.fn(), + }, + core: { + info: vi.fn(), + warning: vi.fn(), + setFailed: vi.fn(), + }, + context: { + repo: { + owner: "testowner", + repo: "testrepo", + }, + }, + process: { + env: {}, + }, + }; + }); + + it("should skip update when GH_AW_COMMENT_ID is not set", async () => { + mockDependencies.process.env.GH_AW_COMMENT_ID = ""; + + const { updateActivationComment } = createFunctionFromScript(mockDependencies); + + await updateActivationComment( + mockDependencies.github, + mockDependencies.context, + mockDependencies.core, + "https://github.com/testowner/testrepo/pull/42", + 42 + ); + + expect(mockDependencies.core.info).toHaveBeenCalledWith("No activation comment to update (GH_AW_COMMENT_ID not set)"); + expect(mockDependencies.github.request).not.toHaveBeenCalled(); + }); + + it("should update issue comment with PR link", async () => { + mockDependencies.process.env.GH_AW_COMMENT_ID = "123456"; + mockDependencies.process.env.GH_AW_COMMENT_REPO = "testowner/testrepo"; + + // Mock GET request to fetch current comment + mockDependencies.github.request.mockImplementation(async (method, params) => { + if (method.startsWith("GET")) { + return { + data: { + body: "Agentic [workflow](https://github.com/testowner/testrepo/actions/runs/12345) triggered by this issue.", + }, + }; + } + // Mock PATCH request to update comment + if (method.startsWith("PATCH")) { + return { + data: { + id: 123456, + html_url: "https://github.com/testowner/testrepo/issues/1#issuecomment-123456", + }, + }; + } + return { data: {} }; + }); + + const { updateActivationComment } = createFunctionFromScript(mockDependencies); + + await updateActivationComment( + mockDependencies.github, + mockDependencies.context, + mockDependencies.core, + "https://github.com/testowner/testrepo/pull/42", + 42 + ); + + // Verify comment was fetched + expect(mockDependencies.github.request).toHaveBeenCalledWith( + "GET /repos/{owner}/{repo}/issues/comments/{comment_id}", + expect.objectContaining({ + owner: "testowner", + repo: "testrepo", + comment_id: 123456, + }) + ); + + // Verify comment was updated with PR link + expect(mockDependencies.github.request).toHaveBeenCalledWith( + "PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", + expect.objectContaining({ + owner: "testowner", + repo: "testrepo", + comment_id: 123456, + body: expect.stringContaining("✅ Pull request created: [#42](https://github.com/testowner/testrepo/pull/42)"), + }) + ); + + expect(mockDependencies.core.info).toHaveBeenCalledWith("Successfully updated comment with PR link"); + }); + + it("should update discussion comment with PR link using GraphQL", async () => { + mockDependencies.process.env.GH_AW_COMMENT_ID = "DC_kwDOABCDEF4ABCDEF"; + mockDependencies.process.env.GH_AW_COMMENT_REPO = "testowner/testrepo"; + + // Mock GraphQL for discussion comment + mockDependencies.github.graphql.mockImplementation(async (query, params) => { + if (query.includes("query")) { + // Mock GET query + return { + node: { + body: "Agentic [workflow](https://github.com/testowner/testrepo/actions/runs/12345) triggered by this discussion.", + }, + }; + } + if (query.includes("mutation")) { + // Mock UPDATE mutation + return { + updateDiscussionComment: { + comment: { + id: "DC_kwDOABCDEF4ABCDEF", + url: "https://github.com/testowner/testrepo/discussions/1#discussioncomment-123456", + }, + }, + }; + } + return {}; + }); + + const { updateActivationComment } = createFunctionFromScript(mockDependencies); + + await updateActivationComment( + mockDependencies.github, + mockDependencies.context, + mockDependencies.core, + "https://github.com/testowner/testrepo/pull/42", + 42 + ); + + // Verify GraphQL was called to get current comment + expect(mockDependencies.github.graphql).toHaveBeenCalledWith( + expect.stringContaining("query($commentId: ID!)"), + expect.objectContaining({ + commentId: "DC_kwDOABCDEF4ABCDEF", + }) + ); + + // Verify GraphQL was called to update comment + expect(mockDependencies.github.graphql).toHaveBeenCalledWith( + expect.stringContaining("mutation($commentId: ID!, $body: String!)"), + expect.objectContaining({ + commentId: "DC_kwDOABCDEF4ABCDEF", + body: expect.stringContaining("✅ Pull request created: [#42](https://github.com/testowner/testrepo/pull/42)"), + }) + ); + + expect(mockDependencies.core.info).toHaveBeenCalledWith("Successfully updated discussion comment with PR link"); + }); + + it("should not fail workflow if comment update fails", async () => { + mockDependencies.process.env.GH_AW_COMMENT_ID = "123456"; + mockDependencies.process.env.GH_AW_COMMENT_REPO = "testowner/testrepo"; + + // Mock request to fail + mockDependencies.github.request.mockRejectedValue(new Error("Comment update failed")); + + const { updateActivationComment } = createFunctionFromScript(mockDependencies); + + await updateActivationComment( + mockDependencies.github, + mockDependencies.context, + mockDependencies.core, + "https://github.com/testowner/testrepo/pull/42", + 42 + ); + + // Verify warning was logged but workflow didn't fail + expect(mockDependencies.core.warning).toHaveBeenCalledWith("Failed to update activation comment: Comment update failed"); + expect(mockDependencies.core.setFailed).not.toHaveBeenCalled(); + }); + + it("should use default repo from context if comment_repo not set", async () => { + mockDependencies.process.env.GH_AW_COMMENT_ID = "123456"; + // GH_AW_COMMENT_REPO not set + + mockDependencies.github.request.mockImplementation(async (method, params) => { + if (method.startsWith("GET")) { + return { + data: { + body: "Original comment", + }, + }; + } + if (method.startsWith("PATCH")) { + return { + data: { + id: 123456, + html_url: "https://github.com/testowner/testrepo/issues/1#issuecomment-123456", + }, + }; + } + return { data: {} }; + }); + + const { updateActivationComment } = createFunctionFromScript(mockDependencies); + + await updateActivationComment( + mockDependencies.github, + mockDependencies.context, + mockDependencies.core, + "https://github.com/testowner/testrepo/pull/42", + 42 + ); + + // Verify request used context repo + expect(mockDependencies.github.request).toHaveBeenCalledWith( + "GET /repos/{owner}/{repo}/issues/comments/{comment_id}", + expect.objectContaining({ + owner: "testowner", + repo: "testrepo", + }) + ); + }); + + it("should handle invalid comment_repo format and fall back to context", async () => { + mockDependencies.process.env.GH_AW_COMMENT_ID = "123456"; + mockDependencies.process.env.GH_AW_COMMENT_REPO = "invalid-format"; + + mockDependencies.github.request.mockImplementation(async (method, params) => { + if (method.startsWith("GET")) { + return { + data: { + body: "Original comment", + }, + }; + } + if (method.startsWith("PATCH")) { + return { + data: { + id: 123456, + html_url: "https://github.com/testowner/testrepo/issues/1#issuecomment-123456", + }, + }; + } + return { data: {} }; + }); + + const { updateActivationComment } = createFunctionFromScript(mockDependencies); + + await updateActivationComment( + mockDependencies.github, + mockDependencies.context, + mockDependencies.core, + "https://github.com/testowner/testrepo/pull/42", + 42 + ); + + // Verify warning was logged + expect(mockDependencies.core.warning).toHaveBeenCalledWith( + 'Invalid comment repo format: invalid-format, expected "owner/repo". Falling back to context.repo.' + ); + + // Verify request used context repo as fallback + expect(mockDependencies.github.request).toHaveBeenCalledWith( + "GET /repos/{owner}/{repo}/issues/comments/{comment_id}", + expect.objectContaining({ + owner: "testowner", + repo: "testrepo", + }) + ); + }); + + it("should handle deleted discussion comment (null body in GraphQL)", async () => { + mockDependencies.process.env.GH_AW_COMMENT_ID = "DC_kwDOABCDEF4ABCDEF"; + mockDependencies.process.env.GH_AW_COMMENT_REPO = "testowner/testrepo"; + + // Mock GraphQL to return null body (comment deleted) + mockDependencies.github.graphql.mockResolvedValue({ + node: { + body: null, + }, + }); + + const { updateActivationComment } = createFunctionFromScript(mockDependencies); + + await updateActivationComment( + mockDependencies.github, + mockDependencies.context, + mockDependencies.core, + "https://github.com/testowner/testrepo/pull/42", + 42 + ); + + // Verify warning was logged + expect(mockDependencies.core.warning).toHaveBeenCalledWith( + "Unable to fetch current comment body, comment may have been deleted or is inaccessible" + ); + + // Verify mutation was not attempted + expect(mockDependencies.github.graphql).toHaveBeenCalledTimes(1); + }); + + it("should handle deleted discussion comment (null node in GraphQL)", async () => { + mockDependencies.process.env.GH_AW_COMMENT_ID = "DC_kwDOABCDEF4ABCDEF"; + mockDependencies.process.env.GH_AW_COMMENT_REPO = "testowner/testrepo"; + + // Mock GraphQL to return null node (comment deleted) + mockDependencies.github.graphql.mockResolvedValue({ + node: null, + }); + + const { updateActivationComment } = createFunctionFromScript(mockDependencies); + + await updateActivationComment( + mockDependencies.github, + mockDependencies.context, + mockDependencies.core, + "https://github.com/testowner/testrepo/pull/42", + 42 + ); + + // Verify warning was logged + expect(mockDependencies.core.warning).toHaveBeenCalledWith( + "Unable to fetch current comment body, comment may have been deleted or is inaccessible" + ); + + // Verify mutation was not attempted + expect(mockDependencies.github.graphql).toHaveBeenCalledTimes(1); + }); + + it("should handle deleted issue comment (null body in REST API)", async () => { + mockDependencies.process.env.GH_AW_COMMENT_ID = "123456"; + mockDependencies.process.env.GH_AW_COMMENT_REPO = "testowner/testrepo"; + + // Mock REST API to return null body (comment deleted) + mockDependencies.github.request.mockResolvedValue({ + data: { + body: null, + }, + }); + + const { updateActivationComment } = createFunctionFromScript(mockDependencies); + + await updateActivationComment( + mockDependencies.github, + mockDependencies.context, + mockDependencies.core, + "https://github.com/testowner/testrepo/pull/42", + 42 + ); + + // Verify warning was logged + expect(mockDependencies.core.warning).toHaveBeenCalledWith("Unable to fetch current comment body, comment may have been deleted"); + + // Verify PATCH was not attempted + expect(mockDependencies.github.request).toHaveBeenCalledTimes(1); + }); + + it("should handle deleted issue comment (undefined body in REST API)", async () => { + mockDependencies.process.env.GH_AW_COMMENT_ID = "123456"; + mockDependencies.process.env.GH_AW_COMMENT_REPO = "testowner/testrepo"; + + // Mock REST API to return undefined body (comment deleted) + mockDependencies.github.request.mockResolvedValue({ + data: {}, + }); + + const { updateActivationComment } = createFunctionFromScript(mockDependencies); + + await updateActivationComment( + mockDependencies.github, + mockDependencies.context, + mockDependencies.core, + "https://github.com/testowner/testrepo/pull/42", + 42 + ); + + // Verify warning was logged + expect(mockDependencies.core.warning).toHaveBeenCalledWith("Unable to fetch current comment body, comment may have been deleted"); + + // Verify PATCH was not attempted + expect(mockDependencies.github.request).toHaveBeenCalledTimes(1); + }); +}); diff --git a/pkg/workflow/js_test.go b/pkg/workflow/js_test.go index 1384973c44..29d8680474 100644 --- a/pkg/workflow/js_test.go +++ b/pkg/workflow/js_test.go @@ -237,7 +237,7 @@ func TestEmbeddedScriptsNotEmpty(t *testing.T) { name string script string }{ - {"createPullRequestScript", createPullRequestScript}, + {"createPullRequestScript", getCreatePullRequestScript()}, {"createIssueScript", getCreateIssueScript()}, {"createDiscussionScript", getCreateDiscussionScript()}, {"createCommentScript", getAddCommentScript()}, diff --git a/pkg/workflow/scripts.go b/pkg/workflow/scripts.go index 4e83a1ef97..35bdb60145 100644 --- a/pkg/workflow/scripts.go +++ b/pkg/workflow/scripts.go @@ -50,6 +50,9 @@ var parseFirewallLogsScriptSource string //go:embed js/push_to_pull_request_branch.cjs var pushToPullRequestBranchScriptSource string +//go:embed js/create_pull_request.cjs +var createPullRequestScriptSource string + // Log parser source scripts // //go:embed js/parse_claude_log.cjs @@ -102,6 +105,9 @@ var ( pushToPullRequestBranchScript string pushToPullRequestBranchScriptOnce sync.Once + createPullRequestScript string + createPullRequestScriptOnce sync.Once + interpolatePromptBundled string interpolatePromptBundledOnce sync.Once @@ -338,6 +344,23 @@ func getPushToPullRequestBranchScript() string { return pushToPullRequestBranchScript } +// getCreatePullRequestScript returns the bundled create_pull_request script +// Bundling is performed on first access and cached for subsequent calls +func getCreatePullRequestScript() string { + createPullRequestScriptOnce.Do(func() { + sources := GetJavaScriptSources() + bundled, err := BundleJavaScriptFromSources(createPullRequestScriptSource, sources, "") + if err != nil { + scriptsLog.Printf("Bundling failed for create_pull_request, using source as-is: %v", err) + // If bundling fails, use the source as-is + createPullRequestScript = createPullRequestScriptSource + } else { + createPullRequestScript = bundled + } + }) + return createPullRequestScript +} + // getInterpolatePromptScript returns the bundled interpolate_prompt script // Bundling is performed on first access and cached for subsequent calls // This bundles is_truthy.cjs inline to avoid require() issues in GitHub Actions