forked from danger/peril
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdanger_run.ts
192 lines (176 loc) · 6.11 KB
/
danger_run.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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
import _ = require("lodash")
import { DangerfileReferenceString, RunnerRuleset } from "../db"
/**
* Think about it, we can't provide the same DSL
* to a PR as we send to an Issue, or to User creation, the lack of
* reference to a PR means that we can't do work like finding diffs.
*/
export enum RunType {
/** What, for years, has been the "Danger DSL" */
pr,
/** Take whatever JSON triggered this run and use that as the `github.` DSL */
import,
}
/** Can Danger reply inline? */
export enum RunFeedback {
/** Is there a way in which Danger can provide any feedback? */
commentable,
/** Can only execute the JS, no feedback into an issue as the event doesn't correlate to one */
silent,
}
/** Represents runs that Danger should do based on Rules and Events */
export interface DangerRun extends RepresentationForURL {
/** What event name triggered this */
event: string
/** What action inside that event trigger this run */
action: string | null
/** What type of DSL should the run use? */
dslType: RunType
/** Can Danger provide commentable feedback? */
feedback: RunFeedback
}
/**
* If the user's settings JSON includes a particular key,
* switch it out to be something else
*/
const keyMapper = (key: string) => {
// See: https://github.com/danger/peril/issues/371
if (key === "pull_request") {
return "pull_request.opened, pull_request.synchronize, pull_request.edited"
}
return key
}
// The opposite of the above, so it can get back when looking up settings object
const reverseKeyMapper = (key: string) => {
if (key === "pull_request.opened, pull_request.synchronize, pull_request.edited") {
return "pull_request"
}
return key
}
/**
* Takes an event and action, and defines whether to do a dangerfile run with it.
*
* This is a complex function, because it has complex behaviors.
* Here is what it supports, at a high level:
*
* - Simple direct key matches
* - Sub-key action matches (e.g. `pull_request.opened`)
* - Glob matches (e.g. `pull_Request.*`)
* - Comma separated keys (e.g. `pull_request.opened, pull_request.closed`)
* - JSON evaluated keys (e.g. `pull_request (pull_request.id == 12)`)
* - Support internal mapping keys (e.g. `pull_request` -> `pull_request.opened, pull_request.closed`)
*/
export const dangerRunForRules = (
event: string,
action: string | null,
rules: RunnerRuleset | undefined | null,
webhook: any,
prefixRepo?: string
): DangerRun[] => {
// tslint:disable-line
// Can't do anything with nothing
if (!rules) {
return []
}
// These are the potential keys that could trigger a run
const directKey = event
const globsKey = event + ".*"
const dotActionKey = event + "." + action
const arrayVersions = Object.keys(rules)
.map(keyMapper)
// Look through all existing rules to see if we can
// find a key that matches the incoming rules
.filter(key => {
// Take into account comma split strings:
// "pull_request.opened, pull_request.closed"
//
// also take into account webhook checking:
// "pull_request (pull_request.id == 12)"
//
const eachRule = key.split(",").map(i => i.split("(")[0].trim())
const allKeys = [directKey, globsKey, dotActionKey]
return allKeys.some(potentialKey => eachRule.includes(potentialKey))
})
.filter(key => {
// Do the webhook data check, return early if there's no () or ==
if (!key.includes("(") || !key.includes("==")) {
return true
}
try {
// get inside the ( )
const inner = key.split("(")[1].split(")")[0]
// allow (x == 1) and (x==1)
const keypath = inner.split(" ")[0].split("=")[0]
const value = _.get(webhook, keypath)
let expected: any = inner.split("==")[1].trim()
// handle boolean values
if (expected === "true") {
expected = true
}
if (expected === "false") {
expected = false
}
// We want to allow things like 1 to equal "1"
// tslint:disable-next-line:triple-equals
return value == expected
} catch (error) {
// Bail, so just always fail to indicate that it didn't run
return false
}
})
.map(key => {
const alwaysArray = (t: any) => (Array.isArray(t) ? t : [t])
return alwaysArray(rules[reverseKeyMapper(key)])
})
let possibilities: string[] = []
arrayVersions.forEach(arr => {
possibilities = possibilities.concat(arr)
})
// Basically, if we provide a prefix repo, the blank repos
// should use that repo
if (prefixRepo) {
possibilities = possibilities.map(p => (p.includes("@") ? p : `${prefixRepo}@${p}`))
}
return possibilities.map(path => ({
action,
dslType: dslTypeForEvent(event),
event,
...dangerRepresentationForPath(path),
feedback: feedbackTypeForEvent(event),
}))
}
export interface RepresentationForURL {
/** The path the the file aka folder/file.ts */
dangerfilePath: string
/** The branch to find the dangerfile on */
branch: string
/** An optional repo */
repoSlug: string | undefined
/** The original full string, with repo etc */
referenceString: DangerfileReferenceString
}
/** Takes a DangerfileReferenceString and lets you know where to find it globally */
export const dangerRepresentationForPath = (value: DangerfileReferenceString): RepresentationForURL => {
const afterAt = value.includes("@") ? value.split("@")[1] : value
return {
branch: value.includes("#") ? value.split("#")[1] : "master",
dangerfilePath: afterAt.split("#")[0],
repoSlug: value.includes("@") ? value.split("@")[0] : undefined,
referenceString: value,
}
}
/** What type of DSL should get used for the Dangerfile eval? */
export const dslTypeForEvent = (event: string): RunType => {
if (event === "pull_request") {
return RunType.pr
}
return RunType.import
}
/** What events can we provide feedback inline with? */
// Build system mentions?
export const feedbackTypeForEvent = (event: string): RunFeedback => {
if (event === "pull_request" || event === "issues" || event === "issue") {
return RunFeedback.commentable
}
return RunFeedback.silent
}