UNPKG

5.15 kBJavaScriptView Raw
1var fs = require('fs'),
2 path = require('path'),
3 handlebars = require('handlebars');
4
5/**
6 * Class to help manage a hook
7 * @class Hook
8 * @module githooks
9 * @constructor
10 */
11function Hook(hookName, taskNames, options) {
12
13 /**
14 * The name of the hook
15 * @property hookName
16 * @type {String}
17 */
18 this.hookName = hookName;
19
20 /**
21 * The name of the tasks that should be run by the hook, space separated
22 * @property taskNames
23 * @type {String}
24 */
25 this.taskNames = taskNames;
26
27 /**
28 * Options for the creation of the hook
29 * @property options
30 * @type {Object}
31 */
32 this.options = options;
33
34 this.markerRegExp = new RegExp(this.options.startMarker.replace(/\//g, '\\/') +
35 '[\\s\\S]*' + // Not .* because it needs to match \n
36 this.options.endMarker.replace(/\//g, '\\/'), 'm');
37}
38
39Hook.prototype = {
40
41 /**
42 * Creates the hook
43 * @method create
44 */
45 create: function() {
46
47 var hookPath = path.resolve(this.options.dest, this.hookName);
48
49 var existingCode = this.getHookContent(hookPath);
50
51 if (existingCode) {
52 this.validateScriptLanguage(existingCode);
53 }
54
55 var hookContent;
56 if(this.hasMarkers(existingCode)) {
57 hookContent = this.insertBindingCode(existingCode);
58 } else {
59 hookContent = this.appendBindingCode(existingCode);
60 }
61
62 fs.writeFileSync(hookPath, hookContent);
63
64 fs.chmodSync(hookPath, '755');
65 },
66
67 /**
68 * Returns the content of the hook at given path
69 * @method getHookContent
70 * @param path {String} The path to the hook
71 * @return {String}
72 */
73 getHookContent: function (path) {
74
75 if (fs.existsSync(path)) {
76 return fs.readFileSync(path, {encoding: 'utf-8'});
77 }
78 },
79
80 /**
81 * Validates that the language of given script matches the one
82 * the binding code will be generated in
83 * @method validateScriptLanguage
84 * @param script {String}
85 * @throws {Error}
86 */
87 validateScriptLanguage: function (script) {
88
89 if (!this.isValidScriptLanguage(script, this.options.hashbang)) {
90 throw new Error('ERR_INVALID_SCRIPT_LANGUAGE');
91 }
92 },
93
94 /**
95 * Checks that content of given hook is written in the appropriate scripting
96 * language, checking if it starts with the appropriate hashbang
97 * @method isValidScriptLanguage
98 * @param hookContent {String}
99 * @param hashbang {String}
100 * @return {Boolean}
101 */
102 isValidScriptLanguage: function(hookContent, hashbang) {
103
104 var firstLine = hookContent.split('\n')[0];
105 return firstLine.indexOf(hashbang) !== -1;
106 },
107
108 /**
109 * Checks if given code has marker for inserting the binding code
110 * @method hasMarkers
111 * @param existingCode {String}
112 * @return {Boolean}
113 */
114 hasMarkers: function(existingCode) {
115
116 return this.markerRegExp.test(existingCode);
117 },
118
119 /**
120 * Creates the code that will run the grunt task from the hook
121 * @method createBindingCode
122 * @param task {String}
123 * @param templatePath {Object}
124 */
125 createBindingCode: function () {
126
127 var template = this.loadTemplate(this.options.template);
128 var bindingCode = template({
129 task: this.taskNames,
130 preventExit: this.options.preventExit,
131 args: this.options.args,
132 gruntfileDirectory: process.cwd()
133 });
134
135 return this.options.startMarker + '\n' + bindingCode + '\n' + this.options.endMarker;
136 },
137
138 /**
139 * Loads template at given path
140 * @method loadTemplate
141 * @param templatePath {String}
142 * @return {Function}
143 */
144 loadTemplate: function () {
145
146 var template = fs.readFileSync(this.options.template, {
147 encoding: 'utf-8'
148 });
149
150 return handlebars.compile(template);
151 },
152
153 /**
154 * Appends code binding the given task to given existing code
155 * @method appendBindingCode
156 * @param existingCode {String}
157 * @return {String}
158 */
159 appendBindingCode: function (existingCode) {
160
161 var bindingCode = this.createBindingCode();
162 return (existingCode || this.options.hashbang) +
163 '\n\n' +
164 bindingCode;
165 },
166
167 /**
168 * Inserts binding code at the position shown by markers in the given code
169 * @method insertBindingCode
170 * @param existingCode {String}
171 * @return {String}
172 */
173 insertBindingCode: function (existingCode) {
174
175 var bindingCode = this.createBindingCode();
176 return existingCode.replace(this.markerRegExp, bindingCode);
177 },
178};
179
180/**
181 * The name of the hooks offered by Git
182 * @property HOOK_NAMES
183 * @type {Array}
184 * @static
185 */
186Hook.HOOK_NAMES = [
187 // CLIENT HOOKS
188 'applypatch-msg',
189 'pre-applypatch',
190 'post-applypatch',
191 'pre-commit',
192 'prepare-commit-msg',
193 'commit-msg',
194 'post-commit',
195 'pre-rebase',
196 'post-checkout',
197 'post-merge',
198 'post-rewrite',
199
200 // SERVER HOOKS
201 'pre-receive',
202 'update',
203 'post-receive',
204 'post-update',
205 'pre-auto-gc'
206];
207/**
208 * Checks that given name is the name of a Git hook (see HOOK_NAMES for the list of Git hook names)
209 * @method isNameOfAGitHook
210 * @param name {String}
211 * @return {Boolean}
212 * @static
213 */
214Hook.isNameOfAGitHook = function(name) {
215 return Hook.HOOK_NAMES.indexOf(name) !== -1;
216};
217
218module.exports.Hook = Hook;