1010 * exempt labels that have been inactive for 90+ days.
1111 * - Avoids sending duplicate Friendly Reminder comments if one was
1212 * posted within the last 7 days.
13+ * - Moves issues labeled 'questions' to GitHub Discussions
1314 */
1415
1516const dedent = ( strings , ...values ) => {
@@ -28,68 +29,112 @@ async function fetchAllOpenIssues(github, owner, repo) {
2829 let page = 1 ;
2930
3031 while ( true ) {
31- const response = await github . rest . issues . listForRepo ( {
32+ try {
33+ const response = await github . rest . issues . listForRepo ( {
34+ owner,
35+ repo,
36+ state : 'open' ,
37+ per_page : 100 ,
38+ page,
39+ } ) ;
40+ const data = response . data || [ ] ;
41+ if ( data . length === 0 ) break ;
42+ const onlyIssues = data . filter ( issue => ! issue . pull_request ) ;
43+ issues . push ( ...onlyIssues ) ;
44+ if ( data . length < 100 ) break ;
45+ page ++ ;
46+ } catch ( err ) {
47+ console . error ( 'Error fetching issues:' , err ) ;
48+ break ;
49+ }
50+ }
51+ return issues ;
52+ }
53+
54+
55+ async function migrateToDiscussion ( github , owner , repo , issue , categories ) {
56+ const discussionCategory = 'Q&A' ;
57+ try {
58+ const category = categories . find ( cat =>
59+ cat . name . toLowerCase ( ) === discussionCategory . toLowerCase ( )
60+ ) ;
61+ if ( ! category ) {
62+ throw new Error ( `Discussion category '${ discussionCategory } ' not found.` ) ;
63+ }
64+ const { data : discussion } = await github . rest . discussions . create ( {
3265 owner,
3366 repo,
34- state : 'open' ,
35- per_page : 100 ,
36- page ,
67+ title : issue . title ,
68+ body : `Originally created by @ ${ issue . user . login } in # ${ issue . number } \n\n---\n\n ${ issue . body || '' } ` ,
69+ category_id : category . id ,
3770 } ) ;
38-
39- const data = response . data || [ ] ;
40- if ( data . length === 0 ) break ;
41- const onlyIssues = data . filter ( issue => ! issue . pull_request ) ;
42- issues . push ( ...onlyIssues ) ;
43-
44- if ( data . length < 100 ) break ;
45- page ++ ;
71+ await github . rest . issues . createComment ( {
72+ owner,
73+ repo,
74+ issue_number : issue . number ,
75+ body : `💬 This issue was moved to [Discussions](${ discussion . html_url } ) for better visibility.` ,
76+ } ) ;
77+ await github . rest . issues . update ( {
78+ owner,
79+ repo,
80+ issue_number : issue . number ,
81+ state : 'closed' ,
82+ } ) ;
83+ return discussion . html_url ;
84+ } catch ( err ) {
85+ console . error ( `Error migrating issue #${ issue . number } to discussion:` , err ) ;
86+ return null ;
4687 }
47- return issues ;
4888}
4989
90+
5091const shouldSendReminder = ( issue , exemptLabels , closeLabels ) => {
51- const hasExempt = issue . labels . some ( l => exemptLabels . includes ( l . name ) ) ;
52- const hasClose = issue . labels . some ( l => closeLabels . includes ( l . name ) ) ;
53- return issue . assignees . length > 0 && ! hasExempt && ! hasClose ;
92+ const hasExempt = issue . labels . some ( l => exemptLabels . includes ( l . name ) ) ;
93+ const hasClose = issue . labels . some ( l => closeLabels . includes ( l . name ) ) ;
94+ return issue . assignees . length > 0 && ! hasExempt && ! hasClose ;
5495} ;
5596
5697
5798module . exports = async ( { github, context } ) => {
99+ let categories = [ ] ;
100+ try {
101+ const { data } = await github . rest . discussions . listCategories ( { owner, repo } ) ;
102+ categories = data ;
103+ } catch ( err ) {
104+ console . error ( 'Error fetching discussion categories:' , err ) ;
105+ }
58106 const { owner, repo } = context . repo ;
59- const issues = await fetchAllOpenIssues ( github , owner , repo ) ;
107+ let issues = [ ] ;
108+ try {
109+ issues = await fetchAllOpenIssues ( github , owner , repo ) ;
110+ } catch ( err ) {
111+ console . error ( 'Failed to fetch issues:' , err ) ;
112+ return ;
113+ }
60114 const now = new Date ( ) ;
61115 const thresholdDays = 90 ;
62- const exemptLabels = [ 'to-be-discussed' ] ;
63- const closeLabels = [ 'awaiting-response' ] ;
116+ const exemptLabels = [ 'Status: Community help needed' , 'Status: Needs investigation' ] ;
117+ const closeLabels = [ 'Status: Awaiting Response' ] ;
118+ const discussionLabel = 'Type: Question' ;
64119 const sevenDays = 7 * 24 * 60 * 60 * 1000 ;
65120
66121 let totalClosed = 0 ;
67122 let totalReminders = 0 ;
68123 let totalSkipped = 0 ;
124+ let totalMigrated = 0 ;
69125
70126 for ( const issue of issues ) {
71127 const isAssigned = issue . assignees && issue . assignees . length > 0 ;
72128 const lastUpdate = new Date ( issue . updated_at ) ;
73129 const daysSinceUpdate = Math . floor ( ( now - lastUpdate ) / ( 1000 * 60 * 60 * 24 ) ) ;
74130
75- if ( daysSinceUpdate < thresholdDays ) {
76- totalSkipped ++ ;
131+ if ( issue . labels . some ( label => label . name === discussionLabel ) ) {
132+ const migrated = await migrateToDiscussion ( github , owner , repo , issue , categories ) ;
133+ if ( migrated ) totalMigrated ++ ;
77134 continue ;
78135 }
79136
80- const { data : comments } = await github . rest . issues . listComments ( {
81- owner,
82- repo,
83- issue_number : issue . number ,
84- per_page : 10 ,
85- } ) ;
86-
87- const recentFriendlyReminder = comments . find ( comment =>
88- comment . user . login === 'github-actions[bot]' &&
89- comment . body . includes ( '⏰ Friendly Reminder' ) &&
90- ( now - new Date ( comment . created_at ) ) < sevenDays
91- ) ;
92- if ( recentFriendlyReminder ) {
137+ if ( daysSinceUpdate < thresholdDays ) {
93138 totalSkipped ++ ;
94139 continue ;
95140 }
@@ -100,19 +145,45 @@ module.exports = async ({ github, context }) => {
100145 }
101146
102147 if ( issue . labels . some ( label => closeLabels . includes ( label . name ) ) || ! isAssigned ) {
103- await github . rest . issues . createComment ( {
104- owner,
105- repo,
106- issue_number : issue . number ,
107- body : '⚠️ This issue was closed automatically due to inactivity. Please reopen or open a new one if still relevant.' ,
108- } ) ;
109- await github . rest . issues . update ( {
148+ try {
149+ await github . rest . issues . createComment ( {
150+ owner,
151+ repo,
152+ issue_number : issue . number ,
153+ body : '⚠️ This issue was closed automatically due to inactivity. Please reopen or open a new one if still relevant.' ,
154+ } ) ;
155+ await github . rest . issues . update ( {
156+ owner,
157+ repo,
158+ issue_number : issue . number ,
159+ state : 'closed' ,
160+ } ) ;
161+ totalClosed ++ ;
162+ } catch ( err ) {
163+ console . error ( `Error closing issue #${ issue . number } :` , err ) ;
164+ }
165+ continue ;
166+ }
167+
168+ let comments = [ ] ;
169+ try {
170+ const { data } = await github . rest . issues . listComments ( {
110171 owner,
111172 repo,
112173 issue_number : issue . number ,
113- state : 'closed' ,
174+ per_page : 50 ,
114175 } ) ;
115- totalClosed ++ ;
176+ comments = data ;
177+ } catch ( err ) {
178+ console . error ( `Error fetching comments for issue #${ issue . number } :` , err ) ;
179+ }
180+
181+ const recentFriendlyReminder = comments . find ( comment =>
182+ comment . user . login === 'github-actions[bot]' &&
183+ comment . body . includes ( '⏰ Friendly Reminder' )
184+ ) ;
185+ if ( recentFriendlyReminder ) {
186+ totalSkipped ++ ;
116187 continue ;
117188 }
118189
@@ -129,14 +200,17 @@ module.exports = async ({ github, context }) => {
129200 - Or label it 'awaiting-response' if you're waiting on something
130201
131202 This is just a reminder; the issue remains open for now.` ;
132-
133- await github . rest . issues . createComment ( {
134- owner,
135- repo,
136- issue_number : issue . number ,
137- body : comment ,
138- } ) ;
139- totalReminders ++ ;
203+ try {
204+ await github . rest . issues . createComment ( {
205+ owner,
206+ repo,
207+ issue_number : issue . number ,
208+ body : comment ,
209+ } ) ;
210+ totalReminders ++ ;
211+ } catch ( err ) {
212+ console . error ( `Error sending reminder for issue #${ issue . number } :` , err ) ;
213+ }
140214 }
141215 }
142216
@@ -145,5 +219,6 @@ module.exports = async ({ github, context }) => {
145219 Total issues processed: ${ issues . length }
146220 Total issues closed: ${ totalClosed }
147221 Total reminders sent: ${ totalReminders }
222+ Total migrated to discussions: ${ totalMigrated }
148223 Total skipped: ${ totalSkipped } ` ) ;
149224} ;
0 commit comments