UNPKG

4.54 kBJavaScriptView Raw
1'use strict';
2
3const { debug, print, strong } = require('./cnsl');
4const { fork } = require('child_process');
5const chalk = require('chalk');
6const merge = require('lodash/merge');
7const path = require('path');
8const portscanner = require('portscanner');
9const series = require('async/series');
10
11let appServer = {};
12let checkingAppServerPort = false;
13let isReloading = false;
14let server, reloadServer;
15
16module.exports = {
17 /**
18 * Start the servers
19 * @param {Boolean} serve
20 * @param {Boolean} reload
21 * @param {Object} options
22 * - {String} buddyServerPath
23 * - {String} directory
24 * - {Object} env
25 * - {Array} extraDirectories
26 * - {Array} flags
27 * - {String} file
28 * - {Object} headers
29 * - {Number} port
30 * @param {Function} fn(err)
31 */
32 start(serve, reload, options, fn) {
33 const { ReloadServerFactory, ServerFactory } = require(options.buddyServerPath);
34 const tasks = [];
35
36 if (reload) {
37 tasks.push(done => {
38 reloadServer = ReloadServerFactory();
39 reloadServer.on('error', err => {
40 if (!err.message.includes('ECONNRESET')) throw err;
41 });
42 print(`live-reloading on port: ${reloadServer.port}`, 0);
43 reloadServer.start(err => {
44 if (err) {
45 /* ignore */
46 }
47 isReloading = true;
48 done();
49 });
50 });
51 }
52
53 if (serve) {
54 const filepath = path.relative(process.cwd(), options.file || options.directory) || path.dirname(process.cwd());
55
56 tasks.push(done => {
57 const file = options.file ? path.resolve(options.file) : '';
58 const started = err => {
59 if (err) return fn(err);
60 done();
61 };
62
63 print(`serving ${strong(filepath)} at ${chalk.underline('http://localhost:' + options.port)}\n`, 0);
64
65 // Initialize app server
66 if (file) {
67 const env = merge({}, process.env, options.env);
68
69 env.PORT = options.port;
70 appServer = {
71 file,
72 port: options.port,
73 options: {
74 cwd: process.cwd(),
75 env,
76 execArgv: options.flags ? Array.isArray(options.flags) ? options.flags : [options.flags] : []
77 }
78 };
79 startAppServer(started);
80
81 // Initialize static file server
82 } else {
83 server = ServerFactory(options.directory, options.port, options.headers, options.extraDirectories);
84 server.start(started);
85 }
86 });
87 }
88
89 series(tasks, fn);
90 },
91
92 /**
93 * Restart app server
94 * @param {Function} fn
95 */
96 restart(fn) {
97 if (server) {
98 checkingAppServerPort = false;
99 server.removeAllListeners();
100 server.on('close', () => {
101 server = null;
102 startAppServer(fn);
103 });
104 server.kill('SIGKILL');
105 } else {
106 startAppServer(fn);
107 }
108 },
109
110 /**
111 * Refresh the 'file' in browser
112 * @param {String} file
113 */
114 refresh(file) {
115 let msg;
116
117 if (isReloading && reloadServer) {
118 msg = {
119 command: 'reload',
120 path: file,
121 liveCSS: true
122 };
123 debug(`sending ${strong('reload')} command to live-reload clients`, 4);
124
125 reloadServer.activeConnections().forEach(connection => {
126 connection.send(msg);
127 });
128 }
129 },
130
131 /**
132 * Stop servers
133 */
134 stop() {
135 try {
136 if (server) server[appServer ? 'kill' : 'close']();
137 if (reloadServer) reloadServer.close();
138 } catch (err) {
139 /* ignore */
140 }
141 server = null;
142 reloadServer = null;
143 isReloading = false;
144 }
145};
146
147/**
148 * Start app server
149 * @param {Function} fn(err)
150 */
151function startAppServer(fn) {
152 if (!checkingAppServerPort) {
153 server = fork(appServer.file, [], appServer.options);
154 server.on('exit', code => {
155 checkingAppServerPort = false;
156 server.removeAllListeners();
157 server = null;
158 fn(Error('failed to start server'));
159 });
160
161 checkingAppServerPort = true;
162 waitForPortOpen(appServer.port, fn);
163 }
164}
165
166/**
167 * Wait for app server connection on 'port'
168 * @param {Number} port
169 * @param {Function} fn(err)
170 */
171function waitForPortOpen(port, fn) {
172 function check() {
173 if (checkingAppServerPort) {
174 portscanner.checkPortStatus(port, '127.0.0.1', (err, status) => {
175 if (err) {
176 /* ignore */
177 }
178 if (!status || status !== 'open') {
179 setTimeout(check, 20);
180 } else {
181 checkingAppServerPort = false;
182 fn();
183 }
184 });
185 }
186 }
187
188 check();
189}