-
Notifications
You must be signed in to change notification settings - Fork 510
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: auto changelog from commits msg
- Loading branch information
Showing
4 changed files
with
426 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,119 @@ | ||
var gulp = require('gulp'); | ||
|
||
gulp.task('build', function() { | ||
var build = function(newVer) { | ||
var rename = require('gulp-rename'), | ||
uglify = require('gulp-uglify'), | ||
header = require('gulp-header'), | ||
pkg = require('./package.json'), | ||
banner = ['/**', | ||
' * <%= pkg.name %> - <%= pkg.description %>', | ||
' * @version v<%= pkg.version %>', | ||
' * @link <%= pkg.homepage %>', | ||
' * @license <%= pkg.license %>', | ||
' * @author <%= pkg.author %>', | ||
' */', | ||
''].join('\n'); | ||
' * <%= pkg.name %> - <%= pkg.description %>', | ||
' * @version v<%= pkg.version %>', | ||
' * @link <%= pkg.homepage %>', | ||
' * @license <%= pkg.license %>', | ||
' * @author <%= pkg.author %>', | ||
' */', | ||
''].join('\n'); | ||
|
||
return gulp.src('src/ocLazyLoad.js') | ||
.pipe(header(banner, { pkg : pkg } )) | ||
.pipe(header(banner, {pkg: pkg})) | ||
.pipe(gulp.dest('dist')) | ||
.pipe(gulp.dest('examples/example1/js/')) | ||
.pipe(gulp.dest('examples/example2/js/')) | ||
.pipe(uglify()) | ||
.pipe(header(banner, { pkg : pkg } )) | ||
.pipe(header(banner, {pkg: pkg})) | ||
.pipe(rename({suffix: '.min'})) | ||
.pipe(gulp.dest('dist')); | ||
}); | ||
} | ||
|
||
gulp.task('build', function() { | ||
return build(); | ||
}); | ||
|
||
var promptBump = function(callback) { | ||
var prompt = require('gulp-prompt'), | ||
semver = require('semver'), | ||
pkg = require('./package.json'); | ||
|
||
return gulp.src('') | ||
.pipe(prompt.prompt({ | ||
type: 'list', | ||
name: 'bump', | ||
message: 'What type of version bump would you like to do ? (current version is ' + pkg.version + ')', | ||
choices: [ | ||
'patch (' + pkg.version + ' --> ' + semver.inc(pkg.version, 'patch') + ')', | ||
'minor (' + pkg.version + ' --> ' + semver.inc(pkg.version, 'minor') + ')', | ||
'major (' + pkg.version + ' --> ' + semver.inc(pkg.version, 'major') + ')', | ||
'none (exit)' | ||
] | ||
}, function(res) { | ||
var newVer; | ||
if(res.bump.match(/^patch/)) { | ||
newVer = semver.inc(pkg.version, 'patch'); | ||
} else if(res.bump.match(/^minor/)) { | ||
newVer = semver.inc(pkg.version, 'minor'); | ||
} else if(res.bump.match(/^major/)) { | ||
newVer = semver.inc(pkg.version, 'major'); | ||
} | ||
if(newVer && typeof callback === 'function') { | ||
return callback(newVer); | ||
} else { | ||
return; | ||
} | ||
})); | ||
} | ||
|
||
var makeChangelog = function(newVer) { | ||
var streamqueue = require('streamqueue'), | ||
stream = streamqueue({objectMode: true}), | ||
exec = require('gulp-exec'), | ||
concat = require('gulp-concat'), | ||
clean = require('gulp-clean'); | ||
|
||
stream.queue(gulp.src('').pipe(exec('node ./src/changelog.js ' + newVer, {pipeStdout: true}))); | ||
stream.queue(gulp.src('CHANGELOG.md').pipe(clean())); | ||
|
||
return stream.done() | ||
.pipe(concat('CHANGELOG.md')) | ||
.pipe(gulp.dest('./')); | ||
} | ||
|
||
// Make changelog | ||
gulp.task('changelog', function(event) { | ||
var prompt = require('gulp-prompt'); | ||
|
||
return promptBump(makeChangelog); | ||
}) | ||
|
||
gulp.task('release', function() { | ||
var jeditor = require("gulp-json-editor"); | ||
|
||
return promptBump(function(newVer) { | ||
var streamqueue = require('streamqueue'); | ||
var stream = streamqueue({objectMode: true}); | ||
|
||
// make the changelog | ||
stream.queue(makeChangelog(newVer)); | ||
|
||
// update the main project version number | ||
stream.queue( | ||
gulp.src('package.json') | ||
.pipe(jeditor({ | ||
'version': newVer | ||
})) | ||
.pipe(gulp.dest("./")) | ||
); | ||
|
||
stream.queue( | ||
gulp.src('bower.json') | ||
.pipe(jeditor({ | ||
'version': newVer | ||
})) | ||
.pipe(gulp.dest("./")) | ||
); | ||
|
||
stream.queue(build(newVer)); | ||
|
||
return stream.done(); | ||
}); | ||
}) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
#!/usr/bin/env node | ||
|
||
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/ocombe/ocLazyLoad/issues/%s)'; | ||
var LINK_COMMIT = '[%s](https://github.com/ocombe/ocLazyLoad/commit/%s)'; | ||
|
||
var EMPTY_COMPONENT = '$$'; | ||
|
||
|
||
var warn = function() { | ||
console.error('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])); | ||
}); | ||
|
||
match = raw.match(/BREAKING CHANGE:([\s\S]*)/); | ||
if (match) { | ||
msg.breaking = match[1]; | ||
} | ||
|
||
msg.body = lines.join('\n'); | ||
match = msg.subject.match(/^(\w*)\s?\:\s?(.*)$/); | ||
|
||
if (!match || !match[1] || !match[2]) { | ||
warn('Incorrect message: %s %s', msg.hash, msg.subject); | ||
return null; | ||
} | ||
|
||
msg.type = match[1]; | ||
// msg.component = match[2]; | ||
msg.subject = match[2]; | ||
|
||
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 || section['$$'].length <= 0) 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', 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: {}, | ||
docs: {}, | ||
breaks: {} | ||
}; | ||
|
||
sections.breaks[EMPTY_COMPONENT] = []; | ||
|
||
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, 'Documentation', sections.docs); | ||
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|^docs|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; | ||
|
||
// hacky start if not run by jasmine :-D | ||
if (process.argv.join('').indexOf('jasmine-node') === -1) { | ||
generate(process.argv[2], process.argv[3]); | ||
} |
Oops, something went wrong.