forked from canjs/canjs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
generate-release-notes.js
204 lines (167 loc) · 6.41 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
/**
* 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.
* To execute:
* node generate-release-notes.js <older version> <newer version>
* // returns a string in markdown with the aggregated release notes.
* Example usage with arguments:
* node generate-release-notes.js 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
* * // 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");
// Default to canjs/canjs repo
const OWNER = 'canjs';
const REPO = 'canjs'
// const TOKEN;
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[3]
const previousRelease = process.argv[2];
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);
tags.push(res.data[i]);
if (diff.prevVer && currentRef === diff.prevVer || tags.length >= upperBound) break;
}
return tags;
} 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];
try {
let release = await github.repos.getReleaseByTag({
"owner": OWNER,
"repo": package,
"tag": version
});
if (release.data.name) {
let title = release.data.name;
let body = release.data.body;
return `${package} ${version}: ${title} \n - ${body || "none"}`;
}
} catch(err) {
console.error(`${package} ${version}: getReleaseByTag Error Code ${err.code}: ${err.message} `);
}
// return `${package} ${version} doesn't have release notes`;
return null;
}));
}));
return releaseNotes;
}
function createAggregateReleaseNote(allReleaseNotes, currentRelease) {
let releaseNote = `# ${OWNER}/${REPO} ${currentRelease || 'INSERT VERSION HERE'} Release Notes \n`;
Object.keys(allReleaseNotes).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`;
}
})
})
releaseNote = `${releaseNote} \n ----`;
return releaseNote;
}
function postReleaseNote(note) {
console.log(note);
}
initialize();