Skip to content

Commit

Permalink
feat(bootstrap): creating initial structure
Browse files Browse the repository at this point in the history
  • Loading branch information
Rafael Pedrola committed Nov 15, 2013
1 parent 89ee30f commit dea45d6
Show file tree
Hide file tree
Showing 76 changed files with 7,033 additions and 0 deletions.
1 change: 1 addition & 0 deletions changelog.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node changelog.js 1.0.0 changelog.md "My App"
225 changes: 225 additions & 0 deletions changelog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
#!/usr/bin/env node

// TODO(vojta): pre-commit hook for validating messages
// TODO(vojta): report errors, currently Q silence everything which really sucks

var child = require('child_process');
var fs = require('fs');
var util = require('util');
var q = require('qq');
var _s = require('underscore-string');

var GIT_LOG_CMD = 'git log development --grep="%s" -E --format=%s %s..HEAD';
//var GIT_LOG_CMD = 'git log --grep="%s" -E --format=%s %s..HEAD';
var GIT_TAG_CMD = 'git describe --tags --abbrev=0';

var PROVIDER = 'BitBucket';
var HEADER_TPL = '<a name="%s">%s</a>\n# %s (%s)\n\n';
var LINK_ISSUE = '[#%s](https://bitbucket.org/adesisnetlife/repsol-proyecto-f-nix-intranet/issues/%s)';
var LINK_COMMIT = '[%s](https://bitbucket.org/adesisnetlife/repsol-proyecto-f-nix-intranet/commits/%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]));
});

match = raw.match(/BREAKING CHANGE:([\s\S]*)/);
if (match) {
msg.breaking = match[1];
}


msg.body = lines.join('\n');
match = msg.subject.match(/^(.*)\((.*)\)\:\s(.*)$/);
console.log(match);
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', 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, appName) {
var sections = {
fix: {},
feat: {},
breaks: {},
problems: {},
docs: {}
};

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, appName, version, currentDate()));
printSection(stream, 'Documentation', sections.docs);
printSection(stream, 'Bug Fixes', sections.fix);
printSection(stream, 'Features', sections.feat);
printSection(stream, 'Breaking Changes', sections.breaks, false);
printSection(stream, 'Client Problems', sections.problems, false);
}


var getPreviousTag = function() {

var deferred = q.defer();
console.log(GIT_TAG_CMD);
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, appName) {

getPreviousTag().then(function(tag) {

console.log('Reading git log since', tag);
readGitLog('^fix|^feat|^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, appName);
});
});
};


// publish for testing
exports.parseRawCommit = parseRawCommit;

// hacky start if not run by jasmine :-D
if (process.argv.join('').indexOf('jasmine-node') === -1) {
//node changelog.js 1.0.0 changelog.md "My App"
if (process.argv[5] != undefined) {
if (process.argv[5] == 'GitHub' || process.argv[5] == 'BitBucket') {
PROVIDER = process.argv[5];
}
}
generate(process.argv[2], process.argv[3], process.argv[4]);
}
34 changes: 34 additions & 0 deletions changelog.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env bash

function catch_errors() {
echo "ERROR. That's life."
exit 1
}

trap catch_errors ERR

TMP_FILE='changelog.tmp'
CHANGELOG_FILE='CHANGELOG.md'

echo "Getting current version..."
VERSION=`./version.js --current`

echo "Generating changelog..."
./changelog.js $VERSION $TMP_FILE

cat $CHANGELOG_FILE >> $TMP_FILE
mv -f $TMP_FILE $CHANGELOG_FILE


echo "Updating version..."
./version.js --remove-snapshot

echo "CONFIRM TO COMMIT"
read WHATEVER


echo "Creating commit..."
git commit version.yaml CHANGELOG.md -m "chore(relase): cutting the v$VERSION release"

echo "Creating tag..."
git tag "v$VERSION"
43 changes: 43 additions & 0 deletions changelog.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
describe('changelog.js', function() {
var ch = require('./changelog');

describe('parseRawCommit', function() {
it('should parse raw commit', function() {
var msg = ch.parseRawCommit(
'9b1aff905b638aa274a5fc8f88662df446d374bd\n' +
'feat(scope): broadcast $destroy event on scope destruction\n' +
'perf testing shows that in chrome this change adds 5-15% overhead\n' +
'when destroying 10k nested scopes where each scope has a $destroy listener\n');

expect(msg.type).toBe('feat');
expect(msg.hash).toBe('9b1aff905b638aa274a5fc8f88662df446d374bd');
expect(msg.subject).toBe('broadcast $destroy event on scope destruction');
expect(msg.body).toBe('perf testing shows that in chrome this change adds 5-15% overhead\n' +
'when destroying 10k nested scopes where each scope has a $destroy listener\n')
expect(msg.component).toBe('scope');
});


it('should parse closed issues', function() {
var msg = ch.parseRawCommit(
'13f31602f396bc269076ab4d389cfd8ca94b20ba\n' +
'feat(ng-list): Allow custom separator\n' +
'bla bla bla\n\n' +
'Closes #123\nCloses #25\n');

expect(msg.closes).toEqual([123, 25]);
});


it('should parse breaking changes', function() {
var msg = ch.parseRawCommit(
'13f31602f396bc269076ab4d389cfd8ca94b20ba\n' +
'feat(ng-list): Allow custom separator\n' +
'bla bla bla\n\n' +
'BREAKING CHANGE: first breaking change\nsomething else\n' +
'another line with more info\n');

expect(msg.breaking).toEqual(' first breaking change\nsomething else\nanother line with more info\n');
});
});
});
1 change: 1 addition & 0 deletions node_modules/qq/.npmignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit dea45d6

Please sign in to comment.