diff --git a/lib/git-hooks.js b/lib/git-hooks.js index 730b49e..eb30f5f 100644 --- a/lib/git-hooks.js +++ b/lib/git-hooks.js @@ -114,14 +114,11 @@ module.exports = { return path.resolve(hooksDirname, hookName); }) .filter(function (hookPath) { - var isFile = fs.lstatSync(hookPath).isFile(); - var isExecutable = fs.lstatSync(hookPath).isFile() && fsHelpers.isExecutable(hookPath); - - if (isFile && !isExecutable) { + if (!isExecutable(hookPath) && !isJS(hookPath) && !isBash(hookPath)) { console.warn('[GIT-HOOKS WARNING] Non-executable file ' + hookPath + ' is skipped'); + return false; } - - return isFile && isExecutable; + return true; }); runHooks(hooks, args, callback); @@ -167,7 +164,15 @@ function runHooks(hooks, args, callback) { */ function spawnHook(hookName, args) { args = args || []; - return spawn(hookName, args, {stdio: 'inherit'}); + if (isExecutable(hookName)) { + return spawn(hookName, args, {stdio: 'inherit'}); + } + if (isJS(hookName)) { + return spawn('node', [hookName].concat(args), {stdio: 'inherit'}); + } + if (isBash(hookName)) { + return spawn('bash', [hookName].concat(args), {stdio: 'inherit'}); + } } /** @@ -195,3 +200,33 @@ function getClosestGitPath(currentPath) { return getClosestGitPath(nextPath); } + +/** + * Check if the file is executable + * + * @param {String} filePath + * @returns {Boolean} + */ +function isExecutable(filePath) { + return fs.lstatSync(filePath).isFile() && fsHelpers.isExecutable(filePath); +} + +/** + * Check if the file is a JavaScript file + * + * @param {String} filePath + * @returns {Boolean} + */ +function isJS(filePath) { + return fs.lstatSync(filePath).isFile() && path.extname(filePath) === '.js'; +} + +/** + * Check if the file is a bash script file + * + * @param {String} filePath + * @returns {Boolean} + */ +function isBash(filePath) { + return fs.lstatSync(filePath).isFile() && path.extname(filePath) === '.sh'; +} diff --git a/package.json b/package.json index 88676b0..f1acccb 100644 --- a/package.json +++ b/package.json @@ -36,8 +36,8 @@ }, "engine-strict": true, "scripts": { - "postinstall": "./bin/git-hooks --install", - "preuninstall": "./bin/git-hooks --uninstall", + "postinstall": "node ./bin/git-hooks --install", + "preuninstall": "node ./bin/git-hooks --uninstall", "test": "jscs . && jshint . && mocha --reporter spec --recursive tests", "coverage": "istanbul cover _mocha --recursive tests" }, diff --git a/tests/run.test.js b/tests/run.test.js index 972f54c..0db9886 100644 --- a/tests/run.test.js +++ b/tests/run.test.js @@ -149,5 +149,38 @@ describe('git-hook runner', function () { }); }); }); + + describe('and a hook is unexecutable but is a JavaScript file', function () { + var logFile = SANDBOX_PATH + 'hello.log'; + beforeEach(function () { + var content = 'var fs = require("fs"); ' + + 'fs.writeFileSync("' + logFile.replace(/\\/g, '\\\\') + '", "Hello, world!");'; + fs.writeFileSync(PROJECT_PRECOMMIT_HOOK + 'hello.js', content); + }); + + it('should run a hook with success status', function (done) { + gitHooks.run(PRECOMMIT_HOOK_PATH, [], function (code) { + code.should.equal(0); + fs.readFileSync(logFile).toString().should.equal('Hello, world!'); + done(); + }); + }); + }); + + describe('and a hook is unexecutable but is a bash script file', function () { + var logFile = SANDBOX_PATH + 'hello.log'; + beforeEach(function () { + var content = 'echo "Hello, world!" > ' + logFile.replace(/\\/g, '\\\\'); + fs.writeFileSync(PROJECT_PRECOMMIT_HOOK + 'hello.sh', content); + }); + + it('should run a hook with success status', function (done) { + gitHooks.run(PRECOMMIT_HOOK_PATH, [], function (code) { + code.should.equal(0); + fs.readFileSync(logFile).toString().should.equal('Hello, world!\n'); + done(); + }); + }); + }); }); });