Skip to content

Commit d2aceaa

Browse files
committedNov 21, 2019
chore: add script for automatic changelog generation
Add script to generate the changelog automatically. The script should be called from the root of the repository in the following way: ``` node generate_changelog.js <milestone> <GitHub Access Token which has read access to repository> ``` For example: ``` node generate_changelog.js 6.2.2 2d2156c261bb1494f7a6e22f11fa446c7ca0e6b7 ``` The script uses GitHub's GraphQL API v4. More information about it can be found [here](https://developer.github.com/v4/). The script is designed to work with current NativeScript CLI workflow: - it generates changelog based on CLI's Changelog.md format - it generates changelog only for issues, PR's added in the milestone are not included in the changelog - issues with no-changelog label are not added to the milestone. A warning is shown for them. - issues with feature label are added to the "New" section of the changelog. All other issues are added to the "Fixed" section. - issues, which are not in state "Ready for Test", "In testing" or "Done" are not added to the changelog. A warning is shown for them.
1 parent 051fb7e commit d2aceaa

File tree

2 files changed

+197
-1
lines changed

2 files changed

+197
-1
lines changed
 

‎.npmignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ scratch/
3131
docs/html/
3232
dev/
3333

34-
.travis/**/*
34+
.travis/**/*
35+
generate_changelog.js

