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);
}
}