mirror of
				https://github.com/paperless-ngx/paperless-ngx.git
				synced 2025-10-30 03:56:23 -05:00 
			
		
		
		
	
		
			
				
	
	
		
			295 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			YAML
		
	
	
	
	
	
			
		
		
	
	
			295 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			YAML
		
	
	
	
	
	
| 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'
 | |
|     if: github.repository_owner == 'paperless-ngx'
 | |
|     runs-on: ubuntu-24.04
 | |
|     steps:
 | |
|       - uses: actions/stale@v9
 | |
|         with:
 | |
|           days-before-stale: 7
 | |
|           days-before-close: 14
 | |
|           any-of-labels: 'stale,cant-reproduce,not a bug'
 | |
|           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. See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
 | |
|   lock-threads:
 | |
|     name: 'Lock Old Threads'
 | |
|     if: github.repository_owner == 'paperless-ngx'
 | |
|     runs-on: ubuntu-24.04
 | |
|     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 or issue for related concerns.
 | |
|             See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
 | |
|           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 or issue for related concerns.
 | |
|             See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
 | |
|           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.
 | |
|             See our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.
 | |
|   close-answered-discussions:
 | |
|     name: 'Close Answered Discussions'
 | |
|     if: github.repository_owner == 'paperless-ngx'
 | |
|     runs-on: ubuntu-24.04
 | |
|     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. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
 | |
|               }
 | |
|               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'
 | |
|     if: github.repository_owner == 'paperless-ngx'
 | |
|     runs-on: ubuntu-24.04
 | |
|     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_kwDOG1Zs184CBKWK",
 | |
|               generalCategory: "DIC_kwDOG1Zs184CBKWJ"
 | |
|             }
 | |
|             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. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
 | |
|                 }
 | |
|                 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'
 | |
|     if: github.repository_owner == 'paperless-ngx'
 | |
|     runs-on: ubuntu-24.04
 | |
|     steps:
 | |
|       - uses: actions/github-script@v7
 | |
|         with:
 | |
|           script: |
 | |
|             function sleep(ms) {
 | |
|               return new Promise(resolve => setTimeout(resolve, ms));
 | |
|             }
 | |
| 
 | |
|             const CUTOFF_MAX_COUNT = 80;
 | |
|             const CUTOFF_1_DAYS = 180;
 | |
|             const CUTOFF_1_COUNT = 5;
 | |
|             const CUTOFF_2_DAYS = 365;
 | |
|             const CUTOFF_2_COUNT = 20;
 | |
|             const CUTOFF_3_DAYS = 730;
 | |
|             const CUTOFF_3_COUNT = 40;
 | |
| 
 | |
|             const cutoff1Date = new Date();
 | |
|             cutoff1Date.setDate(cutoff1Date.getDate() - CUTOFF_1_DAYS);
 | |
|             const cutoff2Date = new Date();
 | |
|             cutoff2Date.setDate(cutoff2Date.getDate() - CUTOFF_2_DAYS);
 | |
|             const cutoff3Date = new Date();
 | |
|             cutoff3Date.setDate(cutoff3Date.getDate() - CUTOFF_3_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_kwDOG1Zs184CBNr4"
 | |
|             }
 | |
|             const result = await github.graphql(query, variables);
 | |
| 
 | |
|             for (const discussion of result.repository.discussions.nodes) {
 | |
|               const discussionUpdatedDate = new Date(discussion.updatedAt);
 | |
|               const discussionCreatedDate = new Date(discussion.createdAt);
 | |
|               if ((discussionUpdatedDate < cutoff1Date && discussion.upvoteCount < CUTOFF_MAX_COUNT) ||
 | |
|                   (discussionCreatedDate < cutoff1Date && discussion.upvoteCount < CUTOFF_1_COUNT) ||
 | |
|                   (discussionCreatedDate < cutoff2Date && discussion.upvoteCount < CUTOFF_2_COUNT) ||
 | |
|                   (discussionCreatedDate < cutoff3Date && discussion.upvoteCount < CUTOFF_3_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 support. Please see our [contributing guidelines](https://github.com/paperless-ngx/paperless-ngx/blob/dev/CONTRIBUTING.md#automatic-repository-maintenance) for more details.',
 | |
|                 }
 | |
|                 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);
 | |
|               }
 | |
|             }
 | 
