Skip to content

Commit

Permalink
V2 (#20)
Browse files Browse the repository at this point in the history
* refactor(additional features): add separator and anywhere in title features

- bump action to Node.js 20
- update dependencies
- add optional separator parameter
- add optional keyAnywhereInTitle parameter
- update docs
- add eslint config file

BREAKING CHANGE: Now requires Node.js 20

* refactor(additional features): add separator and anywhere in title features

- bump action to Node.js 20
- update dependencies
- add optional separator parameter
- add optional keyAnywhereInTitle parameter
- update docs
- add eslint config file

BREAKING CHANGE: Now requires Node.js 20

* docs(separator): fix default docs for separator

- move default docs for separator in readme

* test(tests): reset environment variables on each test

- reset environment variables on each test

* ci(qodana): add qodana quality scan

- add qodana workflow

* ci(qodana): change release of qodana

- switch to qodana action 2023.2.8

* ci(tests): fix typo in test workflow

- fix typo in test workflow

* ci(qodana): install node dependencies

- install node dependencies in qodana workflow

* test(ci): set environment variables at start of tests

- set environment variables at start of tests

* test(regex): fix eslint errors in regex

- fix eslint errors in regex

* refactor(refactor): move logic to main.ts

- move most logic to main.ts
- don't need to call run in internal tests
  • Loading branch information
ryanvade authored Nov 24, 2023
1 parent ccd1cc5 commit 07488cd
Show file tree
Hide file tree
Showing 19 changed files with 34,337 additions and 12,646 deletions.
33 changes: 33 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module.exports = {
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended"
],
"overrides": [
{
"env": {
"node": true
},
"files": [
".eslintrc.{js,cjs}"
],
"parserOptions": {
"sourceType": "script"
}
}
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
}
}
30 changes: 30 additions & 0 deletions .github/workflows/qodana_code_quality.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Qodana
on:
workflow_dispatch:
pull_request:
push:
branches:
- main

jobs:
qodana:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Use Node.js 20.x
uses: actions/setup-node@v1
with:
node-version: 20.x
- uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-node-qodana-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm ci
- name: 'Qodana Scan'
uses: JetBrains/[email protected]
env:
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ jobs:

strategy:
matrix:
node-version: [12.x]
node-version: [20.x]

steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-verison }}
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules/
node_modules/
.idea/
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v12
v20
65 changes: 59 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ This action analyses the titles of Pull Requests to ensure they start with a Jir
For example, if your project key were `AB` then the following would be allowed

```
AB-1 Initialize Project
AB-1 Initialize Project
```

However, the following examples would not be allowed
Expand All @@ -28,26 +28,79 @@ Valid Pull Request titles must also include a short description after the Issue
AB-1
```

Specifying a separator allows for using characters such as `:`, `-` or `_` to be between the Jira key and title.
As an example with the separator `:` the following is allowed

```
AB-1:Initialize Project
```

Note that by specifying a separator the following would not be allowed

```
AB-1: Initialize Project
```

By default this action checks that the Jira Project Key is at the start of the title string. To allow for the Jira Project
Key to be anywhere in the title set the `keyAnywhereInTitle` property to `true`. With this property enabled and the project
key set to `AB` the following title is valid

```
Other: AB-1 Initialize Project
```

By default, this action will allow any valid Issue Key so long as it *could* be valid. If you want to be specific to your project, use the `projectKey` input for the action.

## Inputs

### `projectKey`

A specific Project Key to always check for.
A specific Project Key to always check for

### `separator`

A specific separator to use. Defaults to a space character.

### `keyAnywhereInTitle`

Allows the Jira Project Key, Issue # and separator to be anywhere in the title. Defaults to false.

## Example Usage

```
- name: Enforce Jira Issue Key in Pull Request Title
uses: ryanvade/enforce-pr-title-style-action@v1
uses: ryanvade/enforce-pr-title-style-action@v2
```

## Example Usage with a specific Project Key

```
- name: Enforce Jira Issue Key in Pull Request Title
uses: ryanvade/enforce-pr-title-style-action@v1
uses: ryanvade/enforce-pr-title-style-action@v2
with:
projectKey: 'AB'
```

## Example Usage with a specific Project Key and a separator

```
- name: Enforce Jira Issue Key in Pull Request Title
uses: ryanvade/enforce-pr-title-style-action@v2
with:
projectKey: AB
```
projectKey: 'AB'
separator: ':'
```

## Example Usage with a specific Project Key and allowed to be anywhere in the title

