@@ -13,6 +13,7 @@ const { addReaction, addDiscussionReaction } = require("./add_reaction.cjs");
1313
1414/**
1515 * Event type descriptions for comment messages
16+ * @type {Record<string, string> }
1617 */
1718const EVENT_TYPE_DESCRIPTIONS = {
1819 issues : "issue" ,
@@ -23,6 +24,119 @@ const EVENT_TYPE_DESCRIPTIONS = {
2324 discussion_comment : "discussion comment" ,
2425} ;
2526
27+ /** Valid GitHub reaction types */
28+ const VALID_REACTIONS = Object . freeze ( [ "+1" , "-1" , "laugh" , "confused" , "heart" , "hooray" , "rocket" , "eyes" ] ) ;
29+
30+ /**
31+ * Resolve the reaction and comment API endpoints for a given event.
32+ * Returns null (after calling core.setFailed) when the event or payload is invalid.
33+ * @param {string } eventName - The GitHub event name
34+ * @param {string } owner - Repository owner
35+ * @param {string } repo - Repository name
36+ * @param {Record<string, any> } payload - The event payload
37+ * @returns {Promise<{reactionEndpoint: string, commentUpdateEndpoint: string} | null> }
38+ */
39+ async function resolveEventEndpoints ( eventName , owner , repo , payload ) {
40+ switch ( eventName ) {
41+ case "issues" : {
42+ const issueNumber = payload ?. issue ?. number ;
43+ if ( ! issueNumber ) {
44+ core . setFailed ( `${ ERR_NOT_FOUND } : Issue number not found in event payload` ) ;
45+ return null ;
46+ }
47+ return {
48+ reactionEndpoint : `/repos/${ owner } /${ repo } /issues/${ issueNumber } /reactions` ,
49+ commentUpdateEndpoint : `/repos/${ owner } /${ repo } /issues/${ issueNumber } /comments` ,
50+ } ;
51+ }
52+
53+ case "issue_comment" : {
54+ const commentId = payload ?. comment ?. id ;
55+ const issueNumber = payload ?. issue ?. number ;
56+ if ( ! commentId ) {
57+ core . setFailed ( `${ ERR_VALIDATION } : Comment ID not found in event payload` ) ;
58+ return null ;
59+ }
60+ if ( ! issueNumber ) {
61+ core . setFailed ( `${ ERR_NOT_FOUND } : Issue number not found in event payload` ) ;
62+ return null ;
63+ }
64+ return {
65+ reactionEndpoint : `/repos/${ owner } /${ repo } /issues/comments/${ commentId } /reactions` ,
66+ // Create new comment on the issue itself, not on the comment
67+ commentUpdateEndpoint : `/repos/${ owner } /${ repo } /issues/${ issueNumber } /comments` ,
68+ } ;
69+ }
70+
71+ case "pull_request" : {
72+ const prNumber = payload ?. pull_request ?. number ;
73+ if ( ! prNumber ) {
74+ core . setFailed ( `${ ERR_NOT_FOUND } : Pull request number not found in event payload` ) ;
75+ return null ;
76+ }
77+ // PRs are "issues" for the reactions endpoint
78+ return {
79+ reactionEndpoint : `/repos/${ owner } /${ repo } /issues/${ prNumber } /reactions` ,
80+ commentUpdateEndpoint : `/repos/${ owner } /${ repo } /issues/${ prNumber } /comments` ,
81+ } ;
82+ }
83+
84+ case "pull_request_review_comment" : {
85+ const reviewCommentId = payload ?. comment ?. id ;
86+ const prNumber = payload ?. pull_request ?. number ;
87+ if ( ! reviewCommentId ) {
88+ core . setFailed ( `${ ERR_VALIDATION } : Review comment ID not found in event payload` ) ;
89+ return null ;
90+ }
91+ if ( ! prNumber ) {
92+ core . setFailed ( `${ ERR_NOT_FOUND } : Pull request number not found in event payload` ) ;
93+ return null ;
94+ }
95+ return {
96+ reactionEndpoint : `/repos/${ owner } /${ repo } /pulls/comments/${ reviewCommentId } /reactions` ,
97+ // Create new comment on the PR itself (using issues endpoint since PRs are issues)
98+ commentUpdateEndpoint : `/repos/${ owner } /${ repo } /issues/${ prNumber } /comments` ,
99+ } ;
100+ }
101+
102+ case "discussion" : {
103+ const discussionNumber = payload ?. discussion ?. number ;
104+ if ( ! discussionNumber ) {
105+ core . setFailed ( `${ ERR_NOT_FOUND } : Discussion number not found in event payload` ) ;
106+ return null ;
107+ }
108+ // Discussions use GraphQL API - get the node ID
109+ const discussion = await getDiscussionId ( owner , repo , discussionNumber ) ;
110+ return {
111+ reactionEndpoint : discussion . id , // Store node ID for GraphQL
112+ commentUpdateEndpoint : `discussion:${ discussionNumber } ` , // Special format to indicate discussion
113+ } ;
114+ }
115+
116+ case "discussion_comment" : {
117+ const discussionNumber = payload ?. discussion ?. number ;
118+ const commentId = payload ?. comment ?. id ;
119+ if ( ! discussionNumber || ! commentId ) {
120+ core . setFailed ( `${ ERR_NOT_FOUND } : Discussion or comment information not found in event payload` ) ;
121+ return null ;
122+ }
123+ const commentNodeId = payload ?. comment ?. node_id ;
124+ if ( ! commentNodeId ) {
125+ core . setFailed ( `${ ERR_NOT_FOUND } : Discussion comment node ID not found in event payload` ) ;
126+ return null ;
127+ }
128+ return {
129+ reactionEndpoint : commentNodeId , // Store node ID for GraphQL
130+ commentUpdateEndpoint : `discussion_comment:${ discussionNumber } :${ commentId } ` , // Special format
131+ } ;
132+ }
133+
134+ default :
135+ core . setFailed ( `${ ERR_VALIDATION } : Unsupported event type: ${ eventName } ` ) ;
136+ return null ;
137+ }
138+ }
139+
26140async function main ( ) {
27141 const reaction = process . env . GH_AW_REACTION || "eyes" ;
28142 const command = process . env . GH_AW_COMMAND ; // Only present for command workflows
@@ -34,113 +148,20 @@ async function main() {
34148 core . info ( `Run ID: ${ context . runId } ` ) ;
35149 core . info ( `Run URL: ${ runUrl } ` ) ;
36150
37- // Validate reaction type
38- const validReactions = [ "+1" , "-1" , "laugh" , "confused" , "heart" , "hooray" , "rocket" , "eyes" ] ;
39- if ( ! validReactions . includes ( reaction ) ) {
40- core . setFailed ( `${ ERR_VALIDATION } : Invalid reaction type: ${ reaction } . Valid reactions are: ${ validReactions . join ( ", " ) } ` ) ;
151+ if ( ! VALID_REACTIONS . includes ( reaction ) ) {
152+ core . setFailed ( `${ ERR_VALIDATION } : Invalid reaction type: ${ reaction } . Valid reactions are: ${ VALID_REACTIONS . join ( ", " ) } ` ) ;
41153 return ;
42154 }
43155
44- let reactionEndpoint ;
45- let commentUpdateEndpoint ;
46156 const eventName = invocationContext . eventName ;
47- const owner = invocationContext . eventRepo . owner ;
48- const repo = invocationContext . eventRepo . repo ;
157+ const { owner, repo } = invocationContext . eventRepo ;
49158 const payload = invocationContext . eventPayload ;
50159
51160 try {
52- switch ( eventName ) {
53- case "issues" : {
54- const issueNumber = payload ?. issue ?. number ;
55- if ( ! issueNumber ) {
56- core . setFailed ( `${ ERR_NOT_FOUND } : Issue number not found in event payload` ) ;
57- return ;
58- }
59- reactionEndpoint = `/repos/${ owner } /${ repo } /issues/${ issueNumber } /reactions` ;
60- commentUpdateEndpoint = `/repos/${ owner } /${ repo } /issues/${ issueNumber } /comments` ;
61- break ;
62- }
63-
64- case "issue_comment" : {
65- const commentId = payload ?. comment ?. id ;
66- const issueNumberForComment = payload ?. issue ?. number ;
67- if ( ! commentId ) {
68- core . setFailed ( `${ ERR_VALIDATION } : Comment ID not found in event payload` ) ;
69- return ;
70- }
71- if ( ! issueNumberForComment ) {
72- core . setFailed ( `${ ERR_NOT_FOUND } : Issue number not found in event payload` ) ;
73- return ;
74- }
75- reactionEndpoint = `/repos/${ owner } /${ repo } /issues/comments/${ commentId } /reactions` ;
76- // Create new comment on the issue itself, not on the comment
77- commentUpdateEndpoint = `/repos/${ owner } /${ repo } /issues/${ issueNumberForComment } /comments` ;
78- break ;
79- }
161+ const endpoints = await resolveEventEndpoints ( eventName , owner , repo , payload ) ;
162+ if ( ! endpoints ) return ;
80163
81- case "pull_request" : {
82- const prNumber = payload ?. pull_request ?. number ;
83- if ( ! prNumber ) {
84- core . setFailed ( `${ ERR_NOT_FOUND } : Pull request number not found in event payload` ) ;
85- return ;
86- }
87- // PRs are "issues" for the reactions endpoint
88- reactionEndpoint = `/repos/${ owner } /${ repo } /issues/${ prNumber } /reactions` ;
89- commentUpdateEndpoint = `/repos/${ owner } /${ repo } /issues/${ prNumber } /comments` ;
90- break ;
91- }
92-
93- case "pull_request_review_comment" : {
94- const reviewCommentId = payload ?. comment ?. id ;
95- const prNumberForReviewComment = payload ?. pull_request ?. number ;
96- if ( ! reviewCommentId ) {
97- core . setFailed ( `${ ERR_VALIDATION } : Review comment ID not found in event payload` ) ;
98- return ;
99- }
100- if ( ! prNumberForReviewComment ) {
101- core . setFailed ( `${ ERR_NOT_FOUND } : Pull request number not found in event payload` ) ;
102- return ;
103- }
104- reactionEndpoint = `/repos/${ owner } /${ repo } /pulls/comments/${ reviewCommentId } /reactions` ;
105- // Create new comment on the PR itself (using issues endpoint since PRs are issues)
106- commentUpdateEndpoint = `/repos/${ owner } /${ repo } /issues/${ prNumberForReviewComment } /comments` ;
107- break ;
108- }
109-
110- case "discussion" : {
111- const discussionNumber = payload ?. discussion ?. number ;
112- if ( ! discussionNumber ) {
113- core . setFailed ( `${ ERR_NOT_FOUND } : Discussion number not found in event payload` ) ;
114- return ;
115- }
116- // Discussions use GraphQL API - get the node ID
117- const discussion = await getDiscussionId ( owner , repo , discussionNumber ) ;
118- reactionEndpoint = discussion . id ; // Store node ID for GraphQL
119- commentUpdateEndpoint = `discussion:${ discussionNumber } ` ; // Special format to indicate discussion
120- break ;
121- }
122-
123- case "discussion_comment" : {
124- const discussionCommentNumber = payload ?. discussion ?. number ;
125- const discussionCommentId = payload ?. comment ?. id ;
126- if ( ! discussionCommentNumber || ! discussionCommentId ) {
127- core . setFailed ( `${ ERR_NOT_FOUND } : Discussion or comment information not found in event payload` ) ;
128- return ;
129- }
130- const commentNodeId = payload ?. comment ?. node_id ;
131- if ( ! commentNodeId ) {
132- core . setFailed ( `${ ERR_NOT_FOUND } : Discussion comment node ID not found in event payload` ) ;
133- return ;
134- }
135- reactionEndpoint = commentNodeId ; // Store node ID for GraphQL
136- commentUpdateEndpoint = `discussion_comment:${ discussionCommentNumber } :${ discussionCommentId } ` ; // Special format
137- break ;
138- }
139-
140- default :
141- core . setFailed ( `${ ERR_VALIDATION } : Unsupported event type: ${ eventName } ` ) ;
142- return ;
143- }
164+ const { reactionEndpoint, commentUpdateEndpoint } = endpoints ;
144165
145166 core . info ( `Reaction API endpoint: ${ reactionEndpoint } ` ) ;
146167
@@ -151,10 +172,8 @@ async function main() {
151172 await addReaction ( reactionEndpoint , reaction ) ;
152173 }
153174
154- if ( commentUpdateEndpoint ) {
155- core . info ( `Comment endpoint: ${ commentUpdateEndpoint } ` ) ;
156- await addCommentWithWorkflowLink ( commentUpdateEndpoint , runUrl , eventName , invocationContext ) ;
157- }
175+ core . info ( `Comment endpoint: ${ commentUpdateEndpoint } ` ) ;
176+ await addCommentWithWorkflowLink ( commentUpdateEndpoint , runUrl , eventName , invocationContext ) ;
158177 } catch ( error ) {
159178 if ( isLockedError ( error ) ) {
160179 core . info ( `Cannot add reaction: resource is locked (this is expected and not an error)` ) ;
@@ -319,4 +338,4 @@ async function addCommentWithWorkflowLink(endpoint, runUrl, eventName, invocatio
319338 }
320339}
321340
322- module . exports = { main, addCommentWithWorkflowLink, addReaction, addDiscussionReaction } ;
341+ module . exports = { main, addCommentWithWorkflowLink, resolveEventEndpoints , VALID_REACTIONS , addReaction, addDiscussionReaction } ;
0 commit comments