From c6a741621e7f0fa64078b8e41f989b529e49c9e2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 04:51:15 +0000 Subject: [PATCH 1/2] jsweep: Clean add_comment.cjs - Extract matchesWorkflowId() helper to eliminate duplicate comment filter logic shared between findCommentsWithTrackerId and findDiscussionCommentsWithTrackerId - Remove unused body and createdAt fields from GraphQL discussion comment mutations - Simplify variables construction with spread operator in commentOnDiscussion - Simplify resolvedTemporaryIds merge using nullish coalescing (??) - Add 6 new tests covering staged mode, max count, footer disabled, and hide-older-comments edge cases (no workflow ID, disabled by default) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- actions/setup/js/add_comment.cjs | 81 ++++++-------- actions/setup/js/add_comment.test.cjs | 147 ++++++++++++++++++++++++++ 2 files changed, 181 insertions(+), 47 deletions(-) diff --git a/actions/setup/js/add_comment.cjs b/actions/setup/js/add_comment.cjs index ef922ca42e2..10e6e8d166e 100644 --- a/actions/setup/js/add_comment.cjs +++ b/actions/setup/js/add_comment.cjs @@ -28,7 +28,19 @@ const { generateHistoryUrl } = require("./generate_history_link.cjs"); /** @type {string} Safe output type handled by this module */ const HANDLER_TYPE = "add_comment"; -// Copy helper functions from original file +/** + * Check if a comment body matches a workflow ID marker. + * Supports standalone () and combined XML marker formats. + * @param {string|null|undefined} body - Comment body + * @param {string} workflowId - Workflow ID to match + * @returns {boolean} + */ +function matchesWorkflowId(body, workflowId) { + if (!body || body.includes(``)) return false; + if (body.includes(``)) return true; + return body.includes(``)); +} + async function minimizeComment(github, nodeId, reason = "outdated") { const query = /* GraphQL */ ` mutation ($nodeId: ID!, $classifier: ReportedContentClassifiers!) { @@ -76,19 +88,7 @@ async function findCommentsWithTrackerId(github, owner, repo, issueNumber, workf break; } - // Filter comments that contain the workflow-id and are NOT reaction comments. - // Supports both the standalone marker format () - // and the combined XML marker format (). - const filteredComments = data - .filter(comment => { - if (!comment.body || comment.body.includes(``)) return false; - // Standalone marker: - if (comment.body.includes(``)) return true; - // Combined XML marker: - if (comment.body.includes(``))) return true; - return false; - }) - .map(({ id, node_id, body }) => ({ id, node_id, body })); + const filteredComments = data.filter(comment => matchesWorkflowId(comment.body, workflowId)).map(({ id, node_id, body }) => ({ id, node_id, body })); comments.push(...filteredComments); @@ -141,16 +141,7 @@ async function findDiscussionCommentsWithTrackerId(github, owner, repo, discussi break; } - const filteredComments = result.repository.discussion.comments.nodes - .filter(comment => { - if (!comment.body || comment.body.includes(``)) return false; - // Standalone marker: - if (comment.body.includes(``)) return true; - // Combined XML marker: - if (comment.body.includes(``))) return true; - return false; - }) - .map(({ id, body }) => ({ id, body })); + const filteredComments = result.repository.discussion.comments.nodes.filter(comment => matchesWorkflowId(comment.body, workflowId)).map(({ id, body }) => ({ id, body })); comments.push(...filteredComments); @@ -260,28 +251,28 @@ async function commentOnDiscussion(github, owner, repo, discussionNumber, messag // 2. Add comment (with optional replyToId for threading) const mutation = replyToId - ? `mutation($dId: ID!, $body: String!, $replyToId: ID!) { - addDiscussionComment(input: { discussionId: $dId, body: $body, replyToId: $replyToId }) { - comment { - id - body - createdAt - url + ? /* GraphQL */ ` + mutation ($dId: ID!, $body: String!, $replyToId: ID!) { + addDiscussionComment(input: { discussionId: $dId, body: $body, replyToId: $replyToId }) { + comment { + id + url + } } } - }` - : `mutation($dId: ID!, $body: String!) { - addDiscussionComment(input: { discussionId: $dId, body: $body }) { - comment { - id - body - createdAt - url + ` + : /* GraphQL */ ` + mutation ($dId: ID!, $body: String!) { + addDiscussionComment(input: { discussionId: $dId, body: $body }) { + comment { + id + url + } } } - }`; + `; - const variables = replyToId ? { dId: discussionId, body: message, replyToId } : { dId: discussionId, body: message }; + const variables = { dId: discussionId, body: message, ...(replyToId ? { replyToId } : {}) }; const result = await github.graphql(mutation, variables); @@ -358,12 +349,8 @@ async function main(config = {}) { processedCount++; // Merge resolved temp IDs - if (resolvedTemporaryIds) { - for (const [tempId, resolved] of Object.entries(resolvedTemporaryIds)) { - if (!temporaryIdMap.has(tempId)) { - temporaryIdMap.set(tempId, resolved); - } - } + for (const [tempId, resolved] of Object.entries(resolvedTemporaryIds ?? {})) { + if (!temporaryIdMap.has(tempId)) temporaryIdMap.set(tempId, resolved); } // Resolve and validate target repository diff --git a/actions/setup/js/add_comment.test.cjs b/actions/setup/js/add_comment.test.cjs index 5f6c58bc36a..16c468f4ab1 100644 --- a/actions/setup/js/add_comment.test.cjs +++ b/actions/setup/js/add_comment.test.cjs @@ -1854,6 +1854,153 @@ describe("add_comment", () => { }); }); + describe("staged mode", () => { + it("should return staged preview without creating comment", async () => { + const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); + + process.env.GH_AW_SAFE_OUTPUTS_STAGED = "true"; + + let createCommentCalled = false; + mockGithub.rest.issues.createComment = async () => { + createCommentCalled = true; + return { data: { id: 1, html_url: "https://github.com/owner/repo/issues/8535#issuecomment-1" } }; + }; + + const handler = await eval(`(async () => { ${addCommentScript}; return await main({}); })()`); + + const message = { type: "add_comment", body: "Staged comment preview" }; + const result = await handler(message, {}); + + expect(result.success).toBe(true); + expect(result.staged).toBe(true); + expect(result.previewInfo).toBeDefined(); + expect(result.previewInfo.itemNumber).toBe(8535); + expect(createCommentCalled).toBe(false); + + delete process.env.GH_AW_SAFE_OUTPUTS_STAGED; + }); + + it("should return staged preview for discussion when staged mode is set via config", async () => { + const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); + + mockContext.eventName = "discussion"; + mockContext.payload = { discussion: { number: 55 } }; + + let createCommentCalled = false; + mockGithub.graphql = async () => { + createCommentCalled = true; + return {}; + }; + + const handler = await eval(`(async () => { ${addCommentScript}; return await main({ staged: true }); })()`); + + const message = { type: "add_comment", body: "Staged discussion preview" }; + const result = await handler(message, {}); + + expect(result.success).toBe(true); + expect(result.staged).toBe(true); + expect(result.previewInfo.isDiscussion).toBe(true); + expect(result.previewInfo.itemNumber).toBe(55); + expect(createCommentCalled).toBe(false); + }); + }); + + describe("max count enforcement", () => { + it("should skip comments beyond max count", async () => { + const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); + + const handler = await eval(`(async () => { ${addCommentScript}; return await main({ max: 2 }); })()`); + + const message = { type: "add_comment", body: "Comment" }; + + // First two should succeed + const result1 = await handler(message, {}); + const result2 = await handler(message, {}); + // Third should be skipped + const result3 = await handler(message, {}); + + expect(result1.success).toBe(true); + expect(result2.success).toBe(true); + expect(result3.success).toBe(false); + expect(result3.error).toMatch(/max count/i); + }); + }); + + describe("footer configuration", () => { + it("should include only XML marker (no attribution text) when footer is disabled", async () => { + const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); + + process.env.GH_AW_WORKFLOW_NAME = "No-Footer Workflow"; + + let capturedBody = null; + mockGithub.rest.issues.createComment = async params => { + capturedBody = params.body; + return { data: { id: 1, html_url: "https://github.com/owner/repo/issues/8535#issuecomment-1" } }; + }; + + const handler = await eval(`(async () => { ${addCommentScript}; return await main({ footer: false }); })()`); + + const message = { type: "add_comment", body: "No footer comment" }; + const result = await handler(message, {}); + + expect(result.success).toBe(true); + expect(capturedBody).not.toContain("Generated by"); + expect(capturedBody).toContain(") and combined XML marker formats. - * @param {string|null|undefined} body - Comment body - * @param {string} workflowId - Workflow ID to match - * @returns {boolean} - */ -function matchesWorkflowId(body, workflowId) { - if (!body || body.includes(``)) return false; - if (body.includes(``)) return true; - return body.includes(``)); -} - async function minimizeComment(github, nodeId, reason = "outdated") { const query = /* GraphQL */ ` mutation ($nodeId: ID!, $classifier: ReportedContentClassifiers!) { diff --git a/actions/setup/js/add_comment.test.cjs b/actions/setup/js/add_comment.test.cjs index 16c468f4ab1..bfac328576f 100644 --- a/actions/setup/js/add_comment.test.cjs +++ b/actions/setup/js/add_comment.test.cjs @@ -1858,26 +1858,33 @@ describe("add_comment", () => { it("should return staged preview without creating comment", async () => { const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); + const originalStaged = process.env.GH_AW_SAFE_OUTPUTS_STAGED; process.env.GH_AW_SAFE_OUTPUTS_STAGED = "true"; - let createCommentCalled = false; - mockGithub.rest.issues.createComment = async () => { - createCommentCalled = true; - return { data: { id: 1, html_url: "https://github.com/owner/repo/issues/8535#issuecomment-1" } }; - }; - - const handler = await eval(`(async () => { ${addCommentScript}; return await main({}); })()`); - - const message = { type: "add_comment", body: "Staged comment preview" }; - const result = await handler(message, {}); - - expect(result.success).toBe(true); - expect(result.staged).toBe(true); - expect(result.previewInfo).toBeDefined(); - expect(result.previewInfo.itemNumber).toBe(8535); - expect(createCommentCalled).toBe(false); + try { + let createCommentCalled = false; + mockGithub.rest.issues.createComment = async () => { + createCommentCalled = true; + return { data: { id: 1, html_url: "https://github.com/owner/repo/issues/8535#issuecomment-1" } }; + }; - delete process.env.GH_AW_SAFE_OUTPUTS_STAGED; + const handler = await eval(`(async () => { ${addCommentScript}; return await main({}); })()`); + + const message = { type: "add_comment", body: "Staged comment preview" }; + const result = await handler(message, {}); + + expect(result.success).toBe(true); + expect(result.staged).toBe(true); + expect(result.previewInfo).toBeDefined(); + expect(result.previewInfo.itemNumber).toBe(8535); + expect(createCommentCalled).toBe(false); + } finally { + if (originalStaged === undefined) { + delete process.env.GH_AW_SAFE_OUTPUTS_STAGED; + } else { + process.env.GH_AW_SAFE_OUTPUTS_STAGED = originalStaged; + } + } }); it("should return staged preview for discussion when staged mode is set via config", async () => { @@ -1930,24 +1937,31 @@ describe("add_comment", () => { it("should include only XML marker (no attribution text) when footer is disabled", async () => { const addCommentScript = fs.readFileSync(path.join(__dirname, "add_comment.cjs"), "utf8"); + const originalWorkflowName = process.env.GH_AW_WORKFLOW_NAME; process.env.GH_AW_WORKFLOW_NAME = "No-Footer Workflow"; - let capturedBody = null; - mockGithub.rest.issues.createComment = async params => { - capturedBody = params.body; - return { data: { id: 1, html_url: "https://github.com/owner/repo/issues/8535#issuecomment-1" } }; - }; - - const handler = await eval(`(async () => { ${addCommentScript}; return await main({ footer: false }); })()`); + try { + let capturedBody = null; + mockGithub.rest.issues.createComment = async params => { + capturedBody = params.body; + return { data: { id: 1, html_url: "https://github.com/owner/repo/issues/8535#issuecomment-1" } }; + }; - const message = { type: "add_comment", body: "No footer comment" }; - const result = await handler(message, {}); + const handler = await eval(`(async () => { ${addCommentScript}; return await main({ footer: false }); })()`); - expect(result.success).toBe(true); - expect(capturedBody).not.toContain("Generated by"); - expect(capturedBody).toContain(") and combined XML marker formats. + * @param {string|null|undefined} body - Comment body + * @param {string} workflowId - Workflow ID to match + * @returns {boolean} + */ +function matchesWorkflowId(body, workflowId) { + if (!body || body.includes(``)) return false; + if (body.includes(``)) return true; + return body.includes(``)); +} + /** * Generates an XML comment marker with agentic workflow metadata for traceability. * This marker enables searching and tracing back items generated by an agentic workflow. @@ -170,6 +183,7 @@ module.exports = { generateWorkflowIdMarker, generateWorkflowCallIdMarker, getWorkflowIdMarkerContent, + matchesWorkflowId, generateExpiredEntityFooter, normalizeCloseOlderKey, generateCloseKeyMarker,