```
- name: Enforce Jira Issue Key in Pull Request Title
uses: ryanvade/enforce-pr-title-style-action@v2
with:
projectKey: 'AB'
keyAnywhereInTitle: 'true'
```

Note that edits to a pull request title will not include the updated title as an input to this action
without specifying the `edited` type. See the [pull_request](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request)
event trigger documentation for more details. Thanks to @jdonboch for pointing this out.
187 changes: 140 additions & 47 deletions __tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,144 @@
import * as github from "@actions/github";
import * as core from "@actions/core";
import { readFileSync } from "fs";
import { getPullRequestTitle, getRegex } from "../src";
import { getPullRequestTitle, getRegex } from "../src/main";

const projectKeyInputName = "projectKey";
const separatorKeyInputName = "separator";
const keyAnywhereInTitle = "keyAnywhereInTitle";

const resetEnvironmentVariables = () => {
process.env[`INPUT_${projectKeyInputName.replace(/ /g, "_").toUpperCase()}`] =
"";
process.env[
`INPUT_${separatorKeyInputName.replace(/ /g, "_").toUpperCase()}`
] = "";
process.env[`INPUT_${keyAnywhereInTitle.replace(/ /g, "_").toUpperCase()}`] =
"false";
};

resetEnvironmentVariables();

describe("index", () => {
describe("getPullRequestTitle", () => {
beforeEach(() => {
delete process.env["GITHUB_EVENT_PATH"];
});
it("can get the title from the context", () => {
process.env["GITHUB_EVENT_PATH"] = __dirname + "/valid-context.json";
github.context.payload = JSON.parse(
readFileSync(process.env.GITHUB_EVENT_PATH, { encoding: 'utf8' })
);
const title = getPullRequestTitle();
expect(title).toBe("Test Title");
});

it("raises an exception if the event is not for a pull_request", () => {
process.env["GITHUB_EVENT_PATH"] = __dirname + "/wrong-event-type-context.json";
github.context.payload = JSON.parse(
readFileSync(process.env.GITHUB_EVENT_PATH, { encoding: 'utf8' })
);
expect(getPullRequestTitle).toThrowError("This action should only be run with Pull Request Events");
})
});

describe("getRegex", () => {
const name = "projectKey";
it("gets the default when no project key is provided", () => {
process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] = "";
const regex = getRegex();
let defaultRegex = /(?<=^|[a-z]\-|[\s\p{Punct}&&[^\-]])([A-Z][A-Z0-9_]*-\d+)(?![^\W_])(\s)+(.)+/;
expect(regex).toEqual(defaultRegex);
expect(regex.test("PR-4 this is valid")).toBe(true);
});

it("uses a project key if it exists", () => {
process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] = "AB";
const regex = getRegex();
expect(regex).toEqual(new RegExp(`(^AB-){1}(\\d)+(\\s)+(.)+`));
expect(regex.test("AB-43 stuff and things")).toBe(true);
});

it("throws an exception if the provided project key is not valid", () => {
process.env[`INPUT_${name.replace(/ /g, '_').toUpperCase()}`] = "aB";
expect(getRegex).toThrowError("Project Key \"aB\" is invalid");
});
});
});
describe("getPullRequestTitle", () => {
beforeEach(() => {
delete process.env["GITHUB_EVENT_PATH"];
resetEnvironmentVariables();
});
it("can get the title from the context", () => {
process.env["GITHUB_EVENT_PATH"] = __dirname + "/valid-context.json";
github.context.payload = JSON.parse(
readFileSync(process.env.GITHUB_EVENT_PATH, { encoding: "utf8" }),
);
const title = getPullRequestTitle();
expect(title).toBe("Test Title");
});

it("raises an exception if the event is not for a pull_request", () => {
process.env["GITHUB_EVENT_PATH"] =
__dirname + "/wrong-event-type-context.json";
github.context.payload = JSON.parse(
readFileSync(process.env.GITHUB_EVENT_PATH, { encoding: "utf8" }),
);
expect(getPullRequestTitle).toThrow(
"This action should only be run with Pull Request Events",
);
});
});

