nodejs
environment, you are probably using npm
and bower
for dependency management, and jshint
for code quality. But still, you've to run npm install
and bower install
manually each time the dependencies change. Same with jshint
too. Many developers don't bother about jshint
and just push code which doesn't validate. There must be a way to enforce this. Don't ya wish?We all are lazy. We all want to do less work. Automating repetitive tasks is always welcome. We'll discuss how you can automate dependency management and enforce
jshint
with git hooks
. We can have hooks which run while doing various operations with git. You can know about all the different kind of hooks by looking at the
.git/hooks
folder of your project directory. Here we'll use two different hooks, pre-commit
, which runs before committing and post-merge
, which runs after you merge upstream to local (or pull). The pre-commit
hook will ensure jshint
validation, and the post-merge
hook will take care of npm
and bower
.The git hooks are placed under
.git/hooks
folder, which cannot be committed to version control. Which is a good thing, since it prevents people from executing random scripts on your machine when you clone a third-party repository. But in our case, we need to execute scripts on other people's machine. To overcome the limitation, we can get help from symbolic links. Let's create a folder named .git-hooks
in our project root and place the hooks there. We'll use symlinks to set up the hooks. Note that any script added to the git hooks should be reviewed carefully.First, we'll install all the required npm modules.
npm install --save-dev gulp gulp-jshint gulp-gitmodified gulp-sym
Next, create a file named
pre-commit
under the .git-hooks
folder, and add the following content.#!/usr/bin/env bash
# Check if any .js file changed
git diff --cached --name-only --diff-filter=ACM | grep '.js$' >/dev/null 2>&1
if [[ $? == 0 ]]; then
gulp lint
fi
exit $?
Don't forget to make the file executable. Or our hook won't work.
chmod a+x pre-commit
Here, the hook checks if any javascript files have changed, and runs
gulp lint
. You could add any other task also, but ensure that it returns exit status 1 on failure.Let's look at our gulp task named
lint
. We list all the javascript files except minified ones and the ones under node_modules
or bower_components
, and run jshint
on them. jshint.reporter("fail")
ensures that gulp returns the exit status 1 on failure, which is needed for our hook.var gulp = require("gulp"),
jshint = require("gulp-jshint"),
gitmodified = require("gulp-gitmodified");
// Lint JavaScript files
gulp.task("lint", function() {
return gulp.src([
"**/*.js", "!**/*.min.js",
"!node_modules/**", "!bower_components/**"
])
.pipe(gitmodified("modified"))
.pipe(jshint())
.pipe(jshint.reporter("jshint-stylish"))
.pipe(jshint.reporter("fail"))
.on("error", gutil.log);
});
If jshint validation fails, then the developer won't able to commit the changes unless he fixes the issues first. So, there is less chance committing bad code.
You can also add other things like
jscs
to enforce code style. It's always good to have consistent code in your project.Next, we need to add a symlink
.git/hooks/pre-commit
which points to .git-hooks/pre-commit
. We'll automate it later.Now, we'll add the post-merge hook. Let's create a file named
post-merge
under the .git-hooks
folder and add the following content.#!/usr/bin/env bash
# List changed files
changed_files="$(git diff-tree -r --name-only --no-commit-id ORIG_HEAD HEAD)"
check_file() {
echo "$changed_files" | grep --quiet "$1" && eval "$2"
}
# `npm install` and `npm prune` if the `package.json` file gets changed
check_file "package.json" "npm install && npm prune"
# `bower install` and `bower prune` if the `bower.json` file gets changed
check_file "bower.json" "bower install && bower prune"
Also, don't forget to make the file executable.
chmod a+x post-merge
Here, the hook checks if
package.json
or bower.json
have changed, and runs npm install
or bower install
accordingly. Now, we need to add a symlink .git/hooks/post-merge
which points to .git-hooks/post-merge
.To ensure that these hooks are used, we need to set it up automatically in all developers systems. First, let's add a gulp task which creates the symlinks.
var gulp = require("gulp"),
symlink = require("gulp-sym");
// Install the GIT hooks
gulp.task("hooks", function() {
return gulp.src([ ".git-hooks/pre-commit", ".git-hooks/post-merge" ])
.pipe(symlink([ ".git/hooks/pre-commit", ".git/hooks/post-merge" ], {
relative: true,
force: true
}));
});
Now, we need to run the task on all the machines so that the hooks are set up. We can automate that too.
git
is not the only one which provides hooks, npm
does too. We'll add a npm postinstall
hook which sets up the git hooks (so many hooks, eh?). Let's add an entry named postinstall
under scripts
in our package.json
file, which will add the npm hook.{
...
"scripts": {
"postinstall": "gulp hooks"
}
...
}
Now we're all done and ready to go!
I've tried to keep the tutorial simple and generic. Try it out and tweak things as you need. Though the tutorial uses
gulp
, it's easy to modify it to use grunt
instead. Also, we're using bash to write the scripts. But you could use another scripting language if you wish. Let me know what you think in comments.
No comments :
Post a Comment