name: 'Repository Maintenance' on: schedule: - cron: '0 3 * * *' workflow_dispatch: permissions: issues: write pull-requests: write discussions: write concurrency: group: lock jobs: stale: name: 'Stale' runs-on: ubuntu-latest steps: - uses: actions/stale@v9 with: days-before-stale: 7 days-before-close: 14 stale-issue-label: stale stale-pr-label: stale stale-issue-message: > This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. lock-threads: name: 'Lock Old Threads' runs-on: ubuntu-latest steps: - uses: dessant/lock-threads@v5 with: issue-inactive-days: '30' pr-inactive-days: '30' discussion-inactive-days: '30' log-output: true issue-comment: > This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new discussion for related concerns. pr-comment: > This pull request has been automatically locked since there has not been any recent activity after it was closed. Please open a new discussion for related concerns. discussion-comment: > This discussion has been automatically locked since there has not been any recent activity after it was closed. Please open a new discussion for related concerns. close-answered-discussions: name: 'Close Answered Discussions' runs-on: ubuntu-latest steps: - uses: actions/github-script@v7 with: script: | function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } const query = `query($owner:String!, $name:String!) { repository(owner:$owner, name:$name){ discussions(first:100, answered:true, states:[OPEN]) { nodes { id, number } } } }`; const variables = { owner: context.repo.owner, name: context.repo.repo, } const result = await github.graphql(query, variables) console.log(`Found ${result.repository.discussions.nodes.length} open answered discussions`) for (const discussion of result.repository.discussions.nodes) { console.log(`Closing discussion #${discussion.number} (${discussion.id})`) const addCommentMutation = `mutation($discussion:ID!, $body:String!) { addDiscussionComment(input:{discussionId:$discussion, body:$body}) { clientMutationId } }`; const commentVariables = { discussion: discussion.id, body: 'This discussion has been automatically closed because it was marked as answered.', } await github.graphql(addCommentMutation, commentVariables) const closeDiscussionMutation = `mutation($discussion:ID!, $reason:DiscussionCloseReason!) { closeDiscussion(input:{discussionId:$discussion, reason:$reason}) { clientMutationId } }`; const closeVariables = { discussion: discussion.id, reason: "RESOLVED", } await github.graphql(closeDiscussionMutation, closeVariables) await sleep(1000) } close-outdated-discussions: name: 'Close Outdated Discussions' runs-on: ubuntu-latest steps: - uses: actions/github-script@v7 with: script: | function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } const CUTOFF_DAYS = 180; const cutoff = new Date(); cutoff.setDate(cutoff.getDate() - CUTOFF_DAYS); const query = `query( $owner:String!, $name:String!, $supportCategory:ID!, $generalCategory:ID!, ) { supportDiscussions: repository(owner:$owner, name:$name){ discussions( categoryId:$supportCategory, last:50, answered:false, states:[OPEN], ) { nodes { id, number, updatedAt } }, }, generalDiscussions: repository(owner:$owner, name:$name){ discussions( categoryId:$generalCategory, last:50, states:[OPEN], ) { nodes { id, number, updatedAt } } } }`; const variables = { owner: context.repo.owner, name: context.repo.repo, supportCategory: "DIC_kwDOH31rQM4CRErR", generalCategory: "DIC_kwDOH31rQM4CRErQ" } const result = await github.graphql(query, variables); const combinedDiscussions = [ ...result.supportDiscussions.discussions.nodes, ...result.generalDiscussions.discussions.nodes, ] console.log(`Checking ${combinedDiscussions.length} open discussions`); for (const discussion of combinedDiscussions) { if (new Date(discussion.updatedAt) < cutoff) { console.log(`Closing outdated discussion #${discussion.number} (${discussion.id}), last updated at ${discussion.updatedAt}`); const addCommentMutation = `mutation($discussion:ID!, $body:String!) { addDiscussionComment(input:{discussionId:$discussion, body:$body}) { clientMutationId } }`; const commentVariables = { discussion: discussion.id, body: 'This discussion has been automatically closed due to inactivity.', } await github.graphql(addCommentMutation, commentVariables); const closeDiscussionMutation = `mutation($discussion:ID!, $reason:DiscussionCloseReason!) { closeDiscussion(input:{discussionId:$discussion, reason:$reason}) { clientMutationId } }`; const closeVariables = { discussion: discussion.id, reason: "OUTDATED", } await github.graphql(closeDiscussionMutation, closeVariables); await sleep(1000); } } close-unsupported-feature-requests: name: 'Close Unsupported Feature Requests' runs-on: ubuntu-latest steps: - uses: actions/github-script@v7 with: script: | function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } const CUTOFF_1_DAYS = 180; const CUTOFF_1_COUNT = 5; const CUTOFF_2_DAYS = 365; const CUTOFF_2_COUNT = 10; const cutoff1Date = new Date(); cutoff1Date.setDate(cutoff1Date.getDate() - CUTOFF_1_DAYS); const cutoff2Date = new Date(); cutoff2Date.setDate(cutoff2Date.getDate() - CUTOFF_2_DAYS); const query = `query( $owner:String!, $name:String!, $featureRequestsCategory:ID!, ) { repository(owner:$owner, name:$name){ discussions( categoryId:$featureRequestsCategory, last:100, states:[OPEN], ) { nodes { id, number, updatedAt, upvoteCount, } }, } }`; const variables = { owner: context.repo.owner, name: context.repo.repo, featureRequestsCategory: "DIC_kwDOH31rQM4CRErS" } const result = await github.graphql(query, variables); for (const discussion of result.repository.discussions.nodes) { const discussionDate = new Date(discussion.updatedAt); if ((discussionDate < cutoff1Date && discussion.upvoteCount < CUTOFF_1_COUNT) || (discussionDate < cutoff2Date && discussion.upvoteCount < CUTOFF_2_COUNT)) { console.log(`Closing discussion #${discussion.number} (${discussion.id}), last updated at ${discussion.updatedAt} with votes ${discussion.upvoteCount}`); const addCommentMutation = `mutation($discussion:ID!, $body:String!) { addDiscussionComment(input:{discussionId:$discussion, body:$body}) { clientMutationId } }`; const commentVariables = { discussion: discussion.id, body: 'This discussion has been automatically closed due to lack of community interest.', } await github.graphql(addCommentMutation, commentVariables); const closeDiscussionMutation = `mutation($discussion:ID!, $reason:DiscussionCloseReason!) { closeDiscussion(input:{discussionId:$discussion, reason:$reason}) { clientMutationId } }`; const closeVariables = { discussion: discussion.id, reason: "OUTDATED", } await github.graphql(closeDiscussionMutation, closeVariables); await sleep(1000); } }