Skip to content

Commit 385eca9

Browse files
committed
chore: initial commit
0 parents  commit 385eca9

File tree

10 files changed

+952
-0
lines changed

10 files changed

+952
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules/
2+
dist/
3+
yarn-error.log

.prettierrc.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"arrowParens": "avoid",
3+
"semi": true,
4+
"printWidth": 100,
5+
"tabWidth": 2,
6+
"useTabs": false
7+
}

README.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# semantic-release-jira-notes
2+
3+
[**semantic-release**](https://github.com/semantic-release/semantic-release) plugin to add links to
4+
JIRA issues in the release notes.
5+
6+
[![npm latest version](https://img.shields.io/npm/v/semantic-release-jira-notes/latest.svg)](https://www.npmjs.com/package/semantic-release-jira-notes)
7+
8+
9+
| Step | Description |
10+
| ------------------ | ---------------------------------------------------- |
11+
| `verifyConditions` | Validate the config options |
12+
| `generateNotes` | Generate the release notes with links to JIRA issues |
13+
14+
15+
## Usage
16+
17+
First, install the plugin.
18+
19+
```bash
20+
$ npm install --save-dev semantic-release-jira-notes
21+
$ yarn add --dev semantic-release-jira-notes
22+
```
23+
24+
25+
## Configuration
26+
27+
The plugin should then be added to your config file.
28+
29+
```json
30+
{
31+
"plugins": [
32+
"@semantic-release/commit-analyzer",
33+
["semantic-release-jira-notes", {
34+
"jiraHost": "iamludal.atlassian.net",
35+
"ticketPrefixes": ["ATP", "OMS"], // optional
36+
}]
37+
"@semantic-release/git",
38+
"@semantic-release/github"
39+
]
40+
}
41+
```
42+
43+
**Note:** you don't need to use `@semantic-release/release-notes-generator` as it already uses
44+
it under the hood.
45+
46+
47+
### Inputs
48+
49+
| Name | Required | Description |
50+
| -------------- | :------: | ---------------------------------------------------------------------- |
51+
| jiraHost || Your JIRA host domain name |
52+
| ticketPrefixes || Ticket prefixes to match. If not provided, match all tickets prefixes. |

package.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "semantic-release-jira-notes",
3+
"version": "1.0.0",
4+
"description": "Semantic Release plugin to add JIRA issues link to the release notes",
5+
"main": "src/index.js",
6+
"author": "iamludal",
7+
"license": "MIT",
8+
"dependencies": {
9+
"@semantic-release/error": "2.2.0",
10+
"@semantic-release/release-notes-generator": "^10.0.3",
11+
"aggregate-error": "^4.0.1"
12+
}
13+
}

src/index.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const verifyConditions = require("./lib/verify-conditions");
2+
const generateNotes = require("./lib/generate-notes");
3+
4+
module.exports = { verifyConditions, generateNotes };

src/lib/constants.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const INPUTS = { ticketPrefixes: "ticketPrefixes", jiraHost: "jiraHost" };
2+
3+
const TICKET_PREFIX_REGEX = /^[A-Z][A-Z0-9]{0,50}$/;
4+
const ISSUE_REGEX = /([A-Z][A-Z0-9]{0,50}-[1-9][0-9]*)/;
5+
const DOMAIN_NAME_REGEX = /^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\.)+[A-Za-z]{2,6}$/;
6+
7+
module.exports = { INPUTS, TICKET_PREFIX_REGEX, ISSUE_REGEX, DOMAIN_NAME_REGEX };

src/lib/errors.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
const SemanticReleaseError = require("@semantic-release/error");
2+
3+
class RegexError extends SemanticReleaseError {
4+
constructor(value, pattern) {
5+
super(`Value '${value}' does not match regex: ${pattern}`);
6+
}
7+
}
8+
9+
class InvalidTypeError extends SemanticReleaseError {
10+
constructor(variableName, expectedType) {
11+
super(`${variableName} should be of type: ${expectedType}`);
12+
}
13+
}
14+
15+
class InputRequiredError extends SemanticReleaseError {
16+
constructor(inputName) {
17+
super(`Input '${inputName}' is required`);
18+
}
19+
}
20+
21+
module.exports = { RegexError, InvalidTypeError, InputRequiredError };

src/lib/generate-notes.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const releaseNotesGenerator = require("@semantic-release/release-notes-generator");
2+
const { INPUTS, ISSUE_REGEX } = require("./constants");
3+
4+
module.exports = async (pluginConfig, context) => {
5+
const ticketPrefixes = pluginConfig[INPUTS.ticketPrefixes];
6+
const jiraHost = pluginConfig[INPUTS.jiraHost];
7+
const notes = await releaseNotesGenerator.generateNotes(pluginConfig, context);
8+
9+
let issueRegex;
10+
11+
if (!ticketPrefixes) {
12+
issueRegex = new RegExp(ISSUE_REGEX, "g");
13+
} else if (ticketPrefixes.length > 0) {
14+
issueRegex = new RegExp(`((${ticketPrefixes.join("|")})-[1-9][0-9]*)`, "g");
15+
} else {
16+
return notes;
17+
}
18+
19+
return notes?.replace(issueRegex, `[$1](https://${jiraHost}/browse/$1)`);
20+
};

src/lib/verify-conditions.js

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const { InputRequiredError, RegexError } = require("./errors");
2+
const SemanticReleaseError = require("@semantic-release/error");
3+
const AggregateErrorPromise = import("aggregate-error");
4+
const constants = require("./constants");
5+
6+
const { INPUTS, TICKET_PREFIX_REGEX, DOMAIN_NAME_REGEX } = constants;
7+
8+
module.exports = async pluginConfig => {
9+
const ticketPrefixes = pluginConfig[INPUTS.ticketPrefixes] ?? [];
10+
const jiraHost = pluginConfig[INPUTS.jiraHost];
11+
const errors = [];
12+
13+
const AggregateError = (await AggregateErrorPromise).default;
14+
15+
if (!Array.isArray(ticketPrefixes)) {
16+
errors.push(new SemanticReleaseError(INPUTS.ticketPrefixes, Array.name));
17+
} else {
18+
for (const prefix of ticketPrefixes) {
19+
if (!TICKET_PREFIX_REGEX.test(prefix)) {
20+
errors.push(new RegexError(prefix, TICKET_PREFIX_REGEX));
21+
}
22+
}
23+
}
24+
25+
if (!jiraHost) {
26+
errors.push(new InputRequiredError(INPUTS.jiraHost));
27+
} else if (!DOMAIN_NAME_REGEX.test(jiraHost)) {
28+
errors.push(new RegexError(jiraHost, DOMAIN_NAME_REGEX));
29+
}
30+
31+
if (errors.length > 0) {
32+
throw new AggregateError(errors);
33+
}
34+
};

0 commit comments

Comments
 (0)