Skip to content

Commit

Permalink
Merge pull request #27 from ariatemplates/script
Browse files Browse the repository at this point in the history
External script to post process commits
  • Loading branch information
piuccio authored Jun 16, 2017
2 parents bbc1b0f + f6ccc96 commit 8513428
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 40 deletions.
35 changes: 34 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ Several template variables are made available to the script running inside the t

`dateFnsFormat` is the date-fns [format](https://date-fns.org/docs/format) function. See the [html-bootstrap](https://github.com/ariatemplates/git-release-notes/blob/master/templates/html-bootstrap.ejs) for sample usage.

`range` is the commits range as passed to the command line

### Options

More advanced options are
Expand All @@ -61,6 +63,7 @@ More advanced options are
* `t` or `title` Regular expression to parse the commit title (see next chapter)
* `m` or `meaning` Meaning of capturing block in title's regular expression
* `f` or `file` JSON Configuration file, better option when you don't want to pass all parameters to the command line, for an example see [options.json](https://github.com/ariatemplates/git-release-notes/blob/master/options.json)
* `s` or `script` External script for post-processing commits

#### Title Parsing

Expand Down Expand Up @@ -92,6 +95,36 @@ Another project using similar conventions is [AngularJs](https://github.com/angu
git-release-notes -t "^(\w*)(?:\(([\w\$\.]*)\))?\: (.*)$" -m type -m scope -m title v1.1.2..v1.1.3 markdown
```

#### Post Processing

The advanced options cover the most basic use cases, however sometimes you might need some additional processing, for instance to get commit metadata from external sources (Jira, GitHub, Waffle...)

Using `-s script_file.js` you can invoke any arbitrary node script with the following signature:

```js
module.exports = function(data, callback) {
/**
* Here `data` contains exactly the same values your template will normally receive. e.g.
*
* {
* commits: [], // the array of commits as described above
* range: '<since>..<until>',
* dateFnsFormat: function () {},
* }
*
* Do all the processing you need and when ready call the callback passing the new data structure
*/
callback({
commits: data.commits.map(doSomething),
extra: { additional: 'data' },
});
//
};
```

The object passed to the callback will be merged with the input data and passed back to the template.

For an example check `samples/post-processing.js`

### Debug

Expand All @@ -103,4 +136,4 @@ If the output is not what you expect, set the DEBUG environment variable:
#### Windows

SET DEBUG=release-notes:*
git-release-notes ...
git-release-notes ...
117 changes: 82 additions & 35 deletions index.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,30 +1,34 @@
#!/usr/bin/env node
var argv = require("optimist").usage("release-notes [<options>] <since>..<until> <template>")
.options("f", {
"alias" : "file"
"alias": "file"
})
.options("p", {
"alias" : "path",
"default" : process.cwd()
"alias": "path",
"default": process.cwd()
})
.options("t", {
"alias" : "title",
"default" : "(.*)"
"alias": "title",
"default": "(.*)"
})
.options("m", {
"alias" : "meaning",
"default" : ['type']
"alias": "meaning",
"default": ['type']
})
.options("b", {
"alias" : "branch",
"default" : "master"
"alias": "branch",
"default": "master"
})
.options("s", {
"alias": "script"
})
.describe({
"f" : "Configuration file",
"p" : "Git project path",
"t" : "Commit title regular expression",
"m" : "Meaning of capturing block in title's regular expression",
"b" : "Git branch, defaults to master"
"f": "Configuration file",
"p": "Git project path",
"t": "Commit title regular expression",
"m": "Meaning of capturing block in title's regular expression",
"b": "Git branch, defaults to master",
"s": "External script to rewrite the commit history"
})
.boolean("version")
.check(function (argv) {
Expand Down Expand Up @@ -55,6 +59,15 @@ if (!fs.existsSync(template)) {
process.exit(1);
}
}

debug("Trying to locate script '%s'", argv.s);
if (argv.s && !fs.existsSync(argv.s)) {
debug("Script file '%s' doesn't exist");
require("optimist").showHelp();
console.error("\nExternal script must be a valid path " + argv.s);
process.exit(1);
}

debug("Trying to read template '%s'", template);
fs.readFile(template, function (err, templateContent) {
if (err) {
Expand All @@ -65,24 +78,13 @@ fs.readFile(template, function (err, templateContent) {
getOptions(function (options) {
debug("Running git log in '%s' on branch '%s' with range '%s'", options.p, options.b, argv._[0]);
git.log({
branch : options.b,
range : argv._[0],
title : new RegExp(options.t),
meaning : Array.isArray(options.m) ? options.m : [options.m],
cwd : options.p
branch: options.b,
range: argv._[0],
title: new RegExp(options.t),
meaning: Array.isArray(options.m) ? options.m: [options.m],
cwd: options.p
}, function (commits) {
debug("Got %d commits", commits.length);
if (commits.length) {
debug("Rendering template");
var output = ejs.render(templateContent.toString(), {
commits : commits,
dateFnsFormat: dateFnsFormat
});
process.stdout.write(output + "\n");
} else {
console.error('No commits in the specified range');
process.exit(6);
}
postProcess(templateContent, commits);
});
});
}
Expand All @@ -99,10 +101,10 @@ function getOptions (callback) {
try {
var stored = JSON.parse(data);
options = {
b : stored.b || stored.branch || argv.b,
t : stored.t || stored.title || argv.t,
m : stored.m || stored.meaning || argv.m,
p : stored.p || stored.path || argv.p
b: stored.b || stored.branch || argv.b,
t: stored.t || stored.title || argv.t,
m: stored.m || stored.meaning || argv.m,
p: stored.p || stored.path || argv.p
};
} catch (ex) {
console.error("Invalid JSON in configuration file");
Expand All @@ -116,3 +118,48 @@ function getOptions (callback) {
callback(argv);
}
}

function postProcess(templateContent, commits) {
debug("Got %d commits", commits.length);
if (commits.length) {
if (argv.s) {
try {
var externalScript = require(path.join(process.cwd(), argv.s));
} catch (ex) {
debug("Exception while reading external script '%s'", ex.message);
console.error('Unable to read external script');
process.exit(7);
}
debug("Trying to run the external script");
try {
externalScript({
commits: commits,
range: argv._[0],
dateFnsFormat: dateFnsFormat,
}, function (data) {
render(templateContent, data);
});
debug("Waiting for external script to call the callback");
} catch (ex) {
debug("Exception while running external script '%s'", ex.message);
console.error('Error while processing external script', ex);
process.exit(8);
}
} else {
debug("Rendering template without post processing");
render(templateContent, { commits: commits });
}
} else {
console.error('No commits in the specified range');
process.exit(6);
}
}

function render(templateContent, data) {
debug("Rendering template");
var output = ejs.render(templateContent.toString(), Object.assign({
range: argv._[0],
dateFnsFormat: dateFnsFormat
}, data));
process.stdout.write(output + "\n");
}
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@
"test-html-bootstrap": "cross-env DEBUG=* node index.js -- 32a369f..0419636 ./templates/html-bootstrap.ejs > ./samples/output-html-bootstrap.html",
"test-markdown": "node index.js -- 32a369f..0419636 ./templates/markdown.ejs > ./samples/output-markdown.md"
},
"version": "1.0.0",
"version": "1.1.0",
"dependencies": {
"cross-env": "4.0.0",
"date-fns": "^1.28.4",
"debug": "^2.6.0",
"ejs": "^2.5.5",
Expand All @@ -39,7 +38,7 @@
"homepage": "https://github.com/ariatemplates/git-release-notes",
"preferGlobal": true,
"devDependencies": {
"cross-env": "^4.0.0",
"eslint": "^3.14.0"
"cross-env": "^5.0.0",
"eslint": "^4.0.0"
}
}
30 changes: 30 additions & 0 deletions samples/post-processing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Waffle.io uses the syntax `[Connected to #123]` to link PRs to the original task
*/
const WAFFLE_INFO = /\[connected to #\d+\]\s?/gi;
const ISSUE_ID = /#(\d+)/;

module.exports = function (data, callback) {
const rewritten = data.commits.map((commit) => {
const matches = commit.title.match(WAFFLE_INFO);
if (matches) {
// extra metadata to remember the linked tasks
commit.tasks = matches.map((m) => m.match(ISSUE_ID)[1]);
// remove it from the title
commit.title = commit.title.replace(WAFFLE_INFO, '');
}

if (commit.title.indexOf('[skip ci]') !== -1) {
// Filter out commits we don't care about
return null;
}

return commit;
});

callback({
commits: rewritten.filter(Boolean),
// rewrite the range because the template only cares about the starting point
range: data.range.split('.')[0],
});
}

0 comments on commit 8513428

Please sign in to comment.