-
Notifications
You must be signed in to change notification settings - Fork 12
/
dangerfile.ts
139 lines (124 loc) · 4.31 KB
/
dangerfile.ts
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import { readFile } from 'node:fs/promises'
import { join } from 'node:path'
import lint from '@commitlint/lint'
import load from '@commitlint/load'
import {
LintOptions,
LintOutcome,
ParserOptions,
ParserPreset
} from '@commitlint/types'
import { danger, fail, markdown, warn } from 'danger'
import config from './commitlint.config'
export default async () => {
// merge queues not supported by danger-js
if (danger.github.pr == null) return
const isDependabot = danger.github.pr.user.login === 'renovate[bot]'
// check lockfile updated when package changes
const packageChanged = danger.git.modified_files.includes('package.json')
const lockfileChanged =
danger.git.modified_files.includes('package-lock.json')
if (packageChanged && !lockfileChanged) {
const message =
'Changes were made to package.json, but not to package-lock.json'
const idea = 'Perhaps you need to run `npm install`?'
warn(`${message} - <i>${idea}</i>`)
}
// check max changes fall below threshold
const CHANGE_THRESHOLD = 600
const changeCount = danger.github.pr.additions + danger.github.pr.deletions
if (changeCount > CHANGE_THRESHOLD) {
warn(`:exclamation: Big PR (${changeCount} changes)`)
markdown(
`> (change count - ${changeCount}): Pull Request size seems relatively large. If Pull Request contains multiple changes, split each into separate PR will helps faster, easier review.`
)
}
// check PR has well-formed title
const commitlintReport = await lintPrTitle(
`${danger.github.pr.title} #(${danger.github.pr.number})`
)
if (!commitlintReport.valid) {
fail('Please ensure your PR title matches commitlint convention.')
let errors = ''
commitlintReport.errors.forEach((error) => {
errors = `${errors}# ${error.message}\n`
})
markdown(`> (pr title - ${danger.github.pr.title}): \n${errors}`)
}
// check PR has description and is different from template
const pullRequestTemplate = await readFile(
join(__dirname, '/.github/pull_request_template.md'),
'utf8'
)
if (
danger.github.pr.body.length < 10 ||
danger.github.pr.body.replace(/\r\n/g, '\n') === pullRequestTemplate
) {
fail(
'This pull request needs a description (that differs from the template).'
)
}
// check PR has assignee
if (danger.github.pr.assignee === null) {
fail('Please assign someone to merge this PR.')
}
// check PR has type label
if (
!danger.github.issue.labels.some((label) => label.name.includes('type:'))
) {
fail('Please add type label to this PR.')
}
// check PR has priority label
if (
!danger.github.issue.labels.some((label) =>
label.name.includes('priority:')
)
) {
fail('Please add priority label to this PR.')
}
// check PR has effort label
if (
!danger.github.issue.labels.some((label) => label.name.includes('effort:'))
) {
fail('Please add effort label to this PR.')
}
// pull PR data from GitHub API
const currentPR = await danger.github.api.pulls.get({
...danger.github.thisPR,
pull_number: danger.github.thisPR.number
})
// check PR has milestone
// ignore dependabot
if (currentPR.data.milestone === null && !isDependabot) {
fail('Please add milestone to this PR.')
}
// pull reviews for PR from GitHub API
const reviews = await danger.github.api.pulls.listReviews({
...danger.github.thisPR,
pull_number: danger.github.thisPR.number
})
// check PR has requested reviewers or completed reviews
if (
currentPR.data.requested_reviewers != null &&
currentPR.data.requested_reviewers.length === 0 &&
reviews.data.length === 0
) {
fail('Please request a reviewer for this PR.')
}
}
async function lintPrTitle(title: string): Promise<LintOutcome> {
const loaded = await load(config)
const parserOpts = selectParserOpts(loaded.parserPreset)
const opts: LintOptions & { parserOpts: ParserOptions } = {
parserOpts: parserOpts ?? {},
plugins: loaded.plugins ?? {},
ignores: loaded.ignores ?? [],
defaultIgnores: loaded.defaultIgnores ?? true
}
return lint(title, loaded.rules, opts)
}
function selectParserOpts(preset?: ParserPreset): ParserOptions | undefined {
if (typeof preset !== 'object') return undefined
if (typeof preset.parserOpts !== 'object') return undefined
return preset.parserOpts ?? undefined
}