UNPKG

14.1 kBJavaScriptView Raw
1/**
2 * We.js main file, load we.js core and features
3 */
4
5// Module dependencies.
6
7// Disable uneed bluebird warnings:
8process.env.BLUEBIRD_W_FORGOTTEN_RETURN = '0';
9
10const http = require('http'),
11 _ = require('lodash'),
12 path = require('path'),
13 staticConfig = require('./staticConfig'),
14 Database = require('./Database'),
15 Hooks = require('./Hooks'),
16 PluginManager = require('./PluginManager'),
17 Router = require('./Router'),
18 Sanitizer = require('./Sanitizer'),
19 EventEmiter = require('events'),
20 { readFileSync, writeFileSync, writeFile } = require('fs');
21
22/**
23 * We.js object
24 *
25 * @type {Object}
26 */
27function We (options) {
28 if (!options) options = {};
29
30 const we = this;
31
32 this.packageJSON = require('../package.json');
33
34 this.config = options;
35
36 this.childProcesses = [];
37
38 this.plugins = {};
39 this.pluginPaths = [];
40 this.pluginNames = [];
41 // controllers
42 this.controllers = {};
43
44 this.projectPath = options.projectPath || process.cwd();
45
46 this.projectPackageJSON = require(path.resolve(this.projectPath, 'package.json'));
47
48 this.projectConfigFolder = options.projectConfigFolder || path.join(this.projectPath, 'config');
49
50 // start configs with static configs
51 this.config = staticConfig(this.projectPath, this);
52 // enviroment config prod | dev | test
53 we.env = options.env || require('./getEnv.js')();
54 // winston logger
55 we.log = require('./log')(we);
56 // hooks and events
57 we.hooks = new Hooks();
58
59 we.events = new EventEmiter();
60 // we.js events is controlled then we will use an max of 70 listenners in every event:
61 we.events.setMaxListeners(70);
62 // we.js sanitizer
63 we.sanitizer = new Sanitizer(this);
64 // The we.js router
65 we.router = new Router(this);
66 // we.js prototypes
67 we.class = {
68 Controller: require('./class/Controller.js'),
69 Theme: require('./class/Theme.js')(we),
70 Plugin: require('./class/Plugin.js')(we)
71 };
72 // database logic and models is avaible in we.db.models
73 we.db = new Database(this);
74 // - init database
75 we.db.defaultConnection = we.db.connect(we.env);
76 //set for more compatbility with sequelize docs
77 we.db.sequelize = we.db.defaultConnection;
78 // plugin manager and plugins vars
79 we.pluginManager = new PluginManager(this);
80
81 switch (we.config.bootstrapMode) {
82 case 'install':
83 case 'installation':
84 case 'update':
85 we.hooks.on('bootstrap', [
86 we.bootstrapFunctions.checkDBConnection,
87 we.bootstrapFunctions.loadCoreFeatures,
88 we.bootstrapFunctions.loadPluginFeatures,
89 we.bootstrapFunctions.initI18n,
90 we.bootstrapFunctions.loadTemplateCache,
91 we.bootstrapFunctions.instantiateModels,
92 we.bootstrapFunctions.syncModels,
93 we.bootstrapFunctions.loadControllers,
94 we.bootstrapFunctions.installAndRegisterPlugins
95 ]);
96 break;
97 case 'complete':
98 case 'full':
99 case 'test':
100 // full load, usefull for tests
101 we.hooks.on('bootstrap', [
102 we.bootstrapFunctions.checkDBConnection,
103 we.bootstrapFunctions.loadCoreFeatures,
104 we.bootstrapFunctions.loadPluginFeatures,
105 we.bootstrapFunctions.initI18n,
106 we.bootstrapFunctions.loadTemplateCache,
107 we.bootstrapFunctions.instantiateModels,
108 we.bootstrapFunctions.syncModels,
109 we.bootstrapFunctions.loadControllers,
110 we.bootstrapFunctions.installAndRegisterPlugins,
111 we.bootstrapFunctions.setExpressApp,
112 we.bootstrapFunctions.passport,
113 we.bootstrapFunctions.createDefaultFolders,
114 we.bootstrapFunctions.registerAllViewTemplates,
115 we.bootstrapFunctions.mergeRoutes,
116 we.bootstrapFunctions.bindResources,
117 we.bootstrapFunctions.bindRoutes
118 ]);
119 break;
120 default:
121 // defaults to load for run
122 we.hooks.on('bootstrap', [
123 we.bootstrapFunctions.checkDBConnection,
124 we.bootstrapFunctions.loadCoreFeatures,
125 we.bootstrapFunctions.loadPluginFeatures,
126 we.bootstrapFunctions.initI18n,
127 we.bootstrapFunctions.loadTemplateCache,
128 we.bootstrapFunctions.instantiateModels,
129 we.bootstrapFunctions.syncModels,
130 we.bootstrapFunctions.loadControllers,
131 we.bootstrapFunctions.installAndRegisterPlugins,
132 we.bootstrapFunctions.setExpressApp,
133 we.bootstrapFunctions.passport,
134 we.bootstrapFunctions.createDefaultFolders,
135 we.bootstrapFunctions.registerAllViewTemplates,
136 we.bootstrapFunctions.mergeRoutes,
137 we.bootstrapFunctions.bindResources,
138 we.bootstrapFunctions.bindRoutes
139 ]);
140 }
141}
142
143We.prototype = {
144 /**
145 * Set config in config/configuration.json file
146 *
147 * @param {String} variable path to the variable
148 * @param {String} value
149 * @param {Function} cb callback
150 */
151 setConfig(variable, value, cb) {
152 if (!cb) cb = function(){};
153
154 let cJSON,
155 cFGpath = path.join(this.projectPath, '/config/configuration.json');
156
157 try {
158 cJSON = JSON.parse(readFileSync(cFGpath));
159 } catch(e) {
160 if (e.code == 'ENOENT') {
161 writeFileSync(cFGpath, '{}');
162 cJSON = {};
163 } else {
164 return cb(e);
165 }
166 }
167
168 if (value == 'true') value = true;
169 if (value == 'false') value = false;
170
171 _.set(cJSON, variable, value);
172
173 writeFile(cFGpath, JSON.stringify(cJSON, null, 2), cb);
174 },
175
176 /**
177 * Unset config in config/configuration.json file
178 *
179 * @param {String} variable path to the variable
180 * @param {String} value
181 * @param {Function} cb callback
182 */
183 unSetConfig(variable, cb) {
184 let cJSON,
185 cFGpath = path.join(this.projectPath, '/config/configuration.json');
186
187 try {
188 cJSON = JSON.parse(readFileSync(cFGpath));
189 } catch(e) {
190 if (e.code == 'ENOENT') {
191 writeFileSync(cFGpath, '{}');
192 cJSON = {};
193 } else {
194 return cb(e);
195 }
196 }
197
198 _.unset(cJSON, variable);
199
200 writeFile(cFGpath, JSON.stringify(cJSON, null, 2), cb);
201 },
202 // set bootstrap functions
203 bootstrapFunctions: require('./bootstrapFunctions'),
204 // flag to check if this we.js instance did the bootstrap
205 bootstrapStarted: false,
206 // flag to check if needs restart
207 needsRestart: false,
208 // we.utils.async, we.utils._ ... see the ./utils file
209 utils: require('./utils'),
210 // load we.js responses
211 responses: require('./responses'),
212 // save we-core path to plugin.js for update e install process
213 weCorePluginfile: path.resolve(__dirname, '../') + '/plugin.js',
214 //Overide default toString and inspect to custom infos in we.js object
215 inspect() {
216 return '\nWe.js ;)\n';
217 },
218 toString() {
219 return this.inspect();
220 },
221 // client side config generator
222 getAppBootstrapConfig: require('./staticConfig/getAppBootstrapConfig.js'),
223
224 /**
225 * Bootstrap and initialize the app
226 *
227 * @param {Object} configOnRun optional
228 * @param {Function} cb callback to run after load we.js
229 */
230 bootstrap(configOnRun, cb) {
231 const we = this;
232 // only bootstrap we.js one time
233 if (we.bootstrapStarted) throw new Error('We.js already did bootstrap');
234
235 we.bootstrapStarted = true;
236 // configsOnRun object is optional
237 if (!cb) {
238 cb = configOnRun;
239 configOnRun = null;
240 }
241 // configs on run extends default and file configs
242 if (configOnRun) _.merge(we.config, configOnRun);
243
244 // run the bootstrap hook
245 we.hooks.trigger('bootstrap', we, function bootstrapDone (err) {
246 if (err) {
247 console.log('Error on bootstrap: ');
248 throw err;
249 }
250
251 we.events.emit('we:bootstrap:done', we);
252
253 we.log.debug('We.js bootstrap done');
254 // set pm2 gracefullReload:
255 we.setPM2grShutdownFN(we);
256
257 return cb(null, we);
258 });
259 },
260
261 /**
262 * Start we.js server (express)
263 * use after we.bootstrap
264 *
265 * @param {Function} cb callback how returns with cb(err);
266 */
267 startServer(cb) {
268 if (!cb) cb = function(){}; // cb is optional
269
270 let we = this;
271
272 we.hooks.trigger('we:server:before:start' ,we ,function afterRunBeforeServerStart (err) {
273 if (err) return cb(err);
274 /**
275 * Get port from environment and store in Express.
276 */
277 let port = normalizePort(we.config.port);
278 we.express.set('port', port);
279
280 /**
281 * Create HTTP server with suport to url alias rewrite
282 */
283 const server = http.createServer(function onCreateServer (req, res){
284 req.we = we;
285
286 // suport for we.js widget API
287 // install we-plugin-widget to enable this feature
288 if (req.headers && req.headers['we-widget-action'] && req.method == 'POST') {
289 req.isWidgetAction = true;
290 req.originalMethod = req.method;
291 req.method = 'GET'; // widgets only are associated to get routes
292 }
293
294 // parse extension if not is public folder
295 if (!we.router.isPublicFolder(req.url)) {
296 req.extension = we.router.splitExtensionFromURL(req.url);
297
298 if (req.extension) {
299 let format = we.utils.mime.getType(req.extension);
300
301 if (req.we.config.responseTypes.includes(format)) {
302 // update the header response format with extension format
303 req.headers.accept = format;
304 // update the old url
305 req.url = req.url.replace('.'+req.extension, '');
306 }
307 }
308 }
309 // install we-plugin-url-alias to enable alias feature
310 if (we.plugins['we-plugin-url-alias'] && we.config.enableUrlAlias) {
311 we.router.alias.httpHandler.bind(this)(req, res);
312 } else {
313 we.express.bind(this)(req, res);
314 }
315 });
316
317 we.events.emit('we:server:after:create', { we: we, server: server });
318
319 /**
320 * Listen on provided port, on all network interfaces.
321 */
322
323 // catch 404 and forward to error handler
324 we.express.use(function routeNotFound (req, res) {
325 we.log.debug('Route not found:', {
326 method: req.method,
327 path: req.path
328 });
329 // var err = new Error('Not Found');
330 // err.status = 404;
331 return res.notFound();
332 });
333
334 server.listen(port);
335 server.on('error', onError);
336 server.on('listening', onListening);
337 /**
338 * Normalize a port into a number, string, or false.
339 */
340 function normalizePort (val) {
341 let port = parseInt(val, 10);
342
343 if (isNaN(port)) {
344 // named pipe
345 return val;
346 }
347
348 if (port >= 0) {
349 // port number
350 return port;
351 }
352
353 return false;
354 }
355
356 /**
357 * Event listener for HTTP server "error" event.
358 */
359 function onError (error) {
360 if (error.syscall !== 'listen') {
361 throw error;
362 }
363
364 let bind = typeof port === 'string'
365 ? 'Pipe ' + port
366 : 'Port ' + port;
367
368 // handle specific listen errors with friendly messages
369 switch (error.code) {
370 case 'EACCES':
371 console.error(bind + ' requires elevated privileges');
372 process.exit(1);
373 break;
374 case 'EADDRINUSE':
375 console.error(bind + ' is already in use');
376 process.exit(1);
377 break;
378 default:
379 throw error;
380 }
381 }
382 /**
383 * Event listener for HTTP server "listening" event.
384 */
385 function onListening () {
386 let addr = server.address(),
387 bind = typeof addr === 'string'
388 ? 'pipe ' + addr
389 : 'port ' + addr.port;
390 we.log.info('Run in '+we.env+' enviroment and listening on ' + bind);
391
392 if (process.send) {
393 process.send('ready');
394 }
395 }
396 // save the current http server
397 we.http = server;
398
399 // express error handlers
400 // will print stacktrace
401 we.express.use(function onExpressError (err, req, res, next) {
402 // invalid url error handling
403 if (err instanceof URIError) {
404 we.log.warn('onExpressError:Error on:', req.path, err);
405
406 res.addMessage('warning', {
407 text: 'router.invalid.url'
408 });
409
410 // go to home page on invalid url request
411 return res.goTo('/');
412 }
413
414 we.log.error('onExpressError:Error on:', req.path, err);
415
416 res.serverError(err);
417 });
418
419 we.hooks.trigger('we:server:after:start',we, function afterHookAfterStart (err) {
420 cb(err, we);
421 });
422 });
423 },
424
425 /**
426 * Bootstrap and Start we.js server
427 *
428 * @param {Object} cfgs configs (optional)
429 * @param {Function} cb callback
430 */
431 go(cfgs, cb) {
432 if (!cb) {
433 cb = cfgs;
434 cfgs = {};
435 }
436
437 this.bootstrap(cfgs, function afterBootstrapOnWeGo (err, we) {
438 if (err) throw err;
439 we.startServer(cb);
440 });
441 },
442
443 /**
444 * Turn off process function
445 *
446 * @param {Function} cb callback
447 */
448 exit(cb) {
449 this.isExiting = true;
450 // close db connection
451 if (this.db.defaultConnection) {
452 this.db.defaultConnection.close();
453 }
454 // close all loggers and wait for end writes
455 this.log.closeAllLoggersAndDisconnect(this, cb);
456 },
457
458 /**
459 * Helper function to delete (unpoint) pointers from response for help GC
460 */
461 freeResponseMemory(req, res) {
462 delete res.locals.req;
463 delete res.locals.regions;
464 delete res.locals.Model;
465 delete res.locals.body;
466 delete res.locals.layoutHtml;
467 },
468
469 /**
470 * Run all plugin and project cron tasks
471 *
472 * @param {Function} cb callback
473 */
474 runCron(cb) {
475 this.cron = require('./cron');
476 this.cron.loadAndRunAllTasks(this, cb);
477 },
478
479 /**
480 * set pm2 gracefullReload
481 */
482 setPM2grShutdownFN(we) {
483 // old/windows version:
484 process.on('message', (msg)=> {
485 if (msg === 'shutdown') {
486 // disconect, and exit
487 we.exit( (err)=> { process.exit(err ? 1 : 0); });
488 }
489 });
490 // new version:
491 process.on('SIGINT', ()=> {
492 we.exit( (err)=> { process.exit(err ? 1 : 0); });
493 });
494 }
495};
496
497module.exports = We;
\No newline at end of file