-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathpr-jira-issue-link.js
More file actions
110 lines (92 loc) · 3.71 KB
/
Copy pathpr-jira-issue-link.js
File metadata and controls
110 lines (92 loc) · 3.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
const JIRA_KEY_PATTERN = /\bTTAHUB-\d+\b/gi;
const JIRA_ISSUE_LINK_PATTERN = /https:\/\/jira\.acf\.gov\/browse\/(TTAHUB-\d+)\b/gi;
const PLACEHOLDER_JIRA_LINK_PATTERN = /https:\/\/jira\.acf\.gov\/browse\/TTAHUB-0\b/i;
const ISSUES_HEADINGS = ['Jira Issue(s)', 'Jira Issue', 'Issue(s)', 'Issue'];
const PLACEHOLDER_JIRA_KEY = 'TTAHUB-0';
function standardizeLineEndings(text = '') {
return text.replace(/\r\n/g, '\n');
}
function stripHtmlComments(text = '') {
return text.replace(/<!--[\s\S]*?-->/g, ' ');
}
function extractSectionContent(body = '', heading = '') {
const bodyWithStandardLineEndings = standardizeLineEndings(body);
const headings = [...bodyWithStandardLineEndings.matchAll(/^##\s+.*$/gm)];
const sectionHeading = headings.find((match) => match[0].trim() === `## ${heading}`);
if (!sectionHeading) {
return '';
}
const currentIndex = headings.findIndex((match) => match.index === sectionHeading.index);
const nextHeading = headings[currentIndex + 1];
const contentEnd = nextHeading ? nextHeading.index : bodyWithStandardLineEndings.length;
return bodyWithStandardLineEndings
.slice(sectionHeading.index + sectionHeading[0].length, contentEnd)
.trim();
}
function extractJiraKeys(text = '') {
const keys = standardizeLineEndings(text).match(JIRA_KEY_PATTERN) || [];
return [...new Set(keys.map((key) => key.toUpperCase()))];
}
function extractLinkedJiraIssues(text = '') {
const matches = [...standardizeLineEndings(text).matchAll(JIRA_ISSUE_LINK_PATTERN)];
return [
...new Set(
matches.map((match) => match[1].toUpperCase()).filter((key) => key !== PLACEHOLDER_JIRA_KEY)
),
];
}
function extractIssuesSection(body = '') {
return (
ISSUES_HEADINGS.map((heading) => extractSectionContent(body, heading)).find(
(section) => section && section.trim()
) || ''
);
}
function validatePullRequestBody(body = '') {
const issuesSection = extractIssuesSection(body);
const issuesSectionWithoutComments = stripHtmlComments(issuesSection);
const bodyWithoutComments = stripHtmlComments(body);
const linkedIssues = extractLinkedJiraIssues(issuesSectionWithoutComments);
const placeholderUsed = PLACEHOLDER_JIRA_LINK_PATTERN.test(issuesSectionWithoutComments);
const linkedIssuesElsewhere = extractLinkedJiraIssues(bodyWithoutComments);
const jiraKeysInIssuesSection = extractJiraKeys(issuesSectionWithoutComments).filter(
(key) => key !== PLACEHOLDER_JIRA_KEY
);
if (!issuesSection.trim()) {
return {
valid: false,
jiraKeys: [],
message:
'PR validation failed: add the approved Jira issue link to the `Jira Issue(s)` section.',
};
}
if (placeholderUsed) {
return {
valid: false,
jiraKeys: linkedIssues,
message: `PR validation failed: remove the ${PLACEHOLDER_JIRA_KEY} placeholder from the \`Jira Issue(s)\` section and keep only approved Jira issue links.`,
};
}
if (!linkedIssues.length) {
const keyFormatHint = jiraKeysInIssuesSection.length
? ` Jira key(s) found without links: ${jiraKeysInIssuesSection.join(', ')}. Use the full https://jira.acf.gov/browse/TTAHUB-#### issue link format.`
: '';
const locationHint = linkedIssuesElsewhere.length
? ` Jira issue link(s) found outside \`Jira Issue(s)\`: ${linkedIssuesElsewhere.join(', ')}.`
: '';
return {
valid: false,
jiraKeys: [],
message: `PR validation failed: the \`Jira Issue(s)\` section must include at least one approved Jira issue link.${keyFormatHint}${locationHint}`,
};
}
return {
valid: true,
jiraKeys: linkedIssues,
message: `Validated Jira issue link(s): ${linkedIssues.join(', ')}`,
};
}
module.exports = {
extractIssuesSection,
validatePullRequestBody,
};