forked from ellismg/github-pr-changelog
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcli.ts
147 lines (117 loc) · 5.9 KB
/
cli.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
#!/usr/bin/env node
import * as program from "commander";
import * as changelog from "./changelog";
import * as colors from "colors";
import * as path from "path";
import * as moment from "moment";
let description =
`GitHub pull request changelog generator
* By default, only PRs that have either "impact/changelog" or "impact/breaking" are printed.
* To print all PRs, pass the CLI option --all-prs
* "Added" section: PRs whose label contains the word "feature" or "enhancement"
* "Fixed" section: PRs whose label contains the word "bug"
* "Changed" section: PRs that do not fall in the "Added" or "Fixed" category OR have the label "impact/breaking"`;
program
.version('0.1.1', '-v, --version')
.description(description)
.option('-f, --from <tag>', 'Start of changelog range, as a git tag or revision')
.option('-t, --to <tag>', 'End of changelog range, as a git tag or revision')
.option('-o, --owner <owner>', 'GitHub owner or organization')
.option('-r, --repos <repo>', 'GitHub repo')
.option('-d, --git-directory [directory]', 'Parent directory of git working tree for `repo`. Defaults to current directory if not specified.')
.option('--token [token]', 'GitHub access token. If not provided, uses environment variable GITHUB_TOKEN.')
.option('--all-prs', 'List all pull requests, regardless of the label')
.option('--tab-output', 'Output as table, instead of markdown')
.parse(process.argv);
if (! (program.from && program.to && program.owner && program.repos)) { // required options
program.outputHelp( (text: any) => colors.red(text) );
process.exit(1);
}
if (! (program.token || process.env.GITHUB_TOKEN)) {
console.error("Error: no GitHub token in `token` or environment variable `GITHUB_TOKEN`");
program.outputHelp( (text: any) => colors.red(text) );
process.exit(1);
}
let ghToken = program.token || process.env.GITHUB_TOKEN;
program.gitDirectory = program.gitDirectory || ".";
enum ItemSection {
Breaking = "(**Breaking**) ", // the space at the end is important, as it makes formatting easier
Added = "Added",
Fixed = "Fixed",
Changed = "Changed"
}
(async () => {
const changelogLabels = program.allPrs ? undefined : [ "impact/changelog", "impact/breaking" ];
let allPrs: any[] = [];
let allContributors = new Set();
program.repos = program.repos.split(",");
for (let repo of program.repos) {
let directory = path.join(program.gitDirectory, repo);
// will filter on changelogLabels, which will be no filtering if `program.allPrs` is true
let prResult = await changelog.getPullsInRange(
program.owner, repo,
directory, program.from, program.to, ghToken, changelogLabels);
allPrs = allPrs.concat(prResult.pullRequests);
prResult.contributors.forEach(user => allContributors.add(user));
}
// add fields to the pull request objects, to make filtering easier
allPrs.forEach(pr => {
pr.repoName = pr.head.repo.full_name;
pr.changelog = hasLabelString(pr, "changelog");
pr.breaking = hasLabelString(pr, "breaking");
// NOTE: pr.section will be undefined if items without "Changelog" are in the list
if (hasLabelString(pr, "bug")) {
pr.section = ItemSection.Fixed;
} else if (hasLabelString(pr, "feature") || hasLabelString(pr, "enhancement")) {
pr.section = ItemSection.Added;
} else if (pr.breaking) {
pr.section = ItemSection.Breaking;
} else if (pr.changelog) {
pr.section = ItemSection.Changed;
}
});
if (program.tabOutput) {
console.log(`Title\tUser\tIsChangelog\tIsBreaking\tChangelog Section\tRepo\tLink`)
allPrs.forEach(pr => {
console.log(
`${pr.title}\t${pr.user.login}\t${pr.changelog}\t${pr.breaking}\t${pr.section}\t` +
`${pr.repoName}\t=HYPERLINK("${pr.html_url}", "${pr.html_url}")`
);
});
} else {
let anchorName = program.to.replace(/^v0/, 'v'); // remove leading zero in version
anchorName = anchorName.replace(/\./g, ''); // remove periods from version number (since they don't work in anchor)
console.log(`## ${program.to} {#${anchorName}}`);
console.log('\nReleased on ' + moment().format('MMMM DD, YYYY'));
let contributors = Array.from(allContributors).sort().map(user => `[${user}](https://github.com/${user})`).join('\n');
console.log(`\n**Contributors:**\n${contributors}`);
console.log(`\n### Added`);
printSection(ItemSection.Added, allPrs, true);
console.log("\n### Changed");
printSection(ItemSection.Breaking, allPrs, true)
printSection(ItemSection.Changed, allPrs, true);
console.log("\n### Fixed");
printSection(ItemSection.Fixed, allPrs, true);
}
console.error("*** Output complete ***");
})();
function hasLabelString(gitHubPr: any, labelText: string): boolean {
return gitHubPr.labels.filter( (l: any) => l.name.includes(labelText) ).length > 0
}
function formatAsMarkdown(gitHubPr: any, includeBody: boolean): string {
// print a prefix for [BREAKING]
let prefixStr = (gitHubPr.section == ItemSection.Breaking) ? gitHubPr.section : "";
let body =
includeBody ?
`<!-- BEGIN BODY ${gitHubPr.number} -->\n${gitHubPr.body}\n<!-- END BODY ${gitHubPr.number}-->`
: "";
let result =
`<!-- ${gitHubPr.section} ${gitHubPr.repoName}#${gitHubPr.number} -->\n`
+ `- ${prefixStr}${gitHubPr.title} ([${gitHubPr.repoName}#${gitHubPr.number}](${gitHubPr.html_url})).\n`
+ body;
return result;
}
function printSection(sectionFilter: ItemSection, gitHubPrs: any[], includeBody: boolean): void {
let sectionPrs = gitHubPrs.filter(pr => pr.section == sectionFilter);
sectionPrs.forEach(pr => { console.log(formatAsMarkdown(pr, includeBody)) });
}