UNPKG

6 kBJavaScriptView Raw
1/*
2 * pub server.js
3 *
4 * launches (express) server after resolving opts
5 * invoke directly via node command line, or via require()
6 * see: https://nodejs.org/api/modules.html#modules_accessing_the_main_module
7 *
8 * copyright 2015-2020, Jürgen Leschner - github.com/jldec - MIT license
9 */
10
11var debug = require('debug')('pub:server');
12
13var osType = require('os').type();
14var exec = require('child_process').exec;
15var events = require('events');
16var path = require('path');
17var u = require('pub-util');
18var asyncbuilder = require('asyncbuilder');
19
20if (require.main === module) {
21 pubServer().run();
22}
23else {
24 module.exports = pubServer;
25}
26
27u.inherits(pubServer, events.EventEmitter);
28
29//--//--//--//--//--//--//--//--//--//--//--//--//
30
31function pubServer(opts) {
32
33 if (!(this instanceof pubServer)) return new pubServer(opts);
34 events.EventEmitter.call(this);
35
36 var server = this;
37
38 // pass platform-specific ./node_modules path to resolve-opts for builtin packages
39 server.opts = opts = require('pub-resolve-opts')(opts, path.join(__dirname, 'node_modules'));
40
41 opts.production = opts.production || (process.env.NODE_ENV === 'production');
42 opts.port = opts.port || process.env.PORT || '3001';
43 opts.appUrl = opts.appUrl || process.env.APP || ('http://localhost:' + opts.port);
44
45 server.run = run;
46
47 var generator = server.generator = require('pub-generator')(opts);
48
49 u.each(opts.serverPlugins.reverse(), function(plugin) {
50 debug('server plugin:', plugin.inspect());
51 require(plugin.path)(server);
52 });
53
54 u.each(opts.generatorPlugins.reverse(), function(plugin) {
55 debug('generator plugin:', plugin.path, plugin.inspect());
56 require(plugin.path)(generator);
57 });
58
59 var log = opts.log;
60
61 //--//--//--//--//--//--//--//--//--//--//--//
62
63 function run() {
64
65 if (opts.outputOnly) {
66
67 // only support single output for now
68 var output = opts.outputs[0];
69 if (!output) return log('No output configured.');
70
71 if (output.overrideOpts) {
72 log('output only - override opts', output.overrideOpts);
73 u.assign(opts, output.overrideOpts);
74 }
75
76 generator.load(function(err) {
77 if (err) return log(err);
78 outputAll(output, function(err) {
79 if (err) { log(err); }
80 generator.unload();
81 });
82 });
83
84 return;
85 }
86
87 generator.load(function(err) {
88 if (err) return log(err);
89
90 if (opts.logPages) {
91 generator.logPages();
92 generator.unload();
93 return;
94 }
95
96 require('./server/watch-sources')(generator);
97 generator.listen(true);
98
99 if (!opts['no-server']) { expressApp(); }
100 });
101 }
102
103 function outputAll(output, cb) {
104
105 var ab = asyncbuilder(function(err, a) {
106 if (err) return cb(err);
107 log('output %s pages, %s scripts, %s statics', a[0].length, a[1].length, a[2].length);
108
109 var filemap = u.flatten(a);
110 // check for duplicates assuming case-insensitive output file system
111 var dup$ = {};
112 u.each(filemap, function(file) {
113 if (dup$[file.path.toLowerCase()]) return log('WARNING: duplicate file in output:', file.path);
114 dup$[file.path.toLowerCase()] = 1;
115 });
116 if (output.fileMap) {
117 output.src.put( [ { path:'/filemap.json', text:JSON.stringify(filemap,null,2) } ], function(err) {
118 if (err) log(err);
119 });
120 }
121 cb();
122 });
123
124 var pagesDone = ab.asyncAppend();
125 var scriptsDone = ab.asyncAppend();
126 var staticsDone = ab.asyncAppend();
127 ab.complete();
128
129 generator.outputPages(output, pagesDone);
130
131 require('./server/serve-scripts')(opts).outputAll(output, generator, scriptsDone);
132
133 // wait for static scan to complete before starting output
134 var statics = require('./server/serve-statics')(opts, function(){
135 statics.outputAll(output, staticsDone);
136 });
137 }
138
139 function expressApp() {
140
141 server.app = require('express')();
142 server.http = require('http').Server(server.app);
143 require('./server/serve-sockets')(server);
144
145 server.app.disable('x-powered-by');
146
147 // see https://expressjs.com/en/guide/behind-proxies.html - boolean type sensitive
148 server.app.set('trust proxy', opts['trust proxy'] || false);
149
150 // log(err) shouldn't throw anymore
151 log.logger.noErrors = true;
152
153 // sessions come with optional redis logger
154 server.sessions = require('pub-serve-sessions')(server);
155 log('starting up', opts.appUrl, (opts.production ? 'production' : 'non-production'));
156
157 // other default middleware
158 var bodyParser = require('body-parser');
159 server.app.use(bodyParser.json());
160 server.app.use(bodyParser.urlencoded({ extended: false }));
161 server.app.use(require('compression')());
162
163 server.emit('init-app-first');
164 if (!(opts.auth && opts.auth.disabled)) {
165 server.sessions.authorizeRoutes();
166 }
167
168 server.statics = require('./server/serve-statics')(opts).serveRoutes(server);
169
170 if (!opts.staticOnly) {
171 server.scripts = require('./server/serve-scripts')(opts).serveRoutes(server);
172 require('./server/serve-pages')(server);
173 }
174
175 server.emit('init-app-last');
176
177 var limit = 10;
178 var tried = 1;
179 server.http.on('error', function(err) {
180 if (err.code === 'EADDRINUSE' && tried < limit) {
181 server.http.close();
182 opts.port = (parseInt(opts.port) || 3001) + 1;
183 log('port in use, trying', opts.port);
184 server.http.listen(opts.port);
185 tried ++;
186 }
187 });
188
189 require('./server/handle-errors')(server);
190
191 server.http.listen(opts.port);
192 log('port', opts.port);
193
194 process.on('SIGTERM', function() {
195 log('shutting down');
196 server.http.close(function() {
197 generator.unload();
198 server.emit('shutdown');
199 process.exit();
200 });
201 });
202
203 setTimeout(function() {
204 if (opts.cli && opts.openBrowser && osType.match(/Darwin/)) {
205 exec('open "http://localhost:"' + opts.port);
206 }
207 }, 100);
208 }
209
210}