├── .eslintignore ├── .eslintrc.json ├── .github ├── FUNDING.yml ├── release-drafter.yml └── workflows │ ├── release-note.yml │ └── test.yml ├── .gitignore ├── .node-version ├── .prettierignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── __tests__ └── utils.test.ts ├── action.yml ├── actions_console.png ├── check_result.png ├── dist └── index.js ├── jest.config.js ├── package-lock.json ├── package.json ├── renovate.json5 ├── src ├── main.ts └── utils.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | lib/ 3 | node_modules/ -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["jest", "@typescript-eslint"], 3 | "extends": ["plugin:github/es6"], 4 | "parser": "@typescript-eslint/parser", 5 | "parserOptions": { 6 | "ecmaVersion": 9, 7 | "sourceType": "module", 8 | "project": "./tsconfig.json" 9 | }, 10 | "rules": { 11 | "eslint-comments/no-use": "off", 12 | "import/no-namespace": "off", 13 | "no-unused-vars": "off", 14 | "@typescript-eslint/no-unused-vars": "error", 15 | "@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}], 16 | "@typescript-eslint/no-require-imports": "error", 17 | "@typescript-eslint/array-type": "error", 18 | "@typescript-eslint/await-thenable": "error", 19 | "@typescript-eslint/ban-ts-ignore": "error", 20 | "camelcase": "off", 21 | "@typescript-eslint/camelcase": "error", 22 | "@typescript-eslint/class-name-casing": "error", 23 | "@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}], 24 | "@typescript-eslint/func-call-spacing": ["error", "never"], 25 | "@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"], 26 | "@typescript-eslint/no-array-constructor": "error", 27 | "@typescript-eslint/no-empty-interface": "error", 28 | "@typescript-eslint/no-explicit-any": "error", 29 | "@typescript-eslint/no-extraneous-class": "error", 30 | "@typescript-eslint/no-for-in-array": "error", 31 | "@typescript-eslint/no-inferrable-types": "error", 32 | "@typescript-eslint/no-misused-new": "error", 33 | "@typescript-eslint/no-namespace": "error", 34 | "@typescript-eslint/no-non-null-assertion": "warn", 35 | "@typescript-eslint/no-object-literal-type-assertion": "error", 36 | "@typescript-eslint/no-unnecessary-qualifier": "error", 37 | "@typescript-eslint/no-unnecessary-type-assertion": "error", 38 | "@typescript-eslint/no-useless-constructor": "error", 39 | "@typescript-eslint/no-var-requires": "error", 40 | "@typescript-eslint/prefer-for-of": "warn", 41 | "@typescript-eslint/prefer-function-type": "warn", 42 | "@typescript-eslint/prefer-includes": "error", 43 | "@typescript-eslint/prefer-interface": "error", 44 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 45 | "@typescript-eslint/promise-function-async": "error", 46 | "@typescript-eslint/require-array-sort-compare": "error", 47 | "@typescript-eslint/restrict-plus-operands": "error", 48 | "semi": "off", 49 | "@typescript-eslint/semi": ["error", "never"], 50 | "@typescript-eslint/type-annotation-spacing": "error", 51 | "@typescript-eslint/unbound-method": "error" 52 | }, 53 | "env": { 54 | "node": true, 55 | "es6": true, 56 | "jest/globals": true 57 | } 58 | } -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: kentaro-m 2 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | template: | 2 | ## Changes 3 | 4 | $CHANGES -------------------------------------------------------------------------------- /.github/workflows/release-note.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | update_release_draft: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: release-drafter/release-drafter@v6 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push 4 | 5 | jobs: 6 | build-and-test: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: actions/setup-node@v4 11 | with: 12 | node-version: '16.x' 13 | - name: npm install, build, lint and test 14 | run: | 15 | npm install 16 | npm run build 17 | npm run lint 18 | npm test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | node_modules 3 | 4 | # Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | .env.test 71 | 72 | # parcel-bundler cache (https://parceljs.org/) 73 | .cache 74 | 75 | # next.js build output 76 | .next 77 | 78 | # nuxt.js build output 79 | .nuxt 80 | 81 | # vuepress build output 82 | .vuepress/dist 83 | 84 | # Serverless directories 85 | .serverless/ 86 | 87 | # FuseBox cache 88 | .fusebox/ 89 | 90 | # DynamoDB Local files 91 | .dynamodb/ 92 | 93 | # OS metadata 94 | .DS_Store 95 | Thumbs.db 96 | 97 | # Ignore built ts files 98 | __tests__/runner/* 99 | lib/**/* -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 16.20.2 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | lib/ 3 | node_modules/ 4 | __tests__/ -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": false, 9 | "arrowParens": "avoid", 10 | "parser": "typescript" 11 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Kentaro Matsushita 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Task Completed Checker Action 2 | A GitHub action that checks if all tasks are completed in the pull requests. 3 | 4 | ## :arrow_forward: Usage 5 | 6 | ### Create a workflow 7 | ```yml 8 | name: 'PR Tasks Completed Check' 9 | on: 10 | pull_request: 11 | types: [opened, edited] 12 | 13 | jobs: 14 | task-check: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: kentaro-m/task-completed-checker-action@v0.1.2 18 | with: 19 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 20 | ``` 21 | 22 | ### Check whether tasks are completed 23 | Add a pull request template to your repository (`.github/pull_request_template.md`). 24 | 25 | For example: 26 | ```markdown 27 | ## Issue Type 28 | 29 | - [ ] Bug 30 | - [ ] Document 31 | - [ ] Enhancement 32 | 33 | 34 | ## Checklist 35 | - [x] I have read the [CONTRIBUTING.md]() 36 | - [x] I have made corresponding changes to the documentation 37 | - [x] My changes generate no lint errors 38 | - [x] I have added tests that prove my fix is effective or that my feature works 39 | - [x] New and existing unit tests pass locally with my changes 40 | ``` 41 | 42 | Create a pull request that contained tasks list to your repository and will be started automatically a workflow to check whether tasks are completed. 43 | 44 | Every time edit a description of a pull request will be started automatically a new workflow to check. 45 | 46 | ![Check whether tasks are completed](check_result.png) 47 | 48 | You can check a list of completed tasks and uncompleted tasks at the Actions page. 49 | 50 | ![Check a list of completed/uncompleted tasks](actions_console.png) 51 | 52 | ### Ignore checks whether tasks are completed 53 | Please surround the task list with `` and `` for ignoring checks whether tasks are completed. 54 | 55 | ```markdown 56 | ## Issue Type 57 | 58 | - [ ] Bug 59 | - [ ] Document 60 | - [x] Enhancement 61 | 62 | 63 | ## Checklist 64 | - [x] I have read the [CONTRIBUTING.md]() 65 | - [x] I have made corresponding changes to the documentation 66 | - [x] My changes generate no lint errors 67 | - [x] I have added tests that prove my fix is effective or that my feature works 68 | - [x] New and existing unit tests pass locally with my changes 69 | ``` 70 | 71 | ## :memo: Licence 72 | MIT -------------------------------------------------------------------------------- /__tests__/utils.test.ts: -------------------------------------------------------------------------------- 1 | import {removeIgnoreTaskLitsText, createTaskListText} from '../src/utils' 2 | 3 | describe('removeIgnoreTaskLitsText', () => { 4 | it('removes multiple ignore task list from task list text.', () => { 5 | const text = `## Issue Type 6 | 7 | - [ ] Bug 8 | - [ ] Document 9 | - [x] Enhancement Feature 10 | 11 | 12 | ## Checklist 13 | - [x] I have read the [CONTRIBUTING.md]() 14 | - [x] I have made corresponding changes to the documentation 15 | - [x] My changes generate no lint errors 16 | - [x] I have added tests that prove my fix is effective or that my feature works 17 | - [x] New and existing unit tests pass locally with my changes` 18 | 19 | const result = removeIgnoreTaskLitsText(text) 20 | 21 | expect(result).toEqual(`## Issue Type 22 | 23 | 24 | ## Checklist 25 | - [x] I have read the [CONTRIBUTING.md]() 26 | - [x] I have made corresponding changes to the documentation 27 | - [x] My changes generate no lint errors 28 | - [x] I have added tests that prove my fix is effective or that my feature works 29 | - [x] New and existing unit tests pass locally with my changes`) 30 | }) 31 | 32 | it('removes single ignore task list from task list text.', () => { 33 | const text = ` 34 | - [ ] foo 35 | 36 | - [x] bar` 37 | 38 | const result = removeIgnoreTaskLitsText(text) 39 | 40 | expect(result).toEqual(` 41 | - [x] bar`) 42 | }) 43 | 44 | it('skips remove process if task list text does not contain ignore task list.', () => { 45 | const text = '- [x] bar' 46 | 47 | const result = removeIgnoreTaskLitsText(text) 48 | 49 | expect(result).toEqual('- [x] bar') 50 | }) 51 | }) 52 | 53 | describe('createTaskListText', () => { 54 | it('creates a list of completed tasks', () => { 55 | const text = `## Issue Type 56 | 57 | 58 | ## Checklist 59 | - [x] I have read the [CONTRIBUTING.md]() 60 | - [x] I have made corresponding changes to the documentation 61 | - [x] My changes generate no lint errors 62 | - [x] I have added tests that prove my fix is effective or that my feature works 63 | - [x] New and existing unit tests pass locally with my changes` 64 | 65 | const result = createTaskListText(text) 66 | 67 | expect(result).toEqual(`## :white_check_mark: Completed Tasks 68 | - [x] I have read the [CONTRIBUTING.md]() 69 | - [x] I have made corresponding changes to the documentation 70 | - [x] My changes generate no lint errors 71 | - [x] I have added tests that prove my fix is effective or that my feature works 72 | - [x] New and existing unit tests pass locally with my changes 73 | `) 74 | }) 75 | 76 | it('creates a list of completed tasks and uncompleted tasks', () => { 77 | const text = `## Issue Type 78 | 79 | 80 | ## Checklist 81 | - [x] I have read the [CONTRIBUTING.md]() 82 | - [ ] I have made corresponding changes to the documentation 83 | - [x] My changes generate no lint errors 84 | - [ ] I have added tests that prove my fix is effective or that my feature works 85 | - [x] New and existing unit tests pass locally with my changes` 86 | 87 | const result = createTaskListText(text) 88 | 89 | expect(result).toEqual(`## :white_check_mark: Completed Tasks 90 | - [x] I have read the [CONTRIBUTING.md]() 91 | - [x] My changes generate no lint errors 92 | - [x] New and existing unit tests pass locally with my changes 93 | ## :x: Uncompleted Tasks 94 | - [ ] I have made corresponding changes to the documentation 95 | - [ ] I have added tests that prove my fix is effective or that my feature works 96 | `) 97 | }) 98 | }) 99 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Task Completed Checker' 2 | description: 'An action that checks if all tasks are completed in the pull requests.' 3 | author: 'kentaro-m' 4 | runs: 5 | using: 'node16' 6 | main: 'dist/index.js' 7 | inputs: 8 | repo-token: 9 | description: 'The GITHUB_TOKEN secret' 10 | required: true 11 | branding: 12 | icon: 'check-square' 13 | color: 'green' 14 | -------------------------------------------------------------------------------- /actions_console.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentaro-m/task-completed-checker-action/7246f8a7d8e1f0206028cd1427217b4ac3d3d33a/actions_console.png -------------------------------------------------------------------------------- /check_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kentaro-m/task-completed-checker-action/7246f8a7d8e1f0206028cd1427217b4ac3d3d33a/check_result.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | moduleFileExtensions: ['js', 'ts'], 4 | testEnvironment: 'node', 5 | testMatch: ['**/*.test.ts'], 6 | testRunner: 'jest-circus/runner', 7 | transform: { 8 | '^.+\\.ts$': 'ts-jest' 9 | }, 10 | verbose: true 11 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "task-completed-checker-action", 3 | "version": "0.1.2", 4 | "description": "An action that checks if all tasks are completed in the pull requests.", 5 | "main": "lib/main.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "format": "prettier --write **/*.ts", 9 | "format-check": "prettier --check **/*.ts", 10 | "lint": "eslint src/**/*.ts", 11 | "pack": "ncc build", 12 | "test": "jest", 13 | "all": "npm run build && npm run format && npm run lint && npm run pack && npm test -- --passWithNoTests" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/actions/typescript-action.git" 18 | }, 19 | "keywords": [ 20 | "actions", 21 | "node", 22 | "setup" 23 | ], 24 | "author": "kentaro-m", 25 | "license": "MIT", 26 | "dependencies": { 27 | "@actions/core": "^1.2.6", 28 | "@actions/github": "^5.1.1" 29 | }, 30 | "devDependencies": { 31 | "@types/jest": "30.0.0", 32 | "@types/node": "22.18.6", 33 | "@typescript-eslint/parser": "2.34.0", 34 | "@zeit/ncc": "0.22.3", 35 | "eslint": "5.16.0", 36 | "eslint-plugin-github": "2.0.0", 37 | "eslint-plugin-jest": "22.21.0", 38 | "jest": "29.7.0", 39 | "jest-circus": "29.7.0", 40 | "js-yaml": "3.14.1", 41 | "prettier": "2.8.8", 42 | "ts-jest": "29.4.4", 43 | "typescript": "5.9.3" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "github>kentaro-m/renovate-config:default.json5" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | import * as github from '@actions/github' 3 | import {removeIgnoreTaskLitsText, createTaskListText} from './utils' 4 | 5 | async function run(): Promise { 6 | try { 7 | const body = github.context.payload.pull_request?.body 8 | 9 | const token = core.getInput('repo-token', {required: true}) 10 | const githubApi = github.getOctokit(token) 11 | const appName = 'Task Completed Checker' 12 | 13 | if (!body) { 14 | core.info('no task list and skip the process.') 15 | await githubApi.rest.checks.create({ 16 | name: appName, 17 | // eslint-disable-next-line @typescript-eslint/camelcase 18 | head_sha: github.context.payload.pull_request?.head.sha, 19 | status: 'completed', 20 | conclusion: 'success', 21 | // eslint-disable-next-line @typescript-eslint/camelcase 22 | completed_at: new Date().toISOString(), 23 | output: { 24 | title: appName, 25 | summary: 'No task list', 26 | text: 'No task list' 27 | }, 28 | owner: github.context.repo.owner, 29 | repo: github.context.repo.repo 30 | }) 31 | return 32 | } 33 | 34 | const result = removeIgnoreTaskLitsText(body) 35 | 36 | core.debug('creates a list of tasks which removed ignored task: ') 37 | core.debug(result) 38 | 39 | const isTaskCompleted = result.match(/(- \[[ ]\].+)/g) === null 40 | 41 | const text = createTaskListText(result) 42 | 43 | core.debug('creates a list of completed tasks and uncompleted tasks: ') 44 | core.debug(text) 45 | 46 | await githubApi.rest.checks.create({ 47 | name: appName, 48 | // eslint-disable-next-line @typescript-eslint/camelcase 49 | head_sha: github.context.payload.pull_request?.head.sha, 50 | status: 'completed', 51 | conclusion: isTaskCompleted ? 'success' : 'failure', 52 | // eslint-disable-next-line @typescript-eslint/camelcase 53 | completed_at: new Date().toISOString(), 54 | output: { 55 | title: appName, 56 | summary: isTaskCompleted 57 | ? 'All tasks are completed!' 58 | : 'Some tasks are uncompleted!', 59 | text 60 | }, 61 | owner: github.context.repo.owner, 62 | repo: github.context.repo.repo 63 | }) 64 | } catch (error) { 65 | if (error instanceof Error) { 66 | core.setFailed(error.message) 67 | } 68 | } 69 | } 70 | 71 | run() 72 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | export function removeIgnoreTaskLitsText(text: string): string { 2 | return text.replace( 3 | /[\s| ]*(- \[[x| ]\] .+[\s| ]*)+/g, 4 | '' 5 | ) 6 | } 7 | 8 | export function createTaskListText(body: string): string { 9 | const completedTasks = body.match(/(- \[[x]\].+)/g) 10 | const uncompletedTasks = body.match(/(- \[[ ]\].+)/g) 11 | 12 | let text = '' 13 | 14 | if (completedTasks !== null) { 15 | for (let index = 0; index < completedTasks.length; index++) { 16 | if (index === 0) { 17 | text += '## :white_check_mark: Completed Tasks\n' 18 | } 19 | text += `${completedTasks[index]}\n` 20 | } 21 | } 22 | 23 | if (uncompletedTasks !== null) { 24 | for (let index = 0; index < uncompletedTasks.length; index++) { 25 | if (index === 0) { 26 | text += '## :x: Uncompleted Tasks\n' 27 | } 28 | text += `${uncompletedTasks[index]}\n` 29 | } 30 | } 31 | 32 | return text 33 | } 34 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 4 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 5 | "outDir": "./lib", /* Redirect output structure to the directory. */ 6 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 7 | "strict": true, /* Enable all strict type-checking options. */ 8 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 9 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 10 | }, 11 | "exclude": ["node_modules", "**/*.test.ts"] 12 | } 13 | --------------------------------------------------------------------------------