forked from canjs/canjs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgenerate-release-notes.js
207 lines (169 loc) · 6.67 KB
/
generate-release-notes.js
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/**
* Node script that aggregates release notes for CanJS dependencies in one markdown note.
* This note can be used for CanJS release notes or for general reference.
* You’ll need a personal access token to run this script: https://github.com/blog/1509-personal-api-tokens
* To execute:
* node generate-release-notes.js <access token> <older version> <newer version>
* // returns a string in markdown with the aggregated release notes.
* Example usage with arguments:
* node generate-release-notes.js s1w5i2f2t v3.8.1 v3.9.0
* // returns a string in markdown with the all can-* dependency release notes between CanJS v3.8.1 and CanJS v3.9.0
* Without optional arguments:
* node generate-release-notes.js s1w5i2f2t
* * // returns a string in markdown with the all can-* dependency release notes between the most recent CanJS releases
*/
const util = require("util");
const execFile = util.promisify(require("child_process").execFile);
const GitHubApi = require("github");
const semver = require("semver");
// Default to canjs/canjs repo
const OWNER = 'canjs';
const REPO = 'canjs'
const TOKEN = process.argv[2];
const github = new GitHubApi({
"host": "api.github.com",
"pathPrefix": "/repos",
"protocol": "https",
"headers": {
"accept": "application/vnd.github.v3+json",
"user-agent": OWNER
},
"requestMedia": "application/vnd.github.something-custom",
});
github.authenticate({
type: "oauth",
token: TOKEN
});
async function initialize() {
const currentRelease = process.argv[4];
const previousRelease = process.argv[3];
const fileContents = await getPackageJsonByRelease(previousRelease, currentRelease);
const updatedDependencies = getUpdatedDependencies(fileContents.previousRelease, fileContents.currentRelease);
const allReleaseNotes = await getAllReleaseNotes(updatedDependencies);
const aggregateReleaseNote = await createAggregateReleaseNote(allReleaseNotes, currentRelease);
postReleaseNote(aggregateReleaseNote)
}
async function getPackageJsonByRelease(previousRelease, currentRelease) {
const recentReleaseShas = [];
let latestReleaseSha;
let previousReleaseSha;
// try to get the commit sha from the tags passed in
// if not it defaults to the most recent release commits
try {
await execFile("git", ["fetch", "--tags"]);
const { stdout } = await execFile("git", ["log", "--pretty=oneline", previousRelease + "..." + currentRelease]);
const logs = stdout.split("\n");
latestReleaseSha = logs[1].slice(0,7);
previousReleaseSha = logs[logs.length-2].slice(0,7);
} catch(err) {
console.error("The release tags you have passed do not have a match. Using the two most recent releases instead.")
try {
const { stdout } = await execFile("git", ["log", "--pretty=oneline", "-30"]);
const logs = stdout.split("\n");
logs.forEach((log) => {
if(log.slice(41) === "Update dist for release") {
recentReleaseShas.push(log.slice(0, 7))
}
})
latestReleaseSha = recentReleaseShas[0];
previousReleaseSha = recentReleaseShas[1];
} catch (err) {
console.error('Error retrieving or matching the most recent release commits.')
}
}
try {
oldVerPackage = await getFileContentFromCommit(previousReleaseSha, "package.json");
newVerPackage = await getFileContentFromCommit(latestReleaseSha, "package.json");
} catch(err) {
console.error('Error: getFileFileContentFromCommit', err);
}
return { previousRelease: oldVerPackage, currentRelease: newVerPackage };
}
async function getFileContentFromCommit(sha, filename) {
if (sha === "latest") {
const { stdout } = await execFile("cat", [filename]);
return JSON.parse(stdout);
} else {
const revision = sha + ":" + filename;
const { stdout } = await execFile("git", ["show", revision]);
return JSON.parse(stdout);
}
}
function getUpdatedDependencies(prevVer, currentVer) {
let updatedDependencies = {};
for (let key in currentVer.dependencies) {
if (!prevVer.dependencies[key] || (prevVer.dependencies[key] !== currentVer.dependencies[key])) {
updatedDependencies[key] = {
currentVer: currentVer.dependencies[key],
prevVer: prevVer.dependencies[key]
};
}
}
return updatedDependencies;
}
async function matchTags(repo, diff) {
try {
//the maximum number of match tags to return
const upperBound = 10
let tags = [];
const res = await github.gitdata.getTags({ "owner": OWNER, "repo": repo });
for (let i = res.data.length - 1; i >= 0; i--) {
let currentRef = res.data[i].ref.split("/")[2].slice(1);
if (diff.prevVer && currentRef === diff.prevVer || tags.length >= upperBound) break;
tags.push(res.data[i]);
}
// sort in ascending order by ref
return tags.sort((v1, v2) => semver.gt(v1.ref.slice(10), v2.ref.slice(10)));
} catch(err) {
console.error('Error in matchTags', err)
}
}
async function getAllReleaseNotes(updatedDependencies) {
const matchingTags = [];
let releaseNotes = {};
for (let key in updatedDependencies) {
try {
matchingTags[key] = await matchTags(key, updatedDependencies[key]);
} catch(err) {
console.error('Error in getAllReleaseNotes', err)
}
}
await Promise.all(Object.keys(matchingTags).map(async function(package, index) {
releaseNotes[package] = await Promise.all(matchingTags[package].map(async function(taggedRelease) {
let version = taggedRelease.ref.split("/")[2];
let title = "";
let body = "";
try {
let release = await github.repos.getReleaseByTag({
"owner": OWNER,
"repo": package,
"tag": version
});
if (release.data.name) {
title = release.data.name;
body = release.data.body;
}
} catch(err) {
// console.error(`${package} ${version}: getReleaseByTag Error Code ${err.code}: ${err.message} `);
}
return `[${package} ${version}${title ? " - " + title : ""}](https://github.com/canjs/${package}/releases/tag/${version})${body ? "\n" + body : ""}`;
}));
}));
return releaseNotes;
}
function createAggregateReleaseNote(allReleaseNotes, currentRelease) {
let releaseNote = `# ${OWNER}/${REPO} ${currentRelease || 'INSERT VERSION HERE'} Release Notes \n`;
Object.keys(allReleaseNotes).sort().forEach(function(package) {
releaseNote = `${releaseNote} \n## [${package}](https://github.com/canjs/${package}/releases) \n`;
allReleaseNotes[package].forEach(function(note) {
if (note) {
releaseNote = `${releaseNote} - ${note} \n`;
}
})
})
return releaseNote;
}
function postReleaseNote(note) {
console.log(note);
}
initialize();