From 70a6ec5d7acc6c815924f8098575fc983df00cbc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Jul 2026 06:33:54 +0000 Subject: [PATCH 1/4] Initial plan From bd2a5597238ecbed1489b61dc964b8a44a37590e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Jul 2026 06:43:45 +0000 Subject: [PATCH 2/4] refactor: share reaction setup validation logic Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/add_reaction.cjs | 17 ++---- .../js/add_reaction_and_edit_comment.cjs | 17 ++---- actions/setup/js/reaction_setup_helpers.cjs | 25 ++++++++ .../setup/js/reaction_setup_helpers.test.cjs | 60 +++++++++++++++++++ 4 files changed, 97 insertions(+), 22 deletions(-) create mode 100644 actions/setup/js/reaction_setup_helpers.cjs create mode 100644 actions/setup/js/reaction_setup_helpers.test.cjs diff --git a/actions/setup/js/add_reaction.cjs b/actions/setup/js/add_reaction.cjs index ad857c6324e..0c368d9076c 100644 --- a/actions/setup/js/add_reaction.cjs +++ b/actions/setup/js/add_reaction.cjs @@ -3,7 +3,7 @@ const { getErrorMessage, isLockedError } = require("./error_helpers.cjs"); const { ERR_API, ERR_NOT_FOUND, ERR_VALIDATION } = require("./error_codes.cjs"); -const { resolveInvocationContext } = require("./invocation_context_helpers.cjs"); +const { resolveReactionSetup } = require("./reaction_setup_helpers.cjs"); /** @type {Record} Maps REST reaction names to GraphQL ReactionContent enum values */ const REACTION_MAP = { @@ -24,20 +24,15 @@ const REACTION_MAP = { * Use add_reaction_and_edit_comment.cjs in the activation job to create the comment with workflow link. */ async function main() { - // Read inputs from environment variables - const reaction = process.env.GH_AW_REACTION || "eyes"; - - core.info(`Adding reaction: ${reaction}`); - - // Validate reaction type - const validReactions = Object.keys(REACTION_MAP); - if (!validReactions.includes(reaction)) { - core.setFailed(`${ERR_VALIDATION}: Invalid reaction type: ${reaction}. Valid reactions are: ${validReactions.join(", ")}`); + const setup = resolveReactionSetup(context); + if (!setup) { return; } + const { reaction, invocationContext } = setup; + + core.info(`Adding reaction: ${reaction}`); // Determine the API endpoint based on the event type - const invocationContext = resolveInvocationContext(context); const eventName = invocationContext.eventName; const { owner, repo } = invocationContext.eventRepo; const payload = invocationContext.eventPayload; diff --git a/actions/setup/js/add_reaction_and_edit_comment.cjs b/actions/setup/js/add_reaction_and_edit_comment.cjs index fa00fedee60..431607be821 100644 --- a/actions/setup/js/add_reaction_and_edit_comment.cjs +++ b/actions/setup/js/add_reaction_and_edit_comment.cjs @@ -8,7 +8,7 @@ const { sanitizeContent } = require("./sanitize_content.cjs"); const { ERR_API, ERR_NOT_FOUND, ERR_VALIDATION } = require("./error_codes.cjs"); const { buildWorkflowRunUrl } = require("./workflow_metadata_helpers.cjs"); const { resolveTopLevelDiscussionCommentId } = require("./github_api_helpers.cjs"); -const { resolveInvocationContext } = require("./invocation_context_helpers.cjs"); +const { VALID_REACTIONS, resolveReactionSetup } = require("./reaction_setup_helpers.cjs"); const { addReaction, addDiscussionReaction, getDiscussionNodeId } = require("./add_reaction.cjs"); /** @@ -24,9 +24,6 @@ const EVENT_TYPE_DESCRIPTIONS = { discussion_comment: "discussion comment", }; -/** Valid GitHub reaction types */ -const VALID_REACTIONS = Object.freeze(["+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", "eyes"]); - /** * Resolve the reaction and comment API endpoints for a given event. * Returns null (after calling core.setFailed) when the event or payload is invalid. @@ -138,10 +135,13 @@ async function resolveEventEndpoints(eventName, owner, repo, payload) { } async function main() { - const reaction = process.env.GH_AW_REACTION || "eyes"; + const setup = resolveReactionSetup(context); + if (!setup) { + return; + } + const { reaction, invocationContext } = setup; const commandsJSON = process.env.GH_AW_COMMANDS; const command = commandsJSON ? (JSON.parse(commandsJSON)[0] ?? null) : null; // Only present for command workflows - const invocationContext = resolveInvocationContext(context); const runUrl = buildWorkflowRunUrl(context, invocationContext.workflowRepo); core.info(`Reaction type: ${reaction}`); @@ -149,11 +149,6 @@ async function main() { core.info(`Run ID: ${context.runId}`); core.info(`Run URL: ${runUrl}`); - if (!VALID_REACTIONS.includes(reaction)) { - core.setFailed(`${ERR_VALIDATION}: Invalid reaction type: ${reaction}. Valid reactions are: ${VALID_REACTIONS.join(", ")}`); - return; - } - const eventName = invocationContext.eventName; const { owner, repo } = invocationContext.eventRepo; const payload = invocationContext.eventPayload; diff --git a/actions/setup/js/reaction_setup_helpers.cjs b/actions/setup/js/reaction_setup_helpers.cjs new file mode 100644 index 00000000000..cefc21d617f --- /dev/null +++ b/actions/setup/js/reaction_setup_helpers.cjs @@ -0,0 +1,25 @@ +// @ts-check +/// + +const { ERR_VALIDATION } = require("./error_codes.cjs"); +const { resolveInvocationContext } = require("./invocation_context_helpers.cjs"); + +/** Valid GitHub reaction types */ +const VALID_REACTIONS = Object.freeze(["+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", "eyes"]); + +/** + * Resolve and validate reaction input plus invocation context. + * @param {any} rawContext + * @returns {{ reaction: string, invocationContext: ReturnType } | null} + */ +function resolveReactionSetup(rawContext) { + const reaction = process.env.GH_AW_REACTION || "eyes"; + const invocationContext = resolveInvocationContext(rawContext); + if (!VALID_REACTIONS.includes(reaction)) { + core.setFailed(`${ERR_VALIDATION}: Invalid reaction type: ${reaction}. Valid reactions are: ${VALID_REACTIONS.join(", ")}`); + return null; + } + return { reaction, invocationContext }; +} + +module.exports = { VALID_REACTIONS, resolveReactionSetup }; diff --git a/actions/setup/js/reaction_setup_helpers.test.cjs b/actions/setup/js/reaction_setup_helpers.test.cjs new file mode 100644 index 00000000000..9c3ddadc417 --- /dev/null +++ b/actions/setup/js/reaction_setup_helpers.test.cjs @@ -0,0 +1,60 @@ +// @ts-check +import { describe, it, expect, beforeEach, vi } from "vitest"; + +const mockCore = { + setFailed: vi.fn(), +}; + +global.core = mockCore; + +describe("reaction_setup_helpers", () => { + beforeEach(() => { + vi.resetAllMocks(); + vi.resetModules(); + delete process.env.GH_AW_REACTION; + }); + + async function importHelpers() { + return import("./reaction_setup_helpers.cjs?" + Date.now()); + } + + it("uses eyes as the default reaction", async () => { + const { resolveReactionSetup } = await importHelpers(); + + const result = resolveReactionSetup({ + eventName: "issues", + repo: { owner: "testowner", repo: "testrepo" }, + payload: { issue: { number: 123 } }, + }); + + expect(result?.reaction).toBe("eyes"); + expect(result?.invocationContext.eventName).toBe("issues"); + }); + + it("uses GH_AW_REACTION when provided", async () => { + process.env.GH_AW_REACTION = "rocket"; + const { resolveReactionSetup } = await importHelpers(); + + const result = resolveReactionSetup({ + eventName: "issues", + repo: { owner: "testowner", repo: "testrepo" }, + payload: { issue: { number: 123 } }, + }); + + expect(result?.reaction).toBe("rocket"); + }); + + it("fails and returns null for invalid reaction", async () => { + process.env.GH_AW_REACTION = "invalid"; + const { resolveReactionSetup } = await importHelpers(); + + const result = resolveReactionSetup({ + eventName: "issues", + repo: { owner: "testowner", repo: "testrepo" }, + payload: { issue: { number: 123 } }, + }); + + expect(result).toBeNull(); + expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining("Invalid reaction type: invalid")); + }); +}); From 3d0b9670b782c035c2fa9bd5a318026e8a30f0d0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Jul 2026 06:46:00 +0000 Subject: [PATCH 3/4] style: fix jsdoc spacing in reaction setup helper Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/reaction_setup_helpers.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actions/setup/js/reaction_setup_helpers.cjs b/actions/setup/js/reaction_setup_helpers.cjs index cefc21d617f..3584df25e4b 100644 --- a/actions/setup/js/reaction_setup_helpers.cjs +++ b/actions/setup/js/reaction_setup_helpers.cjs @@ -10,7 +10,7 @@ const VALID_REACTIONS = Object.freeze(["+1", "-1", "laugh", "confused", "heart", /** * Resolve and validate reaction input plus invocation context. * @param {any} rawContext - * @returns {{ reaction: string, invocationContext: ReturnType } | null} + * @returns {{ reaction: string, invocationContext: ReturnType } | null } */ function resolveReactionSetup(rawContext) { const reaction = process.env.GH_AW_REACTION || "eyes"; From 5017107fc3e990feba86568de2483f7c451368c6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 5 Jul 2026 07:25:10 +0000 Subject: [PATCH 4/4] fix: validate reaction before resolving invocation context; add REACTION_MAP sync test Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- actions/setup/js/reaction_setup_helpers.cjs | 2 +- actions/setup/js/reaction_setup_helpers.test.cjs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/actions/setup/js/reaction_setup_helpers.cjs b/actions/setup/js/reaction_setup_helpers.cjs index 3584df25e4b..a3a00cedb6c 100644 --- a/actions/setup/js/reaction_setup_helpers.cjs +++ b/actions/setup/js/reaction_setup_helpers.cjs @@ -14,11 +14,11 @@ const VALID_REACTIONS = Object.freeze(["+1", "-1", "laugh", "confused", "heart", */ function resolveReactionSetup(rawContext) { const reaction = process.env.GH_AW_REACTION || "eyes"; - const invocationContext = resolveInvocationContext(rawContext); if (!VALID_REACTIONS.includes(reaction)) { core.setFailed(`${ERR_VALIDATION}: Invalid reaction type: ${reaction}. Valid reactions are: ${VALID_REACTIONS.join(", ")}`); return null; } + const invocationContext = resolveInvocationContext(rawContext); return { reaction, invocationContext }; } diff --git a/actions/setup/js/reaction_setup_helpers.test.cjs b/actions/setup/js/reaction_setup_helpers.test.cjs index 9c3ddadc417..9d64aae15c1 100644 --- a/actions/setup/js/reaction_setup_helpers.test.cjs +++ b/actions/setup/js/reaction_setup_helpers.test.cjs @@ -57,4 +57,11 @@ describe("reaction_setup_helpers", () => { expect(result).toBeNull(); expect(mockCore.setFailed).toHaveBeenCalledWith(expect.stringContaining("Invalid reaction type: invalid")); }); + + it("VALID_REACTIONS stays in sync with REACTION_MAP keys", async () => { + const { VALID_REACTIONS } = await importHelpers(); + const { REACTION_MAP } = await import("./add_reaction.cjs?" + Date.now()); + + expect([...VALID_REACTIONS].sort()).toEqual(Object.keys(REACTION_MAP).sort()); + }); });