Skip to content

Commit

Permalink
fix(ci): JIRA-2085 improve Jira ID consistency validation
Browse files Browse the repository at this point in the history
Improved the check as to have a 3-way matching of the Jira ID,
to ensure it's the same in branch name, PR title and commits.
  • Loading branch information
BogdanSorlea committed Jun 11, 2021
1 parent 3d4ca06 commit 9a6aafc
Show file tree
Hide file tree
Showing 11 changed files with 398 additions and 824 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
.idea
.idea
package-lock.json
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
# JIRA Branch Name Validation Action
A GitHub action for validating if the branch name is contains JIRA id
A GitHub action for validating if the branch name contains a JIRA id (as format) and whether the same JIRA id is contained in the PR title and commit message(s).

The same code is npm-packaged and used for local pre-commit validation of the branch name, only (via git hooks / husky). The PR title and commits are not checked locally because they are not relevant at this step in the workflow (PR doesn't have to exist when still developing locally and local commit messages can be whatever the developer wants - i.e. before squash and push).

## Inputs

### `branch-name`

**Required** The name of the branch to validate against.

### `pr-title`

**Required** The title of the PR to validate

### `commits`

**Required** The Github API response JSON containing the commits of the PR

***

## Example usage

```
Expand Down Expand Up @@ -34,3 +46,30 @@ jobs:
branch-name: ${{ github.event.pull_request.head.ref }}
```

## Setting up for action development
```
npm i -g @vercel/ncc
```

## Building and releasing the action

```
npm run build
```
The resulting (generated) code requires pushing to Github, after which a new release can be drafted with this new code as source. Once the PR is merged, a new release off main branch can be drafted with a major.minor.patch version. A release off the PR branch can be drafted with a major.minor.patch-rcnumber version or similar. To use the new action functionality on Github, the action needs to be published on the Github Marketplace (during release drafting).

## Setting up for hook deployment

Access to Worksome npm registry is required. Ask about it in #devtalk
New versions of the package should be published to the npm registry, trying to maintain version consistency between the npm package and the github action release version.

## Testing

The github action requires the new version to be pushed to PR branch and a new release to be drafted (and published to github marketplace). After that, update the version of the github action as used in the "client" repository (as well as any other changes it might require, e.g. add newly introduced parameters to the module) and push and test (by triggering the relevant events, whenever possible).

The local branch validator can be manually triggered via
```
node_modules/.bin/branch-validator
```
on repositories that use the module (requires publish of the new package to worksome registry or [locally](https://medium.com/@debshish.pal/publish-a-npm-package-locally-for-testing-9a00015eb9fd)).
12 changes: 9 additions & 3 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
name: 'Validate JIRA branch name'
description: 'Validates if the branch name contains JIRA'
name: 'Validate JIRA-ID branch name and consistency across PR title and commits'
description: 'Validates if the branch name contains JIRA-ID - and PR title and commits have the same one'
inputs:
branch-name:
description: The name of the branch to validate against
required: true
pr-title:
description: The title of the PR to validate
required: true
commits:
description: The Github API response JSON containing the commits of the PR
required: true
runs:
using: 'node12'
main: 'dist/github/index.js'
branding:
icon: 'git-branch'
color: 'gray-dark'
color: 'gray-dark'
195 changes: 116 additions & 79 deletions dist/github/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
module.exports =
/******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({

/***/ 604:
/***/ 777:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {


Expand All @@ -16,7 +15,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const os = __importStar(__nccwpck_require__(87));
const utils_1 = __nccwpck_require__(245);
const utils_1 = __nccwpck_require__(855);
/**
* Commands
*
Expand Down Expand Up @@ -88,7 +87,7 @@ function escapeProperty(s) {

/***/ }),

/***/ 127:
/***/ 181:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {


Expand All @@ -109,9 +108,9 @@ var __importStar = (this && this.__importStar) || function (mod) {
return result;
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
const command_1 = __nccwpck_require__(604);
const file_command_1 = __nccwpck_require__(352);
const utils_1 = __nccwpck_require__(245);
const command_1 = __nccwpck_require__(777);
const file_command_1 = __nccwpck_require__(679);
const utils_1 = __nccwpck_require__(855);
const os = __importStar(__nccwpck_require__(87));
const path = __importStar(__nccwpck_require__(622));
/**
Expand Down Expand Up @@ -332,7 +331,7 @@ exports.getState = getState;

/***/ }),

/***/ 352:
/***/ 679:
/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) {


Expand All @@ -349,7 +348,7 @@ Object.defineProperty(exports, "__esModule", ({ value: true }));
/* eslint-disable @typescript-eslint/no-explicit-any */
const fs = __importStar(__nccwpck_require__(747));
const os = __importStar(__nccwpck_require__(87));
const utils_1 = __nccwpck_require__(245);
const utils_1 = __nccwpck_require__(855);
function issueCommand(command, message) {
const filePath = process.env[`GITHUB_${command}`];
if (!filePath) {
Expand All @@ -367,7 +366,7 @@ exports.issueCommand = issueCommand;

/***/ }),

/***/ 245:
/***/ 855:
/***/ ((__unused_webpack_module, exports) => {


Expand All @@ -392,68 +391,6 @@ exports.toCommandValue = toCommandValue;

/***/ }),

/***/ 803:
/***/ ((__unused_webpack_module, __webpack_exports__, __nccwpck_require__) => {

// ESM COMPAT FLAG
__nccwpck_require__.r(__webpack_exports__);

// CONCATENATED MODULE: ./src/validator.js
/* harmony default export */ function validator(branchName) {

let result = []

if (!branchName.startsWith('JIRA')) {
result.push(`Branch doesn't start with \`JIRA\` prefix, found ${branchName}.`)
}

branchName = branchName.substring(4)
if (!branchName.startsWith('-')) {
result.push(`Separator after prefix is not \`-\`, found ${branchName.substring(0, 1)}.`)
}

branchName = branchName.substring(1);
const rawJiraId = branchName.match(/^\d*/)[0]
const jiraId = parseInt(rawJiraId)
if (isNaN(jiraId) || jiraId === 0) {
result.push(`JIRA id is not a positive number, found ${rawJiraId}.`)
}
if (rawJiraId.length !== jiraId.toString().length) {
result.push(`JIRA id has leading zeros, found ${rawJiraId}.`)
}

branchName = branchName.substring(rawJiraId.length)
if (!branchName.startsWith('_')) {
result.push(`Separator after JIRA id is not \`_\`, found ${branchName.substring(0, 1)}.`)
}

branchName = branchName.substring(1)
if (!/^[a-zA-Z0-9\-_]+$/.test(branchName)) {
result.push(`Description after JIRA id should use hyphen or underscore as word separator, found ${branchName}.`)
}

if (branchName.length > 100) {
result.push(`Description after JIRA id has to be shorter than 100 characters, found ${branchName}.`)
}

return result
}
// CONCATENATED MODULE: ./src/index.js

const core = __nccwpck_require__(127)

let branchName = core.getInput("branch-name")
core.info(`Received the following branch name ${branchName}.`)
core.info("The format should be `JIRA-123_fixing-bug`.")

const results = validator(branchName)

results.forEach(message => {
core.setFailed(message)
})

/***/ }),

/***/ 747:
/***/ ((module) => {

Expand Down Expand Up @@ -483,8 +420,9 @@ module.exports = require("path");;
/******/ // The require function
/******/ function __nccwpck_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(__webpack_module_cache__[moduleId]) {
/******/ return __webpack_module_cache__[moduleId].exports;
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
Expand Down Expand Up @@ -520,10 +458,109 @@ module.exports = require("path");;
/******/
/******/ /* webpack/runtime/compat */
/******/
/******/ __nccwpck_require__.ab = __dirname + "/";/************************************************************************/
/******/ // module exports must be returned from runtime so entry inlining is disabled
/******/ // startup
/******/ // Load entry module and return exports
/******/ return __nccwpck_require__(803);
/******/ if (typeof __nccwpck_require__ !== 'undefined') __nccwpck_require__.ab = __dirname + "/";/************************************************************************/
var __webpack_exports__ = {};
// This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk.
(() => {
// ESM COMPAT FLAG
__nccwpck_require__.r(__webpack_exports__);

;// CONCATENATED MODULE: ./src/validator.js
/* harmony default export */ function validator(branchName) {

let result = []

if (!branchName.startsWith('JIRA')) {
result.push(`Branch doesn't start with \`JIRA\` prefix, found ${branchName}.`)
}

branchName = branchName.substring(4)
if (!branchName.startsWith('-')) {
result.push(`Separator after prefix is not \`-\`, found ${branchName.substring(0, 1)}.`)
}

branchName = branchName.substring(1);
const rawJiraId = branchName.match(/^\d*/)[0]
const jiraId = parseInt(rawJiraId)
if (isNaN(jiraId) || jiraId === 0) {
result.push(`JIRA id is not a positive number, found ${rawJiraId}.`)
}
if (rawJiraId.length !== jiraId.toString().length) {
result.push(`JIRA id has leading zeros, found ${rawJiraId}.`)
}

branchName = branchName.substring(rawJiraId.length)
if (!branchName.startsWith('_')) {
result.push(`Separator after JIRA id is not \`_\`, found ${branchName.substring(0, 1)}.`)
}

branchName = branchName.substring(1)
if (!/^[a-zA-Z0-9\-_]+$/.test(branchName)) {
result.push(`Description after JIRA id should use hyphen or underscore as word separator, found ${branchName}.`)
}

if (branchName.length > 100) {
result.push(`Description after JIRA id has to be shorter than 100 characters, found ${branchName}.`)
}

return ["JIRA-" + jiraId, result]
}


;// CONCATENATED MODULE: ./src/jira-id-consistency-check.js
/* harmony default export */ function jira_id_consistency_check(jiraId, prTitle, commits) {

let result = []

if (prTitle.search(jiraId) < 0) {
result.push(`PR title <${prTitle}> does not contain Jira ID inferred from branch name, ${jiraId}`)
}

JSON.parse(commits).filter(c => c.commit.message.search(jiraId) < 0).forEach(c => {
result.push(`Commit message <${c.commit.message}> does not contain Jira ID inferred from branch name, ${jiraId}`)
})
return result
}


;// CONCATENATED MODULE: ./src/index.js


const core = __nccwpck_require__(181)

let branchName = core.getInput("branch-name")
let prTitle = core.getInput("pr-title")
let commits = core.getInput("commits")

let prValidation = (prTitle.length > 0 && commits.length > 0) ? true : false

core.info(`Received the following branch name: ${branchName}.`)
core.info("The format should be `JIRA-123_fixing-bug`.")

let [jiraId, results] = validator(branchName)

core.info(`Extracted the following JIRA ID from branch name: ${jiraId}`)

if (prValidation) {
core.info(`Received the following PR title: ${prTitle}`)
core.info("Should contain the same JIRA ID.")
core.info("Received the following commits information:")
JSON.parse(commits).forEach(c => {
core.info(` ${c.commit.message}`)
})
core.info("Commit message(s) should contain the same JIRA ID as above.")
results = results.concat(jira_id_consistency_check(jiraId, prTitle, commits))
} else {
core.info(`PR title or commits information is missing`)
}

results.forEach(message => {
core.setFailed(message)
})


})();

module.exports = __webpack_exports__;
/******/ })()
;
Loading

0 comments on commit 9a6aafc

Please sign in to comment.