‎generate_changelog.js

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
"use strict";
2+
3+
const _ = require("lodash");
4+
const request = require("request");
5+
const fs = require("fs");
6+
const path = require("path");
7+
require("colors");
8+
9+
const argv = process.argv;
10+
if (argv.length < 3 || argv.length > 4) {
11+
console.error(`Incorrect usage. You need to pass the milestone and optionally the Authorization token.\n`.red +
12+
`### Example:
13+
node generate_changelog.js 6.2.2 2d2156c261bb1494f7a6e22f11fa446c7ca0e6b7\n`.yellow);
14+
process.exit(127);
15+
}
16+
17+
const selectedMilestone = process.argv[2];
18+
const token = process.argv[3] || process.env.NS_CLI_CHANGELOG_AUTHORIZATION;
19+
if (!token) {
20+
console.error(`Unable to find Authorization token.\n`.red +
21+
`You must either set NS_CLI_CHANGELOG_AUTHORIZATION environment variable or pass the token as an argument to the script:\n`.yellow +
22+
`node generate_changelog.js 6.2.2 2d2156c261bb1494f7a6e22f11fa446c7ca0e6b7\n`.green);
23+
process.exit(127);
24+
}
25+
26+
const sendRequest = (query) => {
27+
return new Promise((resolve, reject) => {
28+
request.post("https://api.github.com/graphql", {
29+
headers: {
30+
"Accept": "application/json",
31+
"Authorization": `Bearer ${token}`,
32+
"User-Agent": "NativeScript CLI Changelog Generator"
33+
},
34+
body: JSON.stringify(query),
35+
followAllRedirects: true
36+
}, (err, response, body) => {
37+
if (err) {
38+
reject(err);
39+
return;
40+
}
41+
resolve(JSON.parse(body));
42+
});
43+
});
44+
};
45+
46+
const getMilestonesInfoQuery = {
47+
query: `{
48+
repository(owner:"NativeScript", name:"nativescript-cli") {
49+
milestones(first: 100, states: OPEN) {
50+
nodes {
51+
number
52+
id
53+
title
54+
url
55+
}
56+
}
57+
}
58+
}`
59+
};
60+
61+
sendRequest(getMilestonesInfoQuery)
62+
.then(result => {
63+
const milestones = result && result.data && result.data.repository && result.data.repository.milestones && result.data.repository.milestones.nodes || [];
64+
const matchingMilestone = _.find(milestones, m => m.title === selectedMilestone);
65+
if (!matchingMilestone) {
66+
throw new Error(`Unable to find milestone ${selectedMilestone} in the milestones. Current milestones info is: ${JSON.stringify(milestones, null, 2)}`);
67+
}
68+
return matchingMilestone.number;
69+
})
70+
.then((milestone) => {
71+
const getItemsForMilestoneQuery = {
72+
query: `{
73+
repository(owner:"NativeScript", name:"nativescript-cli") {
74+
milestone(number: ${milestone}) {
75+
number
76+
id
77+
issuePrioritiesDebug
78+
url
79+
issues(first: 100) {
80+
nodes {
81+
title
82+
url
83+
number
84+
labels(first:100) {
85+
edges {
86+
node {
87+
name
88+
}
89+
}
90+
}
91+
projectCards(first: 100) {
92+
nodes {
93+
column {
94+
name
95+
}
96+
project {
97+
name
98+
number
99+
}
100+
state
101+
}
102+
}
103+
}
104+
}
105+
}
106+
}
107+
}`
108+
};
109+
return sendRequest(getItemsForMilestoneQuery);
110+
})
111+
.then((milestoneQueryResult) => {
112+
const issues = (milestoneQueryResult && milestoneQueryResult.data && milestoneQueryResult.data.repository &&
113+
milestoneQueryResult.data.repository.milestone && milestoneQueryResult.data.repository.milestone.issues &&
114+
milestoneQueryResult.data.repository.milestone.issues.nodes) || [];
115+
const finalIssuesForChangelog = [];
116+
issues.forEach((issue) => {
117+
const labels = ((issue.labels && issue.labels.edges) || []).map((lblObj) => lblObj && lblObj.node && lblObj.node.name);
118+
const isFeature = labels.indexOf("feature") !== -1;
119+
const isBug = labels.indexOf("bug") !== -1;
120+
const shouldBeSkipped = labels.indexOf("no-changelog") !== -1;
121+
if (isFeature && isBug) {
122+
console.error(`The item '${issue.title}' has both bug and feature label. Clear one of them and try again.`.red);
123+
process.exit(1);
124+
} else if (shouldBeSkipped) {
125+
console.log(`Item ${issue && issue.url}(${issue && issue.title}) will not be included in changelog as it has no-changelog label`.yellow);
126+
} else {
127+
// check if we have resolved it:
128+
const columns = (issue && issue.projectCards && issue.projectCards.nodes || []).map(c => c && c.column && c.column.name);
129+
// There shouldn't be more than one columns.
130+
const column = _.first(columns);
131+
if (columns && column === "Ready for Test" || column === "In Testing" || column === "Done") {
132+
finalIssuesForChangelog.push({
133+
type: isFeature ? "feature" : "bug",
134+
number: issue && issue.number,
135+
title: issue && issue.title,
136+
url: issue && issue.url
137+
});
138+
} else {
139+
console.log(`Item ${issue && issue.url}(${issue && issue.title}) will not be included in changelog as its status is ${columns}`.yellow);
140+
}
141+
}
142+
});
143+
144+
return finalIssuesForChangelog;
145+
})
146+
.then(data => {
147+
const features = [];
148+
const bugs = [];
149+
150+
_.sortBy(data, (d) => d.number)
151+
.forEach(d => {
152+
if (d.type === "feature") {
153+
features.push(`* [Implemented #${d.number}](${d.url}): ${d.title}`);
154+
} else {
155+
bugs.push(`* [Fixed #${d.number}](${d.url}): ${d.title}`);
156+
}
157+
});
158+
159+
const pathToChangelog = path.join(__dirname, "CHANGELOG.md");
160+
let changelogContent = fs.readFileSync(pathToChangelog).toString();
161+
162+
if (features.length === 0 && bugs.length === 0) {
163+
console.error(`Unable to find anything ready for milestone ${selectedMilestone}`.red);
164+
process.exit(2);
165+
}
166+
167+
const monthNames = ["January", "February", "March", "April", "May", "June",
168+
"July", "August", "September", "October", "November", "December"
169+
];
170+
const currentDate = new Date();
171+
172+
let newChangelogContent = `\n${selectedMilestone} (${currentDate.getFullYear()}, ${monthNames[currentDate.getMonth()]} ${currentDate.getDate()})
173+
===
174+
`;
175+
if (features.length > 0) {
176+
newChangelogContent += `
177+
### New
178+
179+
${features.join("\n")}
180+
`;
181+
}
182+
if (bugs.length) {
183+
newChangelogContent += `
184+
### Fixed
185+
186+
${bugs.join("\n")}
187+
`;
188+
}
189+
190+
changelogContent = changelogContent.replace(/(NativeScript CLI Changelog\r?\n=+\r?\n)([\s\S]*)/m, `$1${newChangelogContent}\n$2`);
191+
fs.writeFileSync(pathToChangelog, changelogContent);
192+
console.log(`Successfully added Changelog for ${selectedMilestone}`.green);
193+
console.log("Commit the local changes and send a PR.".magenta);
194+
})
195+
.catch(error => console.error(error));

0 commit comments

Comments
 (0)
Please sign in to comment.