UNPKG

6.02 kBJavaScriptView Raw
1'use strict';
2/**
3 * Created by gimm on 3/13/2015.
4 */
5
6var util = require('util'),
7 path = require('path'),
8 assert = require('assert'),
9 spawn = require('child_process').spawn,
10 merge = require('deepmerge'),
11 tinylr = require('tiny-lr'),
12 es = require('event-stream'),
13 Q = require('q'),
14 chalk = require('chalk'),
15 debug = require('debug')('gulp-live-server');
16
17var info = chalk.gray,
18 error = chalk.bold.red;
19
20var glsInstanceCounter = 0;
21
22var callback = {
23 processExit: function (code, sig, server) {
24 glsInstanceCounter--;
25 debug(info('Main process exited with [code => %s | sig => %s]'), code, sig);
26 server && server.kill();
27 },
28
29 serverExit: function (code, sig) {
30 debug(info('server process exited with [code => %s | sig => %s]'), code, sig);
31 if(sig !== 'SIGKILL'){
32 //server stopped unexpectedly
33 process.exit(0);
34 }
35 },
36
37 lrServerReady: function () {
38 console.log(info('livereload[tiny-lr] listening on %s ...'), this.config.livereload.port);
39 },
40
41 serverLog: function (data) {
42 console.log(info(data.trim()));
43 },
44
45 serverError: function (data) {
46 console.log(error(data.trim()));
47 }
48};
49
50/**
51 * set config data for the new server child process
52 * @type {Function}
53 */
54module.exports = exports = (function() {
55 var defaults = {
56 options: {
57 cwd: undefined
58 },
59 livereload: {
60 port: 35729
61 }
62 };
63 defaults.options.env = Object.create(process.env);
64 defaults.options.env.NODE_ENV = 'development';
65
66 return function(args, options, livereload){
67 var config = {}
68 config.args = util.isArray(args) ? args : [args];
69 //deal with options
70 config.options = merge(defaults.options, options || {});
71 //deal with livereload
72 if (livereload) {
73 config.livereload = (typeof livereload === 'object' ? livereload : {port: livereload});
74 }else{
75 config.livereload = (livereload === false ? false : defaults.livereload);
76 }
77 // return exports with its state, the server and livereload instance
78 // this allows multiple servers at once
79 return merge({
80 config: config,
81 server: undefined, // the server child process
82 lr: undefined // tiny-lr serverexports;
83 }, exports);
84 };
85})();
86
87/**
88* default server script, the static server
89*/
90exports.script = path.join(__dirname, 'scripts/static.js');
91
92/**
93* create a server child process with the script file
94*/
95exports.new = function (script) {
96 if(!script){
97 return console.log(error('script file not specified.'));
98 }
99 var args = util.isArray(script) ? script : [script];
100 return this(args);
101};
102
103/**
104* create a server child process with the static server script
105*/
106exports.static = function (folder, port) {
107 var script = this.script;
108 folder = folder || process.cwd();
109 util.isArray(folder) && (folder = folder.join(','));
110 port = port || 3000;
111 return this([script, folder, port]);
112};
113
114/**
115* start/restart the server
116*/
117exports.start = function (execPath) {
118 if (this.server) { // server already running
119 debug(info('kill server'));
120 this.server.kill('SIGKILL');
121 //server.removeListener('exit', callback.serverExit);
122 this.server = undefined;
123 } else {
124 if(this.config.livereload){
125 this.lr = tinylr(this.config.livereload);
126 this.lr.listen(this.config.livereload.port, callback.lrServerReady.bind(this));
127 }
128 }
129
130 // if a executable is specified use that to start the server (e.g. coffeescript)
131 // otherwise use the currents process executable
132 execPath = execPath || process.execPath
133
134 var deferred = Q.defer();
135 this.server = spawn(execPath, this.config.args, this.config.options);
136 this.server.stdout.setEncoding('utf8');
137 this.server.stderr.setEncoding('utf8');
138
139 this.server.stdout.on('data', function (data) {
140 deferred.notify(data);
141 callback.serverLog(data);
142 });
143 this.server.stderr.on('data', function (data) {
144 deferred.notify(data);
145 callback.serverError(data);
146 });
147 this.server.once('exit', function (code, sig) {
148 setTimeout(function() { // yield event loop for stdout/stderr
149 deferred.resolve({
150 code: code,
151 signal: sig
152 });
153 if (glsInstanceCounter == 0)
154 callback.serverExit(code, sig);
155 }, 0)
156 });
157
158 var exit = function(code, sig) {
159 callback.processExit(code,sig,server);
160 }
161 process.listeners('exit') || process.once('exit', exit);
162
163 glsInstanceCounter++;
164 return deferred.promise;
165};
166
167/**
168* stop the server
169*/
170exports.stop = function () {
171 var deferred = Q.defer();
172 if (this.server) {
173 this.server.once('exit', function (code) {
174 deferred.resolve(code);
175 });
176
177 debug(info('kill server'));
178 //use SIGHUP instead of SIGKILL, see issue #34
179 this.server.kill('SIGKILL');
180 //server.removeListener('exit', callback.serverExit);
181 this.server = undefined;
182 }else{
183 deferred.resolve(0);
184 }
185 if(this.lr){
186 debug(info('close livereload server'));
187 this.lr.close();
188 //TODO how to stop tiny-lr from hanging the terminal
189 this.lr = undefined;
190 }
191
192 return deferred.promise;
193};
194
195/**
196* tell livereload.js to reload the changed resource(s)
197*/
198exports.notify = function (event) {
199 if(event && event.path){
200 var filepath = path.relative(__dirname, event.path);
201 debug(info('file(s) changed: %s'), event.path);
202 this.lr.changed({body: {files: [filepath]}});
203 }
204
205 return es.map(function(file, done) {
206 var filepath = path.relative(__dirname, file.path);
207 debug(info('file(s) changed: %s'), filepath);
208 this.lr.changed({body: {files: [filepath]}});
209 done(null, file);
210 });
211};
212