--- /dev/null
+"use strict";
+
+var path = require("path"),
+ fs = require("fs");
+
+var gitSemverTags = require("git-semver-tags"),
+ gitRawCommits = require("git-raw-commits"),
+ minimist = require("minimist");
+
+var basedir = path.join(__dirname, "..");
+var pkg = require(basedir + "/package.json");
+
+var argv = minimist(process.argv, {
+ alias: {
+ tag : "t",
+ write : "w"
+ },
+ string: [ "tag" ],
+ boolean: [ "write" ],
+ default: {
+ tag: null,
+ write: false
+ }
+});
+
+// categories to be used in the future and regexes for lazy / older subjects
+var validCategories = {
+ "Breaking": null,
+ "Fixed": /fix|properly|prevent|correctly/i,
+ "New": /added|initial/i,
+ "CLI": /pbjs|pbts|CLI/,
+ "Docs": /README/i,
+ "Other": null
+};
+var breakingFallback = /removed|stripped|dropped/i;
+
+var repo = "https://github.com/protobufjs/protobuf.js";
+
+gitSemverTags(function(err, tags) {
+ if (err)
+ throw err;
+
+ var categories = {};
+ Object.keys(validCategories).forEach(function(category) {
+ categories[category] = [];
+ });
+ var output = [];
+
+ var from = tags[0];
+ var to = "HEAD";
+ var tag;
+ if (argv.tag) {
+ var idx = tags.indexOf(argv.tag);
+ if (idx < 0)
+ throw Error("no such tag: " + argv.tag);
+ from = tags[idx + 1];
+ tag = to = tags[idx];
+ } else
+ tag = pkg.version;
+
+ var commits = gitRawCommits({
+ from: from,
+ to: to,
+ merges: false,
+ format: "%B%n#%H"
+ });
+
+ commits.on("error", function(err) {
+ throw err;
+ });
+
+ commits.on("data", function(chunk) {
+ var message = chunk.toString("utf8").trim();
+ var match = /##([0-9a-f]{40})$/.exec(message);
+ var hash;
+ if (match) {
+ message = message.substring(0, message.length - match[1].length).trim();
+ hash = match[1];
+ }
+ message.split(";").forEach(function(message) {
+ if (/^(Merge pull request |Post-merge)/.test(message))
+ return;
+ var match = /^(\w+):/i.exec(message = message.trim());
+ var category;
+ if (match && match[1] in validCategories) {
+ category = match[1];
+ message = message.substring(match[1].length + 1).trim();
+ } else {
+ var keys = Object.keys(validCategories);
+ for (var i = 0; i < keys.length; ++i) {
+ var re = validCategories[keys[i]];
+ if (re && re.test(message)) {
+ category = keys[i];
+ break;
+ }
+ }
+ message = message.replace(/^(\w+):/i, "").trim();
+ }
+ if (!category) {
+ if (breakingFallback.test(message))
+ category = "Breaking";
+ else
+ category = "Other";
+ }
+ var nl = message.indexOf("\n");
+ if (nl > -1)
+ message = message.substring(0, nl).trim();
+ if (!hash || message.length < 12)
+ return;
+ message = message.replace(/\[ci skip\]/, "").trim();
+ categories[category].push({
+ text: message,
+ hash: hash
+ });
+ });
+ });
+
+ commits.on("end", function() {
+ output.push("## [" + tag + "](" + repo + "/releases/tag/" + tag + ")\n");
+ Object.keys(categories).forEach(function(category) {
+ var messages = categories[category];
+ if (!messages.length)
+ return;
+ output.push("\n### " + category + "\n");
+ messages.forEach(function(message) {
+ var text = message.text.replace(/#(\d+)/g, "[#$1](" + repo + "/issues/$1)");
+ output.push("[:hash:](" + repo + "/commit/" + message.hash + ") " + text + "<br />\n");
+ });
+ });
+ var current;
+ try {
+ current = fs.readFileSync(basedir + "/CHANGELOG.md").toString("utf8");
+ } catch (e) {
+ current = "";
+ }
+ var re = new RegExp("^## \\[" + tag + "\\]");
+ if (re.test(current)) { // regenerated, replace
+ var pos = current.indexOf("## [", 1);
+ if (pos > -1)
+ current = current.substring(pos).trim();
+ else
+ current = "";
+ }
+ var contents = output.join("") + "\n" + current;
+ if (argv.write)
+ fs.writeFileSync(basedir + "/CHANGELOG.md", contents, "utf8");
+ else
+ process.stdout.write(contents);
+ });
+});