├── .nvmrc ├── .gitignore ├── assets └── screenshot.png ├── .prettierrc.yml ├── .github ├── FUNDING.yml └── workflows │ └── release.yml ├── action.yml ├── LICENSE ├── src ├── schema.js └── index.js ├── package.json ├── CHANGELOG.md └── README.md /.nvmrc: -------------------------------------------------------------------------------- 1 | 16.18.1 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .assets 3 | -------------------------------------------------------------------------------- /assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dessant/issue-states/HEAD/assets/screenshot.png -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | bracketSpacing: false 3 | arrowParens: 'avoid' 4 | trailingComma: 'none' 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dessant 2 | patreon: dessant 3 | custom: 4 | - https://armin.dev/go/paypal 5 | - https://armin.dev/go/bitcoin 6 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Create release 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v[0-9]+.[0-9]+.[0-9]+' 7 | 8 | jobs: 9 | release: 10 | name: Release on GitHub 11 | runs-on: ubuntu-22.04 12 | permissions: 13 | contents: write 14 | steps: 15 | - name: Create GitHub release 16 | uses: softprops/action-gh-release@v1 17 | with: 18 | tag_name: ${{ github.ref_name }} 19 | name: ${{ github.ref_name }} 20 | body: > 21 | Learn more about this release from the [changelog](https://github.com/dessant/issue-states/blob/main/CHANGELOG.md#changelog). 22 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Issue States' 2 | description: 'Close or reopen issues when they are moved to a project column' 3 | author: 'Armin Sebastian' 4 | inputs: 5 | github-token: 6 | description: 'GitHub access token' 7 | default: '${{ github.token }}' 8 | open-issue-columns: 9 | description: 'Reopen issues that are moved to these project columns, value must be a comma separated list of project columns' 10 | default: '' 11 | closed-issue-columns: 12 | description: 'Close issues that are moved to these project columns, value must be a comma separated list of project columns' 13 | default: 'Closed, Done' 14 | log-output: 15 | description: 'Log output parameters, value must be either `true` or `false`' 16 | default: false 17 | outputs: 18 | issues: 19 | description: 'Issues that have been either closed or reopened, value is a JSON string' 20 | runs: 21 | using: 'node16' 22 | main: 'dist/index.js' 23 | branding: 24 | icon: 'box' 25 | color: 'red' 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2023 Armin Sebastian 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/schema.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | 3 | const extendedJoi = Joi.extend(joi => { 4 | return { 5 | type: 'stringList', 6 | base: joi.array(), 7 | coerce: { 8 | from: 'string', 9 | method(value) { 10 | value = value.trim(); 11 | if (value) { 12 | value = value 13 | .split(',') 14 | .map(item => item.trim()) 15 | .filter(Boolean); 16 | } 17 | 18 | return {value}; 19 | } 20 | } 21 | }; 22 | }); 23 | 24 | const schema = Joi.object({ 25 | 'github-token': Joi.string().trim().max(100), 26 | 27 | 'open-issue-columns': Joi.alternatives() 28 | .try( 29 | extendedJoi 30 | .stringList() 31 | .items(Joi.string().trim().max(140)) 32 | .min(1) 33 | .max(30) 34 | .unique(), 35 | Joi.string().trim().valid('') 36 | ) 37 | .default(''), 38 | 39 | 'closed-issue-columns': Joi.alternatives() 40 | .try( 41 | extendedJoi 42 | .stringList() 43 | .items(Joi.string().trim().max(140)) 44 | .min(1) 45 | .max(30) 46 | .unique(), 47 | Joi.string().trim().valid('') 48 | ) 49 | .default('Closed, Done'), 50 | 51 | 'log-output': Joi.boolean().default(false) 52 | }); 53 | 54 | module.exports = schema; 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "issue-states", 3 | "version": "3.0.0", 4 | "description": "A GitHub Action that closes or reopens issues when they are moved to a project column.", 5 | "author": "Armin Sebastian", 6 | "license": "MIT", 7 | "homepage": "https://github.com/dessant/issue-states", 8 | "repository": { 9 | "url": "https://github.com/dessant/issue-states.git", 10 | "type": "git" 11 | }, 12 | "bugs": { 13 | "url": "https://github.com/dessant/issue-states/issues" 14 | }, 15 | "main": "src/index.js", 16 | "scripts": { 17 | "build": "ncc build src/index.js -o dist", 18 | "update": "ncu --upgrade", 19 | "release": "standard-version", 20 | "push": "git push --tags origin main" 21 | }, 22 | "dependencies": { 23 | "@actions/core": "^1.10.0", 24 | "@actions/github": "^5.1.1", 25 | "joi": "^17.7.0" 26 | }, 27 | "devDependencies": { 28 | "@vercel/ncc": "^0.34.0", 29 | "npm-check-updates": "^16.4.3", 30 | "prettier": "^2.8.0", 31 | "standard-version": "^9.5.0" 32 | }, 33 | "engines": { 34 | "node": ">=16.0.0" 35 | }, 36 | "keywords": [ 37 | "github", 38 | "issues", 39 | "projects", 40 | "project columns", 41 | "project cards", 42 | "to do", 43 | "in progress", 44 | "done", 45 | "github projects", 46 | "open issues", 47 | "close issues", 48 | "automation", 49 | "github actions", 50 | "project management", 51 | "bot" 52 | ], 53 | "private": true 54 | } 55 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const core = require('@actions/core'); 2 | const github = require('@actions/github'); 3 | 4 | const schema = require('./schema'); 5 | 6 | async function run() { 7 | try { 8 | const config = getConfig(); 9 | const client = github.getOctokit(config['github-token']); 10 | 11 | const app = new App(config, client); 12 | 13 | const output = await app.processCard(); 14 | 15 | core.debug('Setting output (issues)'); 16 | if (output) { 17 | core.setOutput('issues', JSON.stringify(output)); 18 | 19 | if (config['log-output']) { 20 | core.info('Output (issues):'); 21 | core.info(JSON.stringify(output, null, 2)); 22 | } 23 | } else { 24 | core.setOutput('issues', ''); 25 | } 26 | } catch (err) { 27 | core.setFailed(err); 28 | } 29 | } 30 | 31 | class App { 32 | constructor(config, client) { 33 | this.config = config; 34 | this.client = client; 35 | } 36 | 37 | async processCard() { 38 | const payload = github.context.payload; 39 | 40 | if (payload.sender.type === 'Bot') { 41 | return; 42 | } 43 | 44 | const contentUrl = payload.project_card.content_url || ''; 45 | const match = contentUrl.match(/\/repos\/(.+)\/(.+)\/issues\/([0-9]+)$/); 46 | if (!match) { 47 | return; 48 | } 49 | const [owner, repo, issue_number] = match.slice(1); 50 | const issue = {owner, repo, issue_number}; 51 | 52 | const openIssueColumns = this.config['open-issue-columns']; 53 | const closedIssueColumns = this.config['closed-issue-columns']; 54 | 55 | const { 56 | data: {name: columnName} 57 | } = await this.client.rest.projects.getColumn({ 58 | column_id: payload.project_card.column_id 59 | }); 60 | 61 | let newState; 62 | if (openIssueColumns.includes(columnName)) { 63 | newState = 'open'; 64 | } else if (closedIssueColumns.includes(columnName)) { 65 | newState = 'closed'; 66 | } 67 | if (!newState) { 68 | return; 69 | } 70 | 71 | const {data: issueData} = await this.client.rest.issues.get(issue); 72 | if (issueData.state === newState || issueData.pull_request) { 73 | return; 74 | } 75 | 76 | core.debug('Updating issue state'); 77 | await this.client.rest.issues.update({...issue, state: newState}); 78 | 79 | return [{...issue, state: newState}]; 80 | } 81 | } 82 | 83 | function getConfig() { 84 | const input = Object.fromEntries( 85 | Object.keys(schema.describe().keys).map(item => [item, core.getInput(item)]) 86 | ); 87 | 88 | const {error, value} = schema.validate(input, {abortEarly: false}); 89 | if (error) { 90 | throw error; 91 | } 92 | 93 | return value; 94 | } 95 | 96 | run(); 97 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [3.0.0](https://github.com/dessant/issue-states/compare/v2.2.0...v3.0.0) (2022-12-04) 6 | 7 | 8 | ### ⚠ BREAKING CHANGES 9 | 10 | * the action now requires Node.js 16 11 | 12 | ### Bug Fixes 13 | 14 | * update dependencies ([fcdd3b4](https://github.com/dessant/issue-states/commit/fcdd3b4fdb9a29ed97af9fe8c87d4120d65d6f8e)) 15 | * update docs ([52df266](https://github.com/dessant/issue-states/commit/52df2667583607628dd132efe0f67463dda4cbc3)) 16 | 17 | ## [2.2.0](https://github.com/dessant/issue-states/compare/v2.1.1...v2.2.0) (2021-10-03) 18 | 19 | 20 | ### Features 21 | 22 | * add option for logging output parameters ([54551cb](https://github.com/dessant/issue-states/commit/54551cb00c04d17949509251854b8e1d628c0124)) 23 | 24 | ### [2.1.1](https://github.com/dessant/issue-states/compare/v2.1.0...v2.1.1) (2021-07-09) 25 | 26 | 27 | ### Bug Fixes 28 | 29 | * update GitHub API calls ([b14c1e5](https://github.com/dessant/issue-states/commit/b14c1e53f4bc45f94ba2ff6d9115d0c8bfa85b46)) 30 | 31 | ## [2.1.0](https://github.com/dessant/issue-states/compare/v2.0.0...v2.1.0) (2021-07-09) 32 | 33 | 34 | ### Features 35 | 36 | * make github-token optional and document the use of personal access tokens ([453ac36](https://github.com/dessant/issue-states/commit/453ac3617d92732e1a800f1a010300d750c6dbc0)) 37 | 38 | 39 | ### Bug Fixes 40 | 41 | * declare required permissions ([5721b51](https://github.com/dessant/issue-states/commit/5721b514c82e1be6db9cfa42cfa8dbee1dce5dba)) 42 | 43 | ## [2.0.0](https://github.com/dessant/issue-states/compare/v1.0.1...v2.0.0) (2021-01-21) 44 | 45 | 46 | ### ⚠ BREAKING CHANGES 47 | 48 | * The deployment method and configuration options have changed. 49 | 50 | ### Features 51 | 52 | * move to GitHub Actions ([dc414db](https://github.com/dessant/issue-states/commit/dc414dbff89e3e96131d3c3abe29e98508eb1099)) 53 | 54 | ### [1.0.1](https://github.com/dessant/issue-states/compare/v1.0.0...v1.0.1) (2019-10-25) 55 | 56 | 57 | ### Bug Fixes 58 | 59 | * update dependencies ([d27ab6d](https://github.com/dessant/issue-states/commit/d27ab6d6bab4b3f90cc56c4abf422d8ac684ba63)) 60 | 61 | ## [1.0.0](https://github.com/dessant/issue-states/compare/v0.3.4...v1.0.0) (2019-06-10) 62 | 63 | 64 | ### Features 65 | 66 | * update dependencies ([fccb39c](https://github.com/dessant/issue-states/commit/fccb39c)) 67 | 68 | 69 | ### BREAKING CHANGES 70 | 71 | * probot < 9.2.13 no longer supported. 72 | 73 | 74 | 75 | 76 | ## [0.3.4](https://github.com/dessant/issue-states/compare/v0.3.3...v0.3.4) (2019-01-20) 77 | 78 | 79 | ### Bug Fixes 80 | 81 | * update dependencies ([2045b1f](https://github.com/dessant/issue-states/commit/2045b1f)) 82 | 83 | 84 | 85 | 86 | ## [0.3.3](https://github.com/dessant/issue-states/compare/v0.3.2...v0.3.3) (2019-01-20) 87 | 88 | 89 | ### Bug Fixes 90 | 91 | * apply stricter config validation ([06ccf1e](https://github.com/dessant/issue-states/commit/06ccf1e)) 92 | 93 | 94 | 95 | 96 | ## [0.3.2](https://github.com/dessant/issue-states/compare/v0.3.1...v0.3.2) (2018-10-03) 97 | 98 | 99 | ### Bug Fixes 100 | 101 | * allow newer versions of node ([7e34e45](https://github.com/dessant/issue-states/commit/7e34e45)) 102 | 103 | 104 | 105 | 106 | ## [0.3.1](https://github.com/dessant/issue-states/compare/v0.3.0...v0.3.1) (2018-07-24) 107 | 108 | 109 | ### Bug Fixes 110 | 111 | * pass owner and repo to sendMessage ([1a4f609](https://github.com/dessant/issue-states/commit/1a4f609)) 112 | 113 | 114 | 115 | 116 | # [0.3.0](https://github.com/dessant/issue-states/compare/v0.2.0...v0.3.0) (2018-07-23) 117 | 118 | 119 | ### Features 120 | 121 | * notify maintainers about configuration errors ([5d681d8](https://github.com/dessant/issue-states/commit/5d681d8)) 122 | 123 | 124 | 125 | 126 | # [0.2.0](https://github.com/dessant/issue-states/compare/v0.1.0...v0.2.0) (2018-06-27) 127 | 128 | 129 | ### Features 130 | 131 | * log additional data and add DRY_RUN env var ([cf70fac](https://github.com/dessant/issue-states/commit/cf70fac)) 132 | 133 | 134 | 135 | 136 | # 0.1.0 (2018-06-15) 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Issue States 2 | 3 | > :warning: The action does not work with modern projects: https://github.com/dessant/issue-states/issues/2 4 | 5 | Issue States is a GitHub Action that closes or reopens issues 6 | when they are moved to a project column. 7 | 8 | ![](assets/screenshot.png) 9 | 10 | ## Supporting the Project 11 | 12 | The continued development of Issue States is made possible 13 | thanks to the support of awesome backers. If you'd like to join them, 14 | please consider contributing with 15 | [Patreon](https://armin.dev/go/patreon?pr=issue-states&src=repo), 16 | [PayPal](https://armin.dev/go/paypal?pr=issue-states&src=repo) or 17 | [Bitcoin](https://armin.dev/go/bitcoin?pr=issue-states&src=repo). 18 | 19 | ## Usage 20 | 21 | 1. Create the `issue-states.yml` workflow file in the `.github/workflows` 22 | directory, use one of the [example workflows](#examples) to get started 23 | 2. Start adding or moving issues to the project columns declared 24 | in `open-issue-columns` and `closed-issue-columns` 25 | 26 | Issues which were already in the respective columns before the action 27 | was set up will not be processed. To process these issues, 28 | move them to a different column, then move them back. 29 | 30 | Care must be taken during the use of the action to not conflict 31 | with project automation presets on GitHub. 32 | 33 | ### Inputs 34 | 35 | The action can be configured using [input parameters](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepswith). 36 | 37 | 38 | - **`github-token`** 39 | - GitHub access token, value must be `${{ github.token }}` or an encrypted 40 | secret that contains a [personal access token](#using-a-personal-access-token) 41 | - Optional, defaults to `${{ github.token }}` 42 | - **`open-issue-columns`** 43 | - Reopen issues that are moved to these project columns, value must be 44 | a comma separated list of project columns 45 | - Optional, defaults to `''` 46 | - **`closed-issue-columns`** 47 | - Close issues that are moved to these project columns, value must be 48 | a comma separated list of project columns 49 | - Optional, defaults to `Closed, Done` 50 | - **`log-output`** 51 | - Log output parameters, value must be either `true` or `false` 52 | - Optional, defaults to `false` 53 | 54 | ### Outputs 55 | 56 | 57 | - **`issues`** 58 | - Issues that have been either closed or reopened, value is a JSON string 59 | in the form of `[{"owner": "actions", "repo": "toolkit", "issue_number": 1, 60 | "state": "closed"}]`, value of `state` is either `open` or `closed` 61 | - Defaults to `''` 62 | 63 | ## Examples 64 | 65 | The following workflow will close issues when they are moved 66 | to the `Closed` or `Done` project column. 67 | 68 | 69 | ```yaml 70 | name: 'Issue States' 71 | 72 | on: 73 | project_card: 74 | types: [created, edited, moved] 75 | 76 | permissions: 77 | repository-projects: read 78 | issues: write 79 | pull-requests: write 80 | 81 | jobs: 82 | action: 83 | runs-on: ubuntu-latest 84 | steps: 85 | - uses: dessant/issue-states@v3 86 | ``` 87 | 88 | ### Available input parameters 89 | 90 | This workflow declares all the available input parameters of the action 91 | and their default values. Any of the parameters can be omitted. 92 | 93 | 94 | ```yaml 95 | name: 'Issue States' 96 | 97 | on: 98 | project_card: 99 | types: [created, edited, moved] 100 | 101 | permissions: 102 | repository-projects: read 103 | issues: write 104 | pull-requests: write 105 | 106 | jobs: 107 | action: 108 | runs-on: ubuntu-latest 109 | steps: 110 | - uses: dessant/issue-states@v3 111 | with: 112 | github-token: ${{ github.token }} 113 | open-issue-columns: '' 114 | closed-issue-columns: 'Closed, Done' 115 | log-output: false 116 | ``` 117 | 118 | ### Using a personal access token 119 | 120 | The action uses an installation access token by default to interact with GitHub. 121 | You may also authenticate with a personal access token to perform actions 122 | as a GitHub user instead of the `github-actions` app. 123 | 124 | Create a [personal access token](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token) 125 | with the `repo` or `public_repo` scopes enabled, and add the token as an 126 | [encrypted secret](https://docs.github.com/en/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository) 127 | for the repository or organization, then provide the action with the secret 128 | using the `github-token` input parameter. 129 | 130 | 131 | ```yaml 132 | steps: 133 | - uses: dessant/issue-states@v3 134 | with: 135 | github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} 136 | ``` 137 | 138 | ## License 139 | 140 | Copyright (c) 2018-2023 Armin Sebastian 141 | 142 | This software is released under the terms of the MIT License. 143 | See the [LICENSE](LICENSE) file for further information. 144 | --------------------------------------------------------------------------------