-
Notifications
You must be signed in to change notification settings - Fork 2k
/
loadHooks.js
355 lines (302 loc) · 16.3 KB
/
loadHooks.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
/**
* Module dependencies
*/
var util = require('util');
var _ = require('@sailshq/lodash');
var async = require('async');
var defaultsDeep = require('merge-defaults');// « TODO: Get rid of this
var __hooks = require('../../hooks');
/**
* @param {SailsApp} sails
* @returns {Function}
*/
module.exports = function(sails) {
var Hook = __hooks(sails);
// Keep an array of all the hook timeouts.
// This way if a hook fails to load, we can clear all the timeouts at once.
var hookTimeouts = [];
// NOTE: There's no particular reason this (^^) is outside of the function being returned below.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// FUTURE: pull it in below to avoid leading to any incorrect assumptions about race conditions, etc.)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/**
* Resolve the hook definitions and then finish loading them
*
* @api private
*/
return function initializeHooks(hooks, cb) {
// ==============================================================================
// < inline function declarations >
// ██╗ ██╗███╗ ██╗██╗ ██╗███╗ ██╗███████╗ ███████╗███╗ ██╗ ██████╗ ███████╗███████╗███████╗ ██╗
// ██╔╝ ██║████╗ ██║██║ ██║████╗ ██║██╔════╝ ██╔════╝████╗ ██║ ██╔══██╗██╔════╝██╔════╝██╔════╝ ╚██╗
// ██╔╝ ██║██╔██╗ ██║██║ ██║██╔██╗ ██║█████╗ █████╗ ██╔██╗ ██║ ██║ ██║█████╗ █████╗ ███████╗ ╚██╗
// ╚██╗ ██║██║╚██╗██║██║ ██║██║╚██╗██║██╔══╝ ██╔══╝ ██║╚██╗██║ ██║ ██║██╔══╝ ██╔══╝ ╚════██║ ██╔╝
// ╚██╗ ██║██║ ╚████║███████╗██║██║ ╚████║███████╗ ██║ ██║ ╚████║ ██████╔╝███████╗██║ ███████║ ██╔╝
// ╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚═╝╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═════╝ ╚══════╝╚═╝ ╚══════╝ ╚═╝
//
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// FUTURE: extrapolate the following three inline function definitions
// into separate files.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
/**
* FUTURE: extrapolate
* @param {[type]} id [description]
* @return {[type]} [description]
*/
function prepareHook(id) {
var rawHookFn = hooks[id];
// Backwards compatibility:
if (rawHookFn === 'false') {
// FUTURE: Do not allow the string "false" here (now that all environment variables
// are handled via rttc.parseHuman, this is no longer necessary)
sails.log.debug('The string "false" was configured for `sails.config.hooks[\''+id+'\']`.');
sails.log.debug('For compatibility\'s sake, automatically changing this to `false` (boolean).');
sails.log.debug('(Note that this backwards-compatibility check will be removed in a future');
sails.log.debug('release of Sails, so be sure to update this app ASAP.)');
rawHookFn = false;
}//>-
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// COMPATIBILITY NOTE:
// There used to be a check here, to the effect of this:
// ```
// // Check if this hook has a dot in the name.
// // If so, something is wrong.
// var doesHookHaveDotInName = !!id.match(/\./);
// if (doesHookHaveDotInName) {
// var partBeforeDot = id.split('.')[0];
// hooks[partBeforeDot] = false;
// ```
//
// But it was removed in Sails v1, since it was no longer relevant.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Allow disabling of hooks by setting them to `false`.
if (rawHookFn === false) {
delete hooks[id];
return;
}
// Check for invalid hook config
if (hooks.userconfig && !hooks.moduleloader) {
return cb('Invalid configuration:: Cannot use the `userconfig` hook w/o the `moduleloader` hook enabled!');
}
// Handle folder-defined modules (default to index.js)
// Since a hook definition must be a function
if (_.isObject(rawHookFn) && !_.isArray(rawHookFn) && !_.isFunction(rawHookFn)) {
rawHookFn = rawHookFn.index;
}
if (!_.isFunction(rawHookFn)) {
sails.log.error('Malformed hook! (' + id + ')');
sails.log.error('Hooks should be a function with one argument (`sails`)');
sails.log.error('But instead, got:', rawHookFn);
process.exit(1);
}
// Instantiate the hook
var def = rawHookFn(sails);
// Mix in an `identity` property to hook definition
def.identity = id.toLowerCase();
// If a config key was defined for this hook when it was loaded,
// (probably because a user is overridding the default config key)
// set it on the hook definition
def.configKey = rawHookFn.configKey || def.identity;
// New up an actual Hook instance
hooks[id] = new Hook(def);
}//ƒ
/**
* Apply a hook's "defaults" property
*
* FUTURE: extrapolate
*
* @param {[type]} hook [description]
* @return {[type]} [description]
*/
function applyDefaults(hook) {
// Get the hook defaults
var defaults = (_.isFunction(hook.defaults) ?
hook.defaults(sails.config) :
hook.defaults) || {};
// Replace the special __configKey__ key with the actual config key
if (hook.defaults.__configKey__ && hook.configKey) {
hook.defaults[hook.configKey] = hook.defaults.__configKey__;
delete hook.defaults.__configKey__;
}
defaultsDeep(sails.config, defaults);
}//ƒ
/**
* Load a hook (bind its routes, load any modules and initialize it)
*
* FUTURE: extrapolate
*
* @param {[type]} id [description]
* @param {Function} cb [description]
* @return {[type]} [description]
*/
function loadHook(id, cb) {
// TODO: refactor this^^^
// (no need for an inline function declaration)
// Validate `hookTimeout` setting, if present.
if (!_.isUndefined(sails.config.hookTimeout)) {
if (!_.isNumber(sails.config.hookTimeout) || sails.config.hookTimeout < 1 || Math.floor(sails.config.hookTimeout) !== sails.config.hookTimeout) {
return cb(new Error('Invalid `hookTimeout` config! If set, this should be a positive whole number, but instead got `'+sails.config.hookTimeout+'`. Please change this setting, then try lifting again.'));
}
}
var timestampBeforeLoad = Date.now();
var DEFAULT_HOOK_TIMEOUT = 40000;
var timeoutInterval = (sails.config[hooks[id].configKey || id] && sails.config[hooks[id].configKey || id]._hookTimeout) || sails.config.hookTimeout || DEFAULT_HOOK_TIMEOUT;
var hookTimeout;
if (id !== 'userhooks') {
hookTimeout = setTimeout(function tooLong() {
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// FUTURE: sniff hook id here to improve error msg, e.g.:
// ```
// ((id === 'grunt') ? 'It looks like Grunt is still compiling your assets.' : '...')
// ```
// ^^But note that this would require a bit more work: currently, the id here isn't
// necessarily the hook that timed out. (It could be a dependent hook.)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
var err = new Error(
'Sails is taking too long to load.\n'+
'\n'+
'-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --\n'+
' Troubleshooting tips:\n'+
' -• Were you still reading/responding to an interactive prompt?\n'+
' (Whoops, sorry! Please lift again and try to respond a bit more quickly.)\n'+
'\n'+
' -• Do you have a lot of stuff in `assets/`? Grunt might still be running.\n'+
' (Try increasing the hook timeout. Currently it is '+(sails.config.hookTimeout||DEFAULT_HOOK_TIMEOUT)+'.\n'+
' e.g. `sails lift --hookTimeout='+(Math.max(DEFAULT_HOOK_TIMEOUT, 2*(sails.config.hookTimeout||DEFAULT_HOOK_TIMEOUT)))+'`)\n'+
'\n'+
' -• Is `'+id+'` a custom or 3rd party hook?\n'+
' (*If* `initialize()` is using a callback, make sure it\'s being called.)\n'+
'-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --\n'
);
err.code = 'E_HOOK_TIMEOUT';
cb(err);
}, timeoutInterval);
hookTimeouts.push(hookTimeout);
}
hooks[id].load(function(err) {
// Sanity check: (see https://trello.com/c/1jCljHHP for an example of a
// potential bug this catches)
if (!process.nextTick) {
return cb(new Error('Consistency violation: Hmm... it looks like something is wrong with Node\'s `process` global. Check it out:\n'+util.inspect(process)));
}
if (id !== 'userhooks') {
clearTimeout(hookTimeout);
}
if (err) {
// Clear all hook timeouts so that the process doesn't hang because
// something is waiting for this failed hook to load.
_.each(hookTimeouts, function(hookTimeout) {clearTimeout(hookTimeout);});
if (id !== 'userhooks') {
sails.log.error('A hook (`' + id + '`) failed to load!');
}
sails.emit('hook:' + id + ':error');
// Defer a tick to allow other stuff to happen
process.nextTick(function(){ cb(err); });
return;
}
sails.log.verbose(id, 'hook loaded successfully. ('+(Date.now() - timestampBeforeLoad)+'ms)');
sails.emit('hook:' + id + ':loaded');
// Defer a tick to allow other stuff to happen
process.nextTick(function(){ cb(); });
});
}//ƒ
// ██╗ ██╗ ██╗███╗ ██╗██╗ ██╗███╗ ██╗███████╗ ███████╗███╗ ██╗ ██████╗ ███████╗███████╗███████╗ ██╗
// ██╔╝ ██╔╝ ██║████╗ ██║██║ ██║████╗ ██║██╔════╝ ██╔════╝████╗ ██║ ██╔══██╗██╔════╝██╔════╝██╔════╝ ╚██╗
// ██╔╝ ██╔╝ ██║██╔██╗ ██║██║ ██║██╔██╗ ██║█████╗ █████╗ ██╔██╗ ██║ ██║ ██║█████╗ █████╗ ███████╗ ╚██╗
// ╚██╗ ██╔╝ ██║██║╚██╗██║██║ ██║██║╚██╗██║██╔══╝ ██╔══╝ ██║╚██╗██║ ██║ ██║██╔══╝ ██╔══╝ ╚════██║ ██╔╝
// ╚██╗██╔╝ ██║██║ ╚████║███████╗██║██║ ╚████║███████╗ ██║ ██║ ╚████║ ██████╔╝███████╗██║ ███████║ ██╔╝
// ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚═╝╚═╝ ╚═══╝╚══════╝ ╚═╝ ╚═╝ ╚═══╝ ╚═════╝ ╚══════╝╚═╝ ╚══════╝ ╚═╝
//
// </ inline function declarations (see note above) >
// ==============================================================================
// Now do a few things, one after another.
async.series(
{
// First load the moduleloader (if any)
moduleloader: function(cb) {
if (!hooks.moduleloader) {
return cb();
}
prepareHook('moduleloader');
applyDefaults(hooks['moduleloader']);
hooks['moduleloader'].configure();
loadHook('moduleloader', cb);
},
// Next load the user config (if any)
userconfig: function(cb) {
if (!hooks.userconfig) {
return cb();
}
prepareHook('userconfig');
applyDefaults(hooks['userconfig']);
hooks['userconfig'].configure();
loadHook('userconfig', cb);
},
// Next get the user hooks (if any), which will be
// added to the list of hooks to load
userhooks: function(cb) {
if (!hooks.userhooks) {
return cb();
}
prepareHook('userhooks');
applyDefaults(hooks['userhooks']);
hooks['userhooks'].configure();
loadHook('userhooks', cb);
},
validate: function(cb) {
if (hooks.controllers) {
sails.log.debug('=================================================================================');
sails.log.debug('Ignoring `controllers` hook:');
sails.log.debug('As of Sails v1, `controllers` can no longer be disabled/enabled as hooks.');
sails.log.debug('Instead, Sails core now understands controller actions as first-class citizens.');
sails.log.debug('See the Sails v1.0 upgrade guide: http://sailsjs.com/upgrading');
sails.log.debug('=================================================================================');
delete hooks.controllers;
}
return cb();
},
// Prepare all other hooks
prepare: function(cb) {
async.each(_.without(_.keys(hooks), 'userconfig', 'moduleloader', 'userhooks'), function (id, cb) {
prepareHook(id);
// Defer to next tick to allow other stuff to happen
process.nextTick(cb);
}, cb);
},
// Apply the default config for all other hooks
defaults: function(cb) {
async.each(_.without(_.keys(hooks), 'userconfig', 'moduleloader', 'userhooks'), function (id, cb) {
var hook = hooks[id];
applyDefaults(hook);
// Defer to next tick to allow other stuff to happen
process.nextTick(cb);
}, cb);
},
// Run configuration method for all other hooks
configure: function(cb) {
async.each(_.without(_.keys(hooks), 'userconfig', 'moduleloader', 'userhooks'), function (id, cb) {
var hook = hooks[id];
try {
hook.configure();
} catch (err) {
return process.nextTick(function(){ cb(err); });
}
// Defer to next tick to allow other stuff to happen
process.nextTick(cb);
}, cb);
},
// Load all other hooks
load: function(cb) {
async.each(_.without(_.keys(hooks), 'userconfig', 'moduleloader', 'userhooks'), function (id, cb) {
sails.log.silly('Loading hook: ' + id);
loadHook(id, cb);
}, cb);
}
},
function afterwards(err) {
if (err) { return cb(err); }
return cb();
}
);//</async.series>
};
};