├── .gitignore ├── package.json ├── .github └── workflows │ └── main.yml ├── Readme.md ├── action.yml ├── LICENSE └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "action-pr-title", 3 | "version": "1.0.0", 4 | "description": "Github action to enforce naming convention on Pull Request titles", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "node index.js", 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [ 11 | "github", 12 | "actions" 13 | ], 14 | "author": "deepakputhraya", 15 | "license": "MIT", 16 | "dependencies": { 17 | "@actions/core": "^1.1.3", 18 | "@actions/github": "^1.1.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: main 2 | # This workflow is triggered on pushes to the repository. 3 | on: [pull_request] 4 | 5 | jobs: 6 | build: 7 | # Job name is Greeting 8 | name: Check 9 | # This job runs on Linux 10 | runs-on: 'ubuntu-latest' 11 | steps: 12 | - uses: actions/checkout@master 13 | - uses: actions/setup-node@v1 14 | with: 15 | node-version: '10.x' # This step prints an output (time) from the previous step's action. 16 | - run: npm install 17 | - name: Validate 18 | run: node index.js 19 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Branch naming rules 2 | GitHub Actions status 3 | 4 | Github action to enforce Pull Request title conventions 5 | 6 | ## Usage 7 | 8 | See [action.yml](./action.yml) 9 | 10 | ```yaml 11 | steps: 12 | - uses: deepakputhraya/action-pr-title@master 13 | with: 14 | regex: '([a-z])+\/([a-z])+' # Regex the title should match. 15 | allowed_prefixes: 'feature,stable,fix' # title should start with the given prefix 16 | prefix_case_sensitive: false # title prefix are case insensitive 17 | min_length: 5 # Min length of the title 18 | max_length: 20 # Max length of the title 19 | ``` 20 | 21 | ## License 22 | The scripts and documentation in this project are released under the [MIT License](./LICENSE) 23 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Pull Request title rules' 2 | description: 'Github action to enforce Pull Request title conventions' 3 | author: 'deepakputhraya' 4 | inputs: 5 | regex: 6 | description: 'Regex to validate the pull request title' 7 | default: '.+' 8 | allowed_prefixes: 9 | description: 'Comma separated list of prefix allowed to be used in title. eg: feature,hotfix,JIRA-' 10 | default: '' 11 | prefix_case_sensitive: 12 | description: 'Are the allowed prefixes case sensitive?' 13 | default: false 14 | min_length: 15 | description: 'Min length of title' 16 | default: 1 17 | max_length: 18 | description: 'Max length of title. -1 to ignore the rule' 19 | default: -1 20 | 21 | runs: 22 | using: 'node12' 23 | main: 'index.js' 24 | branding: 25 | icon: 'alert-triangle' 26 | color: 'gray-dark' 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 GitHub, Inc. and contributors 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const core = require('@actions/core'); 2 | const github = require('@actions/github'); 3 | 4 | const validEvent = ['pull_request']; 5 | 6 | function validateTitlePrefix(title, prefix, caseSensitive) { 7 | if (!caseSensitive) { 8 | prefix = prefix.toLowerCase(); 9 | title = prefix.toLowerCase(); 10 | } 11 | return title.startsWith(prefix); 12 | } 13 | 14 | async function run() { 15 | try { 16 | const eventName = github.context.eventName; 17 | core.info(`Event name: ${eventName}`); 18 | if (validEvent.indexOf(eventName) < 0) { 19 | core.setFailed(`Invalid event: ${eventName}`); 20 | return; 21 | } 22 | 23 | const title = github.context.payload.pull_request.title; 24 | core.info(`Pull Request title: "${title}"`); 25 | // Check if title pass regex 26 | const regex = RegExp(core.getInput('regex')); 27 | core.info(`Regex: ${regex}`); 28 | if (!regex.test(title)) { 29 | core.setFailed(`Pull Request title "${title}" failed to pass match regex - ${regex}`); 30 | return 31 | } 32 | 33 | // Check min length 34 | const minLen = parseInt(core.getInput('min_length')); 35 | if (title.length < minLen) { 36 | core.setFailed(`Pull Request title "${title}" is smaller than min length specified - ${minLen}`); 37 | return 38 | } 39 | 40 | // Check max length 41 | const maxLen = parseInt(core.getInput('max_length')); 42 | if (maxLen > 0 && title.length > maxLen) { 43 | core.setFailed(`Pull Request title "${title}" is greater than max length specified - ${maxLen}`); 44 | return 45 | } 46 | 47 | // Check if title starts with a prefix 48 | const prefixes = core.getInput('allowed_prefixes'); 49 | const prefixCaseSensitive = (core.getInput('prefix_case_sensitive') === 'true'); 50 | core.info(`Allowed Prefixes: ${prefixes}`); 51 | if (prefixes.length > 0 && !prefixes.split(',').some((el) => validateTitlePrefix(title, el, prefixCaseSensitive))) { 52 | core.setFailed(`Pull Request title "${title}" did not match any of the prefixes - ${prefixes}`); 53 | return 54 | } 55 | 56 | } catch (error) { 57 | core.setFailed(error.message); 58 | } 59 | } 60 | 61 | run(); 62 | --------------------------------------------------------------------------------