├── .all-contributorsrc
├── .eslintignore
├── .eslintrc.js
├── .github
└── workflows
│ └── pr-branch-checks.yml
├── .gitignore
├── .nvmrc
├── .prettierignore
├── .prettierrc.js
├── LICENSE
├── README.md
├── __tests__
└── utils.test.ts
├── action.yml
├── jest.config.js
├── lib
└── index.js
├── lint-staged.config.js
├── package-lock.json
├── package.json
├── scripts
├── pre-commit.sh
└── pre-push.sh
├── src
├── constants.ts
├── main.ts
├── types.ts
└── utils.ts
├── tsconfig.eslint.json
└── tsconfig.json
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "README.md"
4 | ],
5 | "imageSize": 100,
6 | "commit": false,
7 | "contributors": [
8 | {
9 | "login": "rajanand02",
10 | "name": "Raj Anand",
11 | "avatar_url": "https://avatars3.githubusercontent.com/u/4851763?v=4",
12 | "profile": "http://hacktivist.in",
13 | "contributions": [
14 | "code",
15 | "review",
16 | "ideas"
17 | ]
18 | },
19 | {
20 | "login": "rheaditi",
21 | "name": "Aditi Mohanty",
22 | "avatar_url": "https://avatars3.githubusercontent.com/u/6426069?v=4",
23 | "profile": "https://aditimohanty.com/?utm_source=github&utm_medium=documentation-allcontributors&utm_content=jira-lint",
24 | "contributions": [
25 | "code",
26 | "doc",
27 | "infra"
28 | ]
29 | },
30 | {
31 | "login": "dustman9000",
32 | "name": "Dustin Row",
33 | "avatar_url": "https://avatars0.githubusercontent.com/u/3944352?v=4",
34 | "profile": "https://github.com/dustman9000",
35 | "contributions": [
36 | "review"
37 | ]
38 | },
39 | {
40 | "login": "richardlhao",
41 | "name": "richardlhao",
42 | "avatar_url": "https://avatars1.githubusercontent.com/u/60636550?v=4",
43 | "profile": "https://github.com/richardlhao",
44 | "contributions": [
45 | "code"
46 | ]
47 | },
48 | {
49 | "login": "nimeshjm",
50 | "name": "Nimesh Manmohanlal",
51 | "avatar_url": "https://avatars3.githubusercontent.com/u/2178497?v=4",
52 | "profile": "https://www.nimeshjm.com/",
53 | "contributions": [
54 | "doc"
55 | ]
56 | },
57 | {
58 | "login": "lwaddicor",
59 | "name": "Lewis Waddicor",
60 | "avatar_url": "https://avatars2.githubusercontent.com/u/10589338?v=4",
61 | "profile": "https://github.com/lwaddicor",
62 | "contributions": [
63 | "code"
64 | ]
65 | }
66 | ],
67 | "contributorsPerLine": 7,
68 | "projectName": "jira-lint",
69 | "projectOwner": "ClearTax",
70 | "repoType": "github",
71 | "repoHost": "https://github.com",
72 | "skipCi": true,
73 | "commitConvention": "none"
74 | }
75 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist/
2 | lib/
3 | node_modules/
4 | !.*.js
5 | !*.config.js
6 |
7 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
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.eslint.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-non-null-assertion': 'warn',
34 | '@typescript-eslint/no-unnecessary-qualifier': 'error',
35 | '@typescript-eslint/no-unnecessary-type-assertion': 'error',
36 | '@typescript-eslint/no-useless-constructor': 'error',
37 | '@typescript-eslint/no-var-requires': 'error',
38 | '@typescript-eslint/prefer-for-of': 'warn',
39 | '@typescript-eslint/prefer-function-type': 'warn',
40 | '@typescript-eslint/prefer-includes': 'error',
41 | '@typescript-eslint/prefer-string-starts-ends-with': 'error',
42 | '@typescript-eslint/promise-function-async': 'error',
43 | '@typescript-eslint/require-array-sort-compare': 'error',
44 | '@typescript-eslint/restrict-plus-operands': 'error',
45 | 'semi': 'off',
46 | '@typescript-eslint/semi': ['error', 'always'],
47 | '@typescript-eslint/type-annotation-spacing': 'error',
48 | '@typescript-eslint/unbound-method': 'error',
49 | '@typescript-eslint/no-namespace': 'off',
50 | 'no-console': 'off',
51 | 'jsdoc/require-param-type': 'off',
52 | 'jsdoc/require-param': 'off',
53 | 'jsdoc/require-returns': 'off',
54 | 'jsdoc/check-examples': 'off',
55 | },
56 | env: {
57 | 'node': true,
58 | 'es6': true,
59 | 'jest/globals': true,
60 | },
61 | };
62 |
--------------------------------------------------------------------------------
/.github/workflows/pr-branch-checks.yml:
--------------------------------------------------------------------------------
1 | name: "lint, build & test"
2 | on: # rebuild any PRs and main branch changes (mirror few typescript-action conventions)
3 | pull_request:
4 | push:
5 | branches:
6 | - master
7 | - 'releases/*'
8 | jobs:
9 | build-test:
10 | runs-on: ubuntu-16.04
11 | steps:
12 | - uses: actions/checkout@v2
13 | - uses: actions/setup-node@v1
14 | name: setup node
15 | with:
16 | node-version: '13.x'
17 |
18 | - name: "install"
19 | run: npm ci
20 |
21 | - name: "lint"
22 | run: npm run lint
23 |
24 | - name: "build"
25 | run: npm run build
26 |
27 | - name: "check for uncommitted changes"
28 | # Ensure no diff when built on ci
29 | # ignore node_modules since dev/fresh ci deps installed.
30 | run: |
31 | git diff --exit-code --stat -- . ':!node_modules' \
32 | || (echo "##[error] found changed files after build. please 'npm run build'" \
33 | "and check in all changes" \
34 | && exit 1)
35 |
36 | - name: "archive lib when diff"
37 | uses: actions/upload-artifact@v1
38 | if: failure()
39 | with:
40 | name: ci-built-lib
41 | path: lib/index.js
42 |
43 | - name: "test"
44 | run: npm run test
45 |
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | __tests__/runner/*
2 |
3 | # Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore
4 | # Logs
5 | logs
6 | *.log
7 | npm-debug.log*
8 | yarn-debug.log*
9 | yarn-error.log*
10 | lerna-debug.log*
11 | node_modules
12 |
13 | # local file for testing
14 | src/localTest.ts
15 |
16 | # vs code custom settings
17 | .vscode
18 |
19 | # Diagnostic reports (https://nodejs.org/api/report.html)
20 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
21 |
22 | # Runtime data
23 | pids
24 | *.pid
25 | *.seed
26 | *.pid.lock
27 |
28 | # Directory for instrumented libs generated by jscoverage/JSCover
29 | lib-cov
30 |
31 | # Coverage directory used by tools like istanbul
32 | coverage
33 | *.lcov
34 |
35 | # nyc test coverage
36 | .nyc_output
37 |
38 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
39 | .grunt
40 |
41 | # Bower dependency directory (https://bower.io/)
42 | bower_components
43 |
44 | # node-waf configuration
45 | .lock-wscript
46 |
47 | # Compiled binary addons (https://nodejs.org/api/addons.html)
48 | build/Release
49 |
50 | # Dependency directories
51 | jspm_packages/
52 |
53 | # TypeScript v1 declaration files
54 | typings/
55 |
56 | # TypeScript cache
57 | *.tsbuildinfo
58 |
59 | # Optional npm cache directory
60 | .npm
61 |
62 | # Optional eslint cache
63 | .eslintcache
64 |
65 | # Optional REPL history
66 | .node_repl_history
67 |
68 | # Output of 'npm pack'
69 | *.tgz
70 |
71 | # Yarn Integrity file
72 | .yarn-integrity
73 |
74 | # dotenv environment variables file
75 | .env
76 | .env.test
77 |
78 | # parcel-bundler cache (https://parceljs.org/)
79 | .cache
80 |
81 | # next.js build output
82 | .next
83 |
84 | # nuxt.js build output
85 | .nuxt
86 |
87 | # vuepress build output
88 | .vuepress/dist
89 |
90 | # Serverless directories
91 | .serverless/
92 |
93 | # FuseBox cache
94 | .fusebox/
95 |
96 | # DynamoDB Local files
97 | .dynamodb/
98 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 13
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | dist/
2 | lib/
3 | node_modules/
4 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | printWidth: 120,
3 | semi: true,
4 | singleQuote: true,
5 | tabWidth: 2,
6 | trailingComma: 'es5',
7 | quoteProps: 'consistent',
8 | jsxSingleQuote: true,
9 | bracketSpacing: true,
10 | endOfLine: 'lf',
11 | };
12 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | The MIT License (MIT)
3 |
4 | Copyright (c) 2018 GitHub, Inc. and contributors
5 |
6 | Permission is hereby granted, free of charge, to any person obtaining a copy
7 | of this software and associated documentation files (the "Software"), to deal
8 | in the Software without restriction, including without limitation the rights
9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10 | copies of the Software, and to permit persons to whom the Software is
11 | furnished to do so, subject to the following conditions:
12 |
13 | The above copyright notice and this permission notice shall be included in
14 | all copies or substantial portions of the Software.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # jira-lint 🧹
2 |
3 | > A light-weight lint workflow when using GitHub along with [JIRA][jira] for project management.
4 | > Ported from [pivotal-lint](https://github.com/ClearTax/pivotal-lint/) for similar usage with Atlassian's Jira Software.
5 |
6 | 
7 | [](https://github.com/cleartax/jira-lint/blob/master/LICENSE.md)
8 | [](#contributors)
9 | 
10 |
11 | ---
12 |
13 |
14 |
15 | - [Installation](#installation)
16 | - [Semantic Versions](#semantic-versions)
17 | - [Features](#features)
18 | - [PR Status Checks](#pr-status-checks)
19 | - [PR Description & Labels](#pr-description--labels)
20 | - [Description](#description)
21 | - [Labels](#labels)
22 | - [Soft-validations via comments](#soft-validations-via-comments)
23 | - [Options](#options)
24 | - [`jira-token`](#jira-token)
25 | - [Skipping branches](#skipping-branches)
26 | - [Contributing](#contributing)
27 | - [FAQ](#faq)
28 | - [Contributors](#contributors)
29 |
30 |
31 |
32 | ## Installation
33 |
34 | To make `jira-lint` a part of your workflow, just add a `jira-lint.yml` file in your `.github/workflows/` directory in your GitHub repository.
35 |
36 | ```yml
37 | name: jira-lint
38 | on: [pull_request]
39 |
40 | jobs:
41 | jira-lint:
42 | runs-on: ubuntu-latest
43 | steps:
44 | - uses: cleartax/jira-lint@master
45 | name: jira-lint
46 | with:
47 | github-token: ${{ secrets.GITHUB_TOKEN }}
48 | jira-token: ${{ secrets.JIRA_TOKEN }}
49 | jira-base-url: https://your-domain.atlassian.net
50 | skip-branches: '^(production-release|master|release\/v\d+)$'
51 | skip-comments: true
52 | pr-threshold: 1000
53 | ```
54 |
55 | It can also be used as part of an existing workflow by adding it as a step. More information about the [options here](#options).
56 |
57 | ### Semantic Versions
58 |
59 | If you want more stability in versions of `jira-lint` than `@master` you can also use the [semantic releases for jira-lint](https://github.com/cleartax/jira-lint/releases).
60 |
61 | Example:
62 |
63 | ```yaml
64 | # ...
65 | steps:
66 | - uses: cleartax/jira-lint@v0.0.1
67 | name: jira-lint
68 | # ...
69 | ```
70 |
71 | ## Features
72 |
73 | ### PR Status Checks
74 |
75 | `jira-lint` adds a status check which helps you avoid merging PRs which are missing a valid Jira Issue Key in the branch name. It will use the [Jira API](https://developer.atlassian.com/cloud/jira/platform/rest/v3/) to validate a given key.
76 |
77 | ### PR Description & Labels
78 |
79 | #### Description
80 |
81 | When a PR passes the above check, `jira-lint` will also add the issue details to the top of the PR description. It will pick details such as the Issue summary, type, estimation points, status and labels and add them to the PR description.
82 |
83 | #### Labels
84 |
85 | `jira-lint` will automatically label PRs with:
86 |
87 | - A label based on the Jira Project name (the project the issue belongs to). For example, if your project name is `Escher` then it will add `escher` as a label.
88 | - `HOTFIX-PROD` - if the PR is raised against `production-release`.
89 | - `HOTFIX-PRE-PROD` - if the PR is raised against `release/v*`.
90 | - Jira issue type ([based on your project](https://confluence.atlassian.com/adminjiracloud/issue-types-844500742.html)).
91 |
92 |
93 |
94 |
95 | Issue details and labels added to a PR.
96 |
97 |
98 |
99 | #### Issue Status Validation
100 | Issue status is shown in the [Description](#description).
101 | **Why validate issue status?**
102 | In some cases, one may be pushing changes for a story that is set to `Done`/`Completed` or it may not have been pulled into working backlog or current sprint.
103 |
104 | This option allows discouraging pushing to branches for stories that are set to statuses other than the ones allowed in the project; for example - you may want to only allow PRs for stories that are in `To Do`/`Planning`/`In Progress` states.
105 |
106 | The following flags can be used to validate issue status:
107 | - `validate_issue_status`
108 | - If set to `true`, `jira-lint` will validate the issue status based on `allowed_issue_statuses`
109 | - `allowed_issue_statuses`
110 | - This will only be used when `validate_issue_status` is `true`. This should be a comma separated list of statuses. If the detected issue's status is not in one of the `allowed_issue_statuses` then `jira-lint` will fail the status check.
111 |
112 | **Example of invalid status**
113 |
:broken_heart: The detected issue is not in one of the allowed statuses :broken_heart:
114 |
115 |
116 |
Detected Status
117 |
${issueStatus}
118 |
:x:
119 |
120 |
121 |
Allowed Statuses
122 |
${allowedStatuses}
123 |
:heavy_check_mark:
124 |
125 |
126 |
Please ensure your jira story is in one of the allowed statuses
127 |
128 | #### Soft-validations via comments
129 |
130 | `jira-lint` will add comments to a PR to encourage better PR practices:
131 |
132 | **A good PR title**
133 |
134 |
135 |
136 | When the title of the PR matches the summary/title of the issue well.
137 |
138 |
139 | ---
140 |
141 |
142 |
143 | When the title of the PR is slightly different compared to the summary/title of the issue
144 |
145 |
146 | ---
147 |
148 |
149 |
150 | When the title of the PR is very different compared to the summary/title of the issue
151 |
152 |
153 | ---
154 |
155 | **A comment discouraging PRs which are too large (based on number of lines of code changed).**
156 |
157 |
158 |
159 | Batman says no large PRs 🦇
160 |
161 |
162 | ### Options
163 |
164 | | key | description | required | default |
165 | | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------- |
166 | | `github-token` | Token used to update PR description. `GITHUB_TOKEN` is already available [when you use GitHub actions](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/authenticating-with-the-github_token#about-the-github_token-secret), so all that is required is to pass it as a param here. | true | null |
167 | | `jira-token` | Token used to fetch Jira Issue information. Check [below](#jira-token) for more details on how to generate the token. | true | null |
168 | | `jira-base-url` | The subdomain of JIRA cloud that you use to access it. Ex: "https://your-domain.atlassian.net". | true | null |
169 | | `skip-branches` | A regex to ignore running `jira-lint` on certain branches, like production etc. | false | ' ' |
170 | | `skip-comments` | A `Boolean` if set to `true` then `jira-lint` will skip adding lint comments for PR title. | false | false |
171 | | `pr-threshold` | An `Integer` based on which `jira-lint` will add a comment discouraging huge PRs. | false | 800 |
172 | | `validate_issue_status` | A `Boolean` based on which `jira-lint` will validate the status of the detected jira issue | false | false |
173 | | `allowed_issue_statuses` | A comma separated list of allowed statuses. The detected jira issue's status will be compared against this list and if a match is not found then the status check will fail. *Note*: Requires `validate_issue_status` to be set to `true`. | false | `"In Progress"` |
174 |
175 |
176 | ### `jira-token`
177 |
178 | Since tokens are private, we suggest adding them as [GitHub secrets](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets).
179 |
180 | The Jira token is used to fetch issue information via the Jira REST API. To get the token:-
181 | 1. Generate an [API token via JIRA](https://confluence.atlassian.com/cloud/api-tokens-938839638.html)
182 | 2. Create the encoded token in the format of `base64Encode(:)`.
183 | For example, if the username is `ci@example.com` and the token is `954c38744be9407ab6fb`, then `ci@example.com:954c38744be9407ab6fb` needs to be base64 encoded to form `Y2lAZXhhbXBsZS5jb206OTU0YzM4NzQ0YmU5NDA3YWI2ZmI=`
184 | 3. The above value (in this example `Y2lAZXhhbXBsZS5jb206OTU0YzM4NzQ0YmU5NDA3YWI2ZmI=`) needs to be added as the `JIRA_TOKEN` secret in your GitHub project.
185 |
186 | Note: The user should have the [required permissions (mentioned under GET Issue)](https://developer.atlassian.com/cloud/jira/platform/rest/v3/?utm_source=%2Fcloud%2Fjira%2Fplatform%2Frest%2F&utm_medium=302#api-rest-api-3-issue-issueIdOrKey-get).
187 |
188 | ### Skipping branches
189 |
190 | Since GitHub actions take string inputs, `skip-branches` must be a regex which will work for all sets of branches you want to ignore. This is useful for merging protected/default branches into other branches. Check out some [examples in the tests](https://github.com/ClearTax/jira-lint/blob/08a47ab7a6e2bc235c9e34da1d14eacf9d810bd1/__tests__/utils.test.ts#L33-L44).
191 |
192 | `jira-lint` already skips PRs which are filed by bots (for eg. [dependabot](https://github.com/marketplace/dependabot-preview)). You can add more bots to [this list](https://github.com/ClearTax/jira-lint/blob/08a47ab7a6e2bc235c9e34da1d14eacf9d810bd1/src/constants.ts#L4), or add the branch-format followed by the bot PRs to the `skip-branches` option.
193 |
194 | ## Contributing
195 |
196 | Follow the instructions [here](https://help.github.com/en/articles/creating-a-javascript-action#commit-and-push-your-action-to-github) to know more about GitHub actions.
197 |
198 | ## FAQ
199 |
200 |
201 | Why is a Jira key required in the branch names?
202 |
203 | The key is required in order to:
204 |
205 | - Automate change-logs and release notes ⚙️.
206 | - Automate alerts to QA/Product teams and other external stake-holders 🔊.
207 | - Help us retrospect the sprint progress 📈.
208 |
209 |
210 |
211 |
212 | Is there a way to get around this?
213 | Nope 🙅
214 |
215 |
216 |
217 | [jira]: https://www.atlassian.com/software/jira
218 |
219 | ## Contributors
220 |
221 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
222 |
223 |
224 |
225 |
226 |
146 | Just thought I'd let you know that your PR title and story title look quite different. PR titles
147 | that closely resemble the story title make it easier for reviewers to understand the context of the PR.
148 |
149 |
150 | An easy-to-understand PR title a day makes the reviewer review away! 😛⚡️
151 |
152 |
153 |
154 |
Story Title
155 |
${storyTitle}
156 |
157 |
158 |
PR Title
159 |
${prTitle}
160 |
161 |
162 |
163 | Check out this guide to learn more about PR best-practices.
164 |
168 | Let's make that PR title a 💯 shall we? 💪
169 |
170 |
171 | Your PR title and story title look slightly different. Just checking in to know if it was intentional!
172 |
173 |
174 |
175 |
Story Title
176 |
${storyTitle}
177 |
178 |
179 |
PR Title
180 |
${prTitle}
181 |
182 |
183 |
184 | Check out this guide to learn more about PR best-practices.
185 |
186 | `;
187 | }
188 | return `
I'm a bot and I 👍 this PR title. 🤖
189 |
190 | `;
191 | };
192 |
193 | /**
194 | * Check if the PR is an automated one created by a bot or one matching ignore patterns supplied
195 | * via action metadata.
196 | *
197 | * @example shouldSkipBranchLint('dependabot') -> true
198 | * @example shouldSkipBranchLint('feature/update_123456789') -> false
199 | */
200 | export const shouldSkipBranchLint = (branch: string, additionalIgnorePattern?: string): boolean => {
201 | if (BOT_BRANCH_PATTERNS.some((pattern) => pattern.test(branch))) {
202 | console.log(`You look like a bot 🤖 so we're letting you off the hook!`);
203 | return true;
204 | }
205 |
206 | if (DEFAULT_BRANCH_PATTERNS.some((pattern) => pattern.test(branch))) {
207 | console.log(`Ignoring check for default branch ${branch}`);
208 | return true;
209 | }
210 |
211 | const ignorePattern = new RegExp(additionalIgnorePattern || '');
212 | if (!!additionalIgnorePattern && ignorePattern.test(branch)) {
213 | console.log(
214 | `branch '${branch}' ignored as it matches the ignore pattern '${additionalIgnorePattern}' provided in skip-branches`
215 | );
216 | return true;
217 | }
218 |
219 | console.log(`branch '${branch}' does not match ignore pattern provided in 'skip-branches' option:`, ignorePattern);
220 | return false;
221 | };
222 |
223 | /**
224 | * Returns true if the body contains the hidden marker. Used to avoid adding
225 | * story details to the PR multiple times.
226 | *
227 | * @example shouldUpdatePRDescription('--\nadded_by_pr_lint\n') -> true
228 | * @example shouldUpdatePRDescription('# some description') -> false
229 | */
230 | export const shouldUpdatePRDescription = (
231 | /** The PR description/body as a string. */
232 | body?: string
233 | ): boolean => typeof body === 'string' && !MARKER_REGEX.test(body);
234 |
235 | /**
236 | * Get links to labels & remove spacing so the table works.
237 | */
238 | export const getLabelsForDisplay = (labels: JIRADetails['labels']): string => {
239 | if (!labels || !labels.length) {
240 | return '-';
241 | }
242 | const markUp = labels.map((label) => `${label.name}`).join(', ');
243 | return markUp.replace(/\s+/, ' ');
244 | };
245 |
246 | /** Get PR description with story/issue details. */
247 | export const getPRDescription = (body = '', details: JIRADetails): string => {
248 | const displayKey = details.key.toUpperCase();
249 |
250 | return `
251 |
252 | ${displayKey}
253 |
254 |
255 |
256 |
Summary
257 |
${details.summary}
258 |
259 |
260 |
Type
261 |
262 |
263 | ${details.type.name}
264 |
265 |
266 |
267 |
Status
268 |
${details.status}
269 |
270 |
271 |
Points
272 |
${details.estimate || 'N/A'}
273 |
274 |
275 |
Labels
276 |
${getLabelsForDisplay(details.labels)}
277 |
278 |
279 |
280 |
284 |
285 | ---
286 |
287 | ${body}`;
288 | };
289 |
290 | /** Check if a PR is considered "huge". */
291 | export const isHumongousPR = (additions: number, threshold: number): boolean =>
292 | typeof additions === 'number' && additions > threshold;
293 |
294 | /** Get the comment body for very huge PR. */
295 | export const getHugePrComment = (
296 | /** Number of additions. */
297 | additions: number,
298 | /** Threshold of additions allowed. */
299 | threshold: number
300 | ): string =>
301 | `
This PR is too huge for one to review :broken_heart:
302 |
303 |
304 |
305 |
Additions
306 |
${additions} :no_good_woman:
307 |
308 |
309 |
Expected
310 |
:arrow_down: ${threshold}
311 |
312 |
313 |
314 | Consider breaking it down into multiple small PRs.
315 |
316 |
317 | Check out this guide to learn more about PR best-practices.
318 |
319 | `;
320 |
321 | /** Get the comment body for pr with no JIRA id in the branch name. */
322 | export const getNoIdComment = (branch: string): string => {
323 | return `
A JIRA Issue ID is missing from your branch name! 🦄
324 |
Your branch: ${branch}
325 |
If this is your first time contributing to this repository - welcome!