describe("getRegex", () => {
beforeEach(() => resetEnvironmentVariables());
it("gets the default when no project key is provided", () => {
process.env[
`INPUT_${projectKeyInputName.replace(/ /g, "_").toUpperCase()}`
] = "";
const regex = getRegex();
const defaultRegex =
// eslint-disable-next-line no-useless-escape
/(?<=^|[a-z]-|[\s\p{Punct}&[^\-]])([A-Z][A-Z0-9_]*-\d+)(?![^\W_])(\s)+(.)+/;
expect(regex).toEqual(defaultRegex);
expect(regex.test("PR-4 this is valid")).toBe(true);
});

it("uses a project key if it exists", () => {
process.env[
`INPUT_${projectKeyInputName.replace(/ /g, "_").toUpperCase()}`
] = "AB";
const regex = getRegex();
expect(regex).toEqual(new RegExp(`(^AB-){1}(\\d)+(\\s)+(.)+`));
expect(regex.test("AB-43 stuff and things")).toBe(true);
});

it("throws an exception if the provided project key is not valid", () => {
process.env[
`INPUT_${projectKeyInputName.replace(/ /g, "_").toUpperCase()}`
] = "aB";
expect(getRegex).toThrow('Project Key "aB" is invalid');
});

it("uses a project key and a colon separator if they exist", () => {
process.env[
`INPUT_${projectKeyInputName.replace(/ /g, "_").toUpperCase()}`
] = "AB";
process.env[
`INPUT_${separatorKeyInputName.replace(/ /g, "_").toUpperCase()}`
] = ":";
process.env[
`INPUT_${keyAnywhereInTitle.replace(/ /g, "_").toUpperCase()}`
] = "false";
const regex = getRegex();
expect(regex).toEqual(new RegExp(`(^AB-){1}(\\d)+(:)+(\\S)+(.)+`));
expect(regex.test("AB-43: stuff and things")).toBe(false);
expect(regex.test("AB-123: PR Title")).toBe(false);
expect(regex.test("AB-43:stuff and things")).toBe(true);
expect(regex.test("AB-123:PR Title")).toBe(true);
});

it("uses a project key and an underscore separator if they exist", () => {
process.env[
`INPUT_${projectKeyInputName.replace(/ /g, "_").toUpperCase()}`
] = "AB";
process.env[
`INPUT_${separatorKeyInputName.replace(/ /g, "_").toUpperCase()}`
] = "_";
process.env[
`INPUT_${keyAnywhereInTitle.replace(/ /g, "_").toUpperCase()}`
] = "false";
const regex = getRegex();
expect(regex).toEqual(new RegExp(`(^AB-){1}(\\d)+(_)+(\\S)+(.)+`));
expect(regex.test("AB-43_stuff and things")).toBe(true);
expect(regex.test("AB-123_PR Title")).toBe(true);
});

it("uses a project key if it exists anywhere in the title", () => {
process.env[
`INPUT_${projectKeyInputName.replace(/ /g, "_").toUpperCase()}`
] = "AB";
process.env[
`INPUT_${keyAnywhereInTitle.replace(/ /g, "_").toUpperCase()}`
] = "true";
const regex = getRegex();
expect(regex).toEqual(new RegExp(`(.)*(AB-){1}(\\d)+(\\s)+(.)+`));
expect(regex.test("other words AB-43 stuff and things")).toBe(true);
});

it("uses a project key and a colon separator if they exist anywhere in the title", () => {
process.env[
`INPUT_${projectKeyInputName.replace(/ /g, "_").toUpperCase()}`
] = "AB";
process.env[
`INPUT_${separatorKeyInputName.replace(/ /g, "_").toUpperCase()}`
] = ":";
process.env[
`INPUT_${keyAnywhereInTitle.replace(/ /g, "_").toUpperCase()}`
] = "true";
const regex = getRegex();
expect(regex).toEqual(new RegExp(`(.)*(AB-){1}(\\d)+(:)+(\\S)+(.)+`));
expect(regex.test("other words AB-43: stuff and things")).toBe(false);
expect(regex.test("other words AB-123: PR Title")).toBe(false);
expect(regex.test("other words AB-43:stuff and things")).toBe(true);
expect(regex.test("other words AB-123:PR Title")).toBe(true);
expect(regex.test("AB-43:stuff and things")).toBe(true);
expect(regex.test("AB-123:PR Title")).toBe(true);
});
});
});
9 changes: 8 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ inputs:
projectKey:
description: 'A specific Jira Project Key that must be included.'
required: false
separator:
description: 'Character that should be used to separate the Jira Project Key and Issue Number from the rest of the title'
required: false
keyAnywhereInTitle:
description: 'Allows the Jira Project Key and Issue Number to be anywhere in the title'
required: false
default: 'false'
runs:
using: node12
using: node20
main: dist/index.js
Loading

0 comments on commit 07488cd

Please sign in to comment.