forked from apollographql/react-apollo
-
Notifications
You must be signed in to change notification settings - Fork 1
/
dangerfile.ts
100 lines (83 loc) · 3.35 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
// Removed import
const includes = require('lodash.includes');
import * as fs from 'fs';
// Setup
const pr = danger.github.pr;
const commits = danger.github.commits;
const modified = danger.git.modified_files;
const bodyAndTitle = (pr.body + pr.title).toLowerCase();
// Custom modifiers for people submitting PRs to be able to say "skip this"
const trivialPR = bodyAndTitle.includes('trivial');
const acceptedNoTests = bodyAndTitle.includes('skip new tests');
const typescriptOnly = (file: string) => includes(file, '.ts');
const filesOnly = (file: string) => fs.existsSync(file) && fs.lstatSync(file).isFile();
// Custom subsets of known files
const modifiedAppFiles = modified
.filter(p => includes(p, 'src/'))
.filter(p => filesOnly(p) && typescriptOnly(p));
const modifiedTestFiles = modified.filter(p => includes(p, 'test/'));
// Takes a list of file paths, and converts it into clickable links
const linkableFiles = paths => {
const repoURL = danger.github.pr.head.repo.html_url;
const ref = danger.github.pr.head.ref;
const links = paths.map(path => {
return createLink(`${repoURL}/blob/${ref}/${path}`, path);
});
return toSentence(links);
};
// ["1", "2", "3"] to "1, 2 and 3"
const toSentence = (array: Array<string>): string => {
if (array.length === 1) {
return array[0];
}
return array.slice(0, array.length - 1).join(', ') + ' and ' + array.pop();
};
// ("/href/thing", "name") to "<a href="/href/thing">name</a>"
const createLink = (href: string, text: string): string => `<a href='${href}'>${text}</a>`;
// Raise about missing code inside files
const raiseIssueAboutPaths = (type: Function, paths: string[], codeToInclude: string) => {
if (paths.length > 0) {
const files = linkableFiles(paths);
const strict = '<code>' + codeToInclude + '</code>';
type(`Please ensure that ${strict} is enabled on: ${files}`);
}
};
const authors = commits.map(x => x.author && x.author.login);
const isBot = authors.some(x => ['greenkeeper', 'renovate'].indexOf(x) > -1);
if (!isBot) {
// Rules
// When there are app-changes and it's not a PR marked as trivial, expect
// there to be CHANGELOG changes.
const changelogChanges = includes(modified, 'Changelog.md');
if (modifiedAppFiles.length > 0 && !trivialPR && !changelogChanges) {
fail('No CHANGELOG added.');
}
// No PR is too small to warrant a paragraph or two of summary
if (pr.body.length === 0) {
fail('Please add a description to your PR.');
}
const hasAppChanges = modifiedAppFiles.length > 0;
const hasTestChanges = modifiedTestFiles.length > 0;
// Warn when there is a big PR
const bigPRThreshold = 500;
if (danger.github.pr.additions + danger.github.pr.deletions > bigPRThreshold) {
warn(':exclamation: Big PR');
}
// Warn if there are library changes, but not tests
if (hasAppChanges && !hasTestChanges) {
warn(
"There are library changes, but not tests. That's OK as long as you're refactoring existing code",
);
}
// Be careful of leaving testing shortcuts in the codebase
const onlyTestFiles = modifiedTestFiles.filter(x => {
const content = fs.readFileSync(x).toString();
return (
content.includes('it.only') ||
content.includes('describe.only') ||
content.includes('fdescribe') ||
content.includes('fit(')
);
});
raiseIssueAboutPaths(fail, onlyTestFiles, 'an `only` was left in the test');
}