9360aa2d27
The quotes rule had to be disabled for e2e tests generated from ngdoc because dgeni templates use double quotes as string delimiters. Since we can't have guarantees that dgeni template wrappers will follow the same JS code style the Angular 1 repo uses, we should find a way to enforce our ESLint setup only for the parts in this repo, perhaps via prepending a generated `/* eslint-enable OUR_RULES */` pragma. Closes #15011
209 lines
5.4 KiB
JavaScript
Executable File
209 lines
5.4 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
// TODO(vojta): pre-commit hook for validating messages
|
|
// TODO(vojta): report errors, currently Q silence everything which really sucks
|
|
|
|
'use strict';
|
|
|
|
var child = require('child_process');
|
|
var fs = require('fs');
|
|
var util = require('util');
|
|
var q = require('qq');
|
|
|
|
var GIT_LOG_CMD = 'git log --grep="%s" -E --format=%s %s..HEAD';
|
|
var GIT_TAG_CMD = 'git describe --tags --abbrev=0';
|
|
|
|
var HEADER_TPL = '<a name="%s"></a>\n# %s (%s)\n\n';
|
|
var LINK_ISSUE = '[#%s](https://github.com/angular/angular.js/issues/%s)';
|
|
var LINK_COMMIT = '[%s](https://github.com/angular/angular.js/commit/%s)';
|
|
|
|
var EMPTY_COMPONENT = '$$';
|
|
|
|
|
|
var warn = function() {
|
|
console.log('WARNING:', util.format.apply(null, arguments));
|
|
};
|
|
|
|
|
|
var parseRawCommit = function(raw) {
|
|
if (!raw) return null;
|
|
|
|
var lines = raw.split('\n');
|
|
var msg = {}, match;
|
|
|
|
msg.hash = lines.shift();
|
|
msg.subject = lines.shift();
|
|
msg.closes = [];
|
|
msg.breaks = [];
|
|
|
|
lines.forEach(function(line) {
|
|
match = line.match(/(?:Closes|Fixes)\s#(\d+)/);
|
|
if (match) msg.closes.push(parseInt(match[1], 10));
|
|
});
|
|
|
|
match = raw.match(/BREAKING CHANGE:([\s\S]*)/);
|
|
if (match) {
|
|
msg.breaking = match[1];
|
|
}
|
|
|
|
|
|
msg.body = lines.join('\n');
|
|
match = msg.subject.match(/^(.*)\((.*)\):\s(.*)$/);
|
|
|
|
if (!match || !match[1] || !match[3]) {
|
|
warn('Incorrect message: %s %s', msg.hash, msg.subject);
|
|
return null;
|
|
}
|
|
|
|
msg.type = match[1];
|
|
msg.component = match[2];
|
|
msg.subject = match[3];
|
|
|
|
return msg;
|
|
};
|
|
|
|
|
|
var linkToIssue = function(issue) {
|
|
return util.format(LINK_ISSUE, issue, issue);
|
|
};
|
|
|
|
|
|
var linkToCommit = function(hash) {
|
|
return util.format(LINK_COMMIT, hash.substr(0, 8), hash);
|
|
};
|
|
|
|
|
|
var currentDate = function() {
|
|
var now = new Date();
|
|
var pad = function(i) {
|
|
return ('0' + i).substr(-2);
|
|
};
|
|
|
|
return util.format('%d-%s-%s', now.getFullYear(), pad(now.getMonth() + 1), pad(now.getDate()));
|
|
};
|
|
|
|
|
|
var printSection = function(stream, title, section, printCommitLinks) {
|
|
printCommitLinks = printCommitLinks === undefined ? true : printCommitLinks;
|
|
var components = Object.getOwnPropertyNames(section).sort();
|
|
|
|
if (!components.length) return;
|
|
|
|
stream.write(util.format('\n## %s\n\n', title));
|
|
|
|
components.forEach(function(name) {
|
|
var prefix = '-';
|
|
var nested = section[name].length > 1;
|
|
|
|
if (name !== EMPTY_COMPONENT) {
|
|
if (nested) {
|
|
stream.write(util.format('- **%s:**\n', name));
|
|
prefix = ' -';
|
|
} else {
|
|
prefix = util.format('- **%s:**', name);
|
|
}
|
|
}
|
|
|
|
section[name].forEach(function(commit) {
|
|
if (printCommitLinks) {
|
|
stream.write(util.format('%s %s\n (%s', prefix, commit.subject, linkToCommit(commit.hash)));
|
|
if (commit.closes.length) {
|
|
stream.write(',\n ' + commit.closes.map(linkToIssue).join(', '));
|
|
}
|
|
stream.write(')\n');
|
|
} else {
|
|
stream.write(util.format('%s %s\n', prefix, commit.subject));
|
|
}
|
|
});
|
|
});
|
|
|
|
stream.write('\n');
|
|
};
|
|
|
|
|
|
var readGitLog = function(grep, from) {
|
|
var deferred = q.defer();
|
|
|
|
// TODO(vojta): if it's slow, use spawn and stream it instead
|
|
child.exec(util.format(GIT_LOG_CMD, grep, '%H%n%s%n%b%n==END==', from), function(code, stdout, stderr) {
|
|
var commits = [];
|
|
|
|
stdout.split('\n==END==\n').forEach(function(rawCommit) {
|
|
var commit = parseRawCommit(rawCommit);
|
|
if (commit) commits.push(commit);
|
|
});
|
|
|
|
deferred.resolve(commits);
|
|
});
|
|
|
|
return deferred.promise;
|
|
};
|
|
|
|
|
|
var writeChangelog = function(stream, commits, version) {
|
|
var sections = {
|
|
fix: {},
|
|
feat: {},
|
|
perf: {},
|
|
breaks: {}
|
|
};
|
|
|
|
commits.forEach(function(commit) {
|
|
var section = sections[commit.type];
|
|
var component = commit.component || EMPTY_COMPONENT;
|
|
|
|
if (section) {
|
|
section[component] = section[component] || [];
|
|
section[component].push(commit);
|
|
}
|
|
|
|
if (commit.breaking) {
|
|
sections.breaks[component] = sections.breaks[component] || [];
|
|
sections.breaks[component].push({
|
|
subject: util.format('due to %s,\n %s', linkToCommit(commit.hash), commit.breaking),
|
|
hash: commit.hash,
|
|
closes: []
|
|
});
|
|
}
|
|
});
|
|
|
|
stream.write(util.format(HEADER_TPL, version, version, currentDate()));
|
|
printSection(stream, 'Bug Fixes', sections.fix);
|
|
printSection(stream, 'Features', sections.feat);
|
|
printSection(stream, 'Performance Improvements', sections.perf);
|
|
printSection(stream, 'Breaking Changes', sections.breaks, false);
|
|
};
|
|
|
|
|
|
var getPreviousTag = function() {
|
|
var deferred = q.defer();
|
|
child.exec(GIT_TAG_CMD, function(code, stdout, stderr) {
|
|
if (code) deferred.reject('Cannot get the previous tag.');
|
|
else deferred.resolve(stdout.replace('\n', ''));
|
|
});
|
|
return deferred.promise;
|
|
};
|
|
|
|
|
|
var generate = function(version, file) {
|
|
|
|
getPreviousTag().then(function(tag) {
|
|
console.log('Reading git log since', tag);
|
|
readGitLog('^fix|^feat|^perf|BREAKING', tag).then(function(commits) {
|
|
console.log('Parsed', commits.length, 'commits');
|
|
console.log('Generating changelog to', file || 'stdout', '(', version, ')');
|
|
writeChangelog(file ? fs.createWriteStream(file) : process.stdout, commits, version);
|
|
});
|
|
});
|
|
};
|
|
|
|
|
|
// publish for testing
|
|
exports.parseRawCommit = parseRawCommit;
|
|
exports.printSection = printSection;
|
|
|
|
// hacky start if not run by jasmine :-D
|
|
if (process.argv.join('').indexOf('jasmine-node') === -1) {
|
|
generate(process.argv[2], process.argv[3]);
|
|
}
|