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,113 @@ 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 } ) => {
58- const { owner, repo } = context . repo ;
59- const issues = await fetchAllOpenIssues ( github , owner , repo ) ;
6099 const now = new Date ( ) ;
61100 const thresholdDays = 90 ;
62- const exemptLabels = [ 'to-be-discussed' ] ;
63- const closeLabels = [ 'awaiting-response' ] ;
64- const sevenDays = 7 * 24 * 60 * 60 * 1000 ;
101+ const exemptLabels = [ 'Status: Community help needed' , 'Status: Needs investigation' ] ;
102+ const closeLabels = [ 'Status: Awaiting Response' ] ;
103+ const discussionLabel = 'Type: Question' ;
104+ const { owner, repo } = context . repo ;
65105
66106 let totalClosed = 0 ;
67107 let totalReminders = 0 ;
68108 let totalSkipped = 0 ;
109+ let totalMigrated = 0 ;
110+ let issues = [ ] ;
111+ let categories = [ ] ;
112+
113+ try {
114+ issues = await fetchAllOpenIssues ( github , owner , repo ) ;
115+ } catch ( err ) {
116+ console . error ( 'Failed to fetch issues:' , err ) ;
117+ return ;
118+ }
119+
120+ try {
121+ const { data } = await github . rest . discussions . listCategories ( { owner, repo } ) ;
122+ categories = data ;
123+ } catch ( err ) {
124+ console . error ( 'Error fetching discussion categories:' , err ) ;
125+ }
69126
70127 for ( const issue of issues ) {
71128 const isAssigned = issue . assignees && issue . assignees . length > 0 ;
72129 const lastUpdate = new Date ( issue . updated_at ) ;
73130 const daysSinceUpdate = Math . floor ( ( now - lastUpdate ) / ( 1000 * 60 * 60 * 24 ) ) ;
74131
75- if ( daysSinceUpdate < thresholdDays ) {
76- totalSkipped ++ ;
132+ if ( issue . labels . some ( label => label . name === discussionLabel ) ) {
133+ const migrated = await migrateToDiscussion ( github , owner , repo , issue , categories ) ;
134+ if ( migrated ) totalMigrated ++ ;
77135 continue ;
78136 }
79137
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 ) {
138+ if ( daysSinceUpdate < thresholdDays ) {
93139 totalSkipped ++ ;
94140 continue ;
95141 }
@@ -100,19 +146,45 @@ module.exports = async ({ github, context }) => {
100146 }
101147
102148 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 ( {
149+ try {
150+ await github . rest . issues . createComment ( {
151+ owner,
152+ repo,
153+ issue_number : issue . number ,
154+ body : '⚠️ This issue was closed automatically due to inactivity. Please reopen or open a new one if still relevant.' ,
155+ } ) ;
156+ await github . rest . issues . update ( {
157+ owner,
158+ repo,
159+ issue_number : issue . number ,
160+ state : 'closed' ,
161+ } ) ;
162+ totalClosed ++ ;
163+ } catch ( err ) {
164+ console . error ( `Error closing issue #${ issue . number } :` , err ) ;
165+ }
166+ continue ;
167+ }
168+
169+ let comments = [ ] ;
170+ try {
171+ const { data } = await github . rest . issues . listComments ( {
110172 owner,
111173 repo,
112174 issue_number : issue . number ,
113- state : 'closed' ,
175+ per_page : 50 ,
114176 } ) ;
115- totalClosed ++ ;
177+ comments = data ;
178+ } catch ( err ) {
179+ console . error ( `Error fetching comments for issue #${ issue . number } :` , err ) ;
180+ }
181+
182+ const recentFriendlyReminder = comments . find ( comment =>
183+ comment . user . login === 'github-actions[bot]' &&
184+ comment . body . includes ( '⏰ Friendly Reminder' )
185+ ) ;
186+ if ( recentFriendlyReminder ) {
187+ totalSkipped ++ ;
116188 continue ;
117189 }
118190
@@ -129,14 +201,17 @@ module.exports = async ({ github, context }) => {
129201 - Or label it 'awaiting-response' if you're waiting on something
130202
131203 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 ++ ;
204+ try {
205+ await github . rest . issues . createComment ( {
206+ owner,
207+ repo,
208+ issue_number : issue . number ,
209+ body : comment ,
210+ } ) ;
211+ totalReminders ++ ;
212+ } catch ( err ) {
213+ console . error ( `Error sending reminder for issue #${ issue . number } :` , err ) ;
214+ }
140215 }
141216 }
142217
@@ -145,5 +220,6 @@ module.exports = async ({ github, context }) => {
145220 Total issues processed: ${ issues . length }
146221 Total issues closed: ${ totalClosed }
147222 Total reminders sent: ${ totalReminders }
223+ Total migrated to discussions: ${ totalMigrated }
148224 Total skipped: ${ totalSkipped } ` ) ;
149225} ;
0 commit comments