-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgenerate-changelog.ts
141 lines (113 loc) · 3.85 KB
/
generate-changelog.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
// $ deno run --allow-net --allow-env ./generate-changelog.ts > CHANGELOG.md
import {
difference,
format,
parse,
} from "https://deno.land/std@0.160.0/datetime/mod.ts";
type JSONValue = string | number | boolean | JSONObject | Array<JSONValue>;
interface JSONObject {
[x: string]: JSONValue;
}
interface PullRequest {
title: string;
user: string;
repo: string;
url: string;
mergedAt: string;
}
interface Repo {
name: string;
defaultBranch: string;
}
const org = "verygoodopensource";
const now = new Date();
const token = Deno.env.get("CHANGELOG_GITHUB_TOKEN");
const headers = { Authorization: `Bearer ${token}` };
const githubApi = "https://api.github.com";
const repositories = await getRepositories(org);
const pullRequests = await getPullRequests(org, repositories);
const today = format(now, "MM-dd-yyyy");
console.log(`# Very Good Changelog (${today})`);
console.log(`\nTODO: Someone should write a nice description for this CHANGELOG`);
for (const repo of repositories) {
const repoPullRequests = pullRequests[repo.name];
if (repoPullRequests.length === 0) continue;
console.log(`\n## ${repo.name}`);
for (const issue of repoPullRequests) {
console.log(`- ${issue.title} ([@${issue.user}](https://github.com/${issue.user}))`);
console.log(`\t- ${issue.url}`);
}
}
async function getPullRequests(
org: string,
repos: Array<Repo>
): Promise<{ [repo: string]: Array<PullRequest> }> {
const pullRequests: { [repo: string]: Array<PullRequest> } = {};
for (const repo of repos) {
pullRequests[repo.name] = [];
}
for (const repo of repos) {
pullRequests[repo.name].push(...await getMorePullRequests(org, repo));
}
return pullRequests;
}
async function getMorePullRequests(org: string, repo: Repo, page: number = 1): Promise<PullRequest[]> {
const url = `${githubApi}/repos/${org}/${repo.name}/pulls?state=closed&sort=updated&per_page=100&base=${repo.defaultBranch}&page=${page}&direction=desc`;
let pullRequests: PullRequest[] = [];
const response = await fetch(`${url}`, {
headers: headers,
});
if (response.status != 200) {
console.error(`[ERROR] GET ${url} (${response.status}): ${await response.json()}`);
return pullRequests;
}
const body = (await response.json()) as Array<JSONObject>;
let findMore = true;
for (var element of body) {
const title = element["title"] as string;
const url = element["html_url"] as string;
const mergedAt = element["merged_at"] as string;
const user = (element["user"] as JSONObject)["login"] as string;
// Skip non-merged ones.
if (!mergedAt) continue;
// Ignoring bot users.
if (user.includes("[bot]")) continue;
const diff = difference(parse(mergedAt, "yyyy-MM-ddTHH:mm:ssZ"), now);
findMore = (diff.weeks ?? 0) < 1;
// Too old, rest will be older so lets break.
if (!findMore) break;
pullRequests.push({
title,
user,
url,
mergedAt,
repo: repo.name,
});
}
if (findMore) {
pullRequests.push(...await getMorePullRequests(org, repo, ++page));
}
return pullRequests;
}
async function getRepositories(org: string): Promise<Repo[]> {
const url = `${githubApi}/orgs/${org}/repos?sort=updated&per_page=100`;
const response = await fetch(`${url}`, {
headers: headers,
});
if (response.status != 200) {
throw new Error(`[ERROR] GET ${url} (${response.status})`);
}
const body = (await response.json()) as Array<JSONObject>;
return body
.filter((element) => {
const pushedAt = element["pushed_at"] as string;
const diff = difference(parse(pushedAt, "yyyy-MM-ddTHH:mm:ssZ"), now);
return (diff.weeks ?? 0) < 1 && !['changelogs', '.github'].includes(element['name'] as string);
})
.map((element) => {
return {
name: element['name'] as string,
defaultBranch: element['default_branch'] as string,
};
});
}