Skip to content

Commit 3bb3bff

Browse files
yjbanovmhevery
authored andcommitted
chore(packaging): copy changelog.js from angularjs
1 parent d2d4e7d commit 3bb3bff

2 files changed

Lines changed: 210 additions & 0 deletions

File tree

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"parse5": "1.3.2",
9696
"protractor": "2.0.0",
9797
"q": "^1.0.1",
98+
"qq": "^0.3.5",
9899
"run-sequence": "^0.3.6",
99100
"sorted-object": "^1.0.0",
100101
"source-map": "^0.3.0",

scripts/publish/changelog.js

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
#!/usr/bin/env node
2+
3+
// TODO(vojta): pre-commit hook for validating messages
4+
// TODO(vojta): report errors, currently Q silence everything which really sucks
5+
6+
'use strict';
7+
8+
var child = require('child_process');
9+
var fs = require('fs');
10+
var util = require('util');
11+
var q = require('qq');
12+
13+
var GIT_LOG_CMD = 'git log --grep="%s" -E --format=%s %s..HEAD';
14+
var GIT_TAG_CMD = 'git describe --tags --abbrev=0';
15+
16+
var HEADER_TPL = '<a name="%s"></a>\n# %s (%s)\n\n';
17+
var LINK_ISSUE = '[#%s](https://github.com/angular/angular.js/issues/%s)';
18+
var LINK_COMMIT = '[%s](https://github.com/angular/angular.js/commit/%s)';
19+
20+
var EMPTY_COMPONENT = '$$';
21+
22+
23+
var warn = function() {
24+
console.log('WARNING:', util.format.apply(null, arguments));
25+
};
26+
27+
28+
var parseRawCommit = function(raw) {
29+
if (!raw) return null;
30+
31+
var lines = raw.split('\n');
32+
var msg = {}, match;
33+
34+
msg.hash = lines.shift();
35+
msg.subject = lines.shift();
36+
msg.closes = [];
37+
msg.breaks = [];
38+
39+
lines.forEach(function(line) {
40+
match = line.match(/(?:Closes|Fixes)\s#(\d+)/);
41+
if (match) msg.closes.push(parseInt(match[1]));
42+
});
43+
44+
match = raw.match(/BREAKING CHANGE:([\s\S]*)/);
45+
if (match) {
46+
msg.breaking = match[1];
47+
}
48+
49+
50+
msg.body = lines.join('\n');
51+
match = msg.subject.match(/^(.*)\((.*)\)\:\s(.*)$/);
52+
53+
if (!match || !match[1] || !match[3]) {
54+
warn('Incorrect message: %s %s', msg.hash, msg.subject);
55+
return null;
56+
}
57+
58+
msg.type = match[1];
59+
msg.component = match[2];
60+
msg.subject = match[3];
61+
62+
return msg;
63+
};
64+
65+
66+
var linkToIssue = function(issue) {
67+
return util.format(LINK_ISSUE, issue, issue);
68+
};
69+
70+
71+
var linkToCommit = function(hash) {
72+
return util.format(LINK_COMMIT, hash.substr(0, 8), hash);
73+
};
74+
75+
76+
var currentDate = function() {
77+
var now = new Date();
78+
var pad = function(i) {
79+
return ('0' + i).substr(-2);
80+
};
81+
82+
return util.format('%d-%s-%s', now.getFullYear(), pad(now.getMonth() + 1), pad(now.getDate()));
83+
};
84+
85+
86+
var printSection = function(stream, title, section, printCommitLinks) {
87+
printCommitLinks = printCommitLinks === undefined ? true : printCommitLinks;
88+
var components = Object.getOwnPropertyNames(section).sort();
89+
90+
if (!components.length) return;
91+
92+
stream.write(util.format('\n## %s\n\n', title));
93+
94+
components.forEach(function(name) {
95+
var prefix = '-';
96+
var nested = section[name].length > 1;
97+
98+
if (name !== EMPTY_COMPONENT) {
99+
if (nested) {
100+
stream.write(util.format('- **%s:**\n', name));
101+
prefix = ' -';
102+
} else {
103+
prefix = util.format('- **%s:**', name);
104+
}
105+
}
106+
107+
section[name].forEach(function(commit) {
108+
if (printCommitLinks) {
109+
stream.write(util.format('%s %s\n (%s', prefix, commit.subject, linkToCommit(commit.hash)));
110+
if (commit.closes.length) {
111+
stream.write(',\n ' + commit.closes.map(linkToIssue).join(', '));
112+
}
113+
stream.write(')\n');
114+
} else {
115+
stream.write(util.format('%s %s\n', prefix, commit.subject));
116+
}
117+
});
118+
});
119+
120+
stream.write('\n');
121+
};
122+
123+
124+
var readGitLog = function(grep, from) {
125+
var deferred = q.defer();
126+
127+
// TODO(vojta): if it's slow, use spawn and stream it instead
128+
child.exec(util.format(GIT_LOG_CMD, grep, '%H%n%s%n%b%n==END==', from), function(code, stdout, stderr) {
129+
var commits = [];
130+
131+
stdout.split('\n==END==\n').forEach(function(rawCommit) {
132+
var commit = parseRawCommit(rawCommit);
133+
if (commit) commits.push(commit);
134+
});
135+
136+
deferred.resolve(commits);
137+
});
138+
139+
return deferred.promise;
140+
};
141+
142+
143+
var writeChangelog = function(stream, commits, version) {
144+
var sections = {
145+
fix: {},
146+
feat: {},
147+
perf: {},
148+
breaks: {}
149+
};
150+
151+
sections.breaks[EMPTY_COMPONENT] = [];
152+
153+
commits.forEach(function(commit) {
154+
var section = sections[commit.type];
155+
var component = commit.component || EMPTY_COMPONENT;
156+
157+
if (section) {
158+
section[component] = section[component] || [];
159+
section[component].push(commit);
160+
}
161+
162+
if (commit.breaking) {
163+
sections.breaks[component] = sections.breaks[component] || [];
164+
sections.breaks[component].push({
165+
subject: util.format("due to %s,\n %s", linkToCommit(commit.hash), commit.breaking),
166+
hash: commit.hash,
167+
closes: []
168+
});
169+
}
170+
});
171+
172+
stream.write(util.format(HEADER_TPL, version, version, currentDate()));
173+
printSection(stream, 'Features', sections.feat);
174+
printSection(stream, 'Performance Improvements', sections.perf);
175+
printSection(stream, 'Breaking Changes', sections.breaks, false);
176+
};
177+
178+
179+
var getPreviousTag = function() {
180+
var deferred = q.defer();
181+
child.exec(GIT_TAG_CMD, function(code, stdout, stderr) {
182+
if (code) deferred.reject('Cannot get the previous tag.');
183+
else deferred.resolve(stdout.replace('\n', ''));
184+
});
185+
return deferred.promise;
186+
};
187+
188+
189+
var generate = function(version, file) {
190+
191+
getPreviousTag().then(function(tag) {
192+
console.log('Reading git log since', tag);
193+
readGitLog('^fix|^feat|^perf|BREAKING', tag).then(function(commits) {
194+
console.log('Parsed', commits.length, 'commits');
195+
console.log('Generating changelog to', file || 'stdout', '(', version, ')');
196+
writeChangelog(file ? fs.createWriteStream(file) : process.stdout, commits, version);
197+
});
198+
});
199+
};
200+
201+
202+
// publish for testing
203+
exports.parseRawCommit = parseRawCommit;
204+
exports.printSection = printSection;
205+
206+
// hacky start if not run by jasmine :-D
207+
if (process.argv.join('').indexOf('jasmine-node') === -1) {
208+
generate(process.argv[2], process.argv[3]);
209+
}

0 commit comments

Comments
 (0)