gib-runs 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/gib-run.js ADDED
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env node
2
+ var path = require('path');
3
+ var fs = require('fs');
4
+ var assign = require('object-assign');
5
+ var chalk = require('chalk');
6
+ var gibRuns = require("./index");
7
+
8
+ var opts = {
9
+ host: process.env.IP,
10
+ port: process.env.PORT,
11
+ open: true,
12
+ mount: [],
13
+ proxy: [],
14
+ middleware: [],
15
+ logLevel: 2,
16
+ };
17
+
18
+ var homeDir = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];
19
+ var configPath = path.join(homeDir, '.gib-runs.json');
20
+ if (fs.existsSync(configPath)) {
21
+ var userConfig = fs.readFileSync(configPath, 'utf8');
22
+ assign(opts, JSON.parse(userConfig));
23
+ if (opts.ignorePattern) opts.ignorePattern = new RegExp(opts.ignorePattern);
24
+ }
25
+
26
+ for (var i = process.argv.length - 1; i >= 2; --i) {
27
+ var arg = process.argv[i];
28
+ if (arg.indexOf("--port=") > -1) {
29
+ var portString = arg.substring(7);
30
+ var portNumber = parseInt(portString, 10);
31
+ if (portNumber === +portString) {
32
+ opts.port = portNumber;
33
+ process.argv.splice(i, 1);
34
+ }
35
+ }
36
+ else if (arg.indexOf("--host=") > -1) {
37
+ opts.host = arg.substring(7);
38
+ process.argv.splice(i, 1);
39
+ }
40
+ else if (arg.indexOf("--open=") > -1) {
41
+ var open = arg.substring(7);
42
+ if (open.indexOf('/') !== 0) {
43
+ open = '/' + open;
44
+ }
45
+ switch (typeof opts.open) {
46
+ case "boolean":
47
+ opts.open = open;
48
+ break;
49
+ case "string":
50
+ opts.open = [opts.open, open];
51
+ break;
52
+ case "object":
53
+ opts.open.push(open);
54
+ break;
55
+ }
56
+ process.argv.splice(i, 1);
57
+ }
58
+ else if (arg.indexOf("--watch=") > -1) {
59
+ // Will be modified later when cwd is known
60
+ opts.watch = arg.substring(8).split(",");
61
+ process.argv.splice(i, 1);
62
+ }
63
+ else if (arg.indexOf("--ignore=") > -1) {
64
+ // Will be modified later when cwd is known
65
+ opts.ignore = arg.substring(9).split(",");
66
+ process.argv.splice(i, 1);
67
+ }
68
+ else if (arg.indexOf("--ignorePattern=") > -1) {
69
+ opts.ignorePattern = new RegExp(arg.substring(16));
70
+ process.argv.splice(i, 1);
71
+ }
72
+ else if (arg === "--no-css-inject") {
73
+ opts.noCssInject = true;
74
+ process.argv.splice(i, 1);
75
+ }
76
+ else if (arg === "--no-browser") {
77
+ opts.open = false;
78
+ process.argv.splice(i, 1);
79
+ }
80
+ else if (arg.indexOf("--browser=") > -1) {
81
+ opts.browser = arg.substring(10).split(",");
82
+ process.argv.splice(i, 1);
83
+ }
84
+ else if (arg.indexOf("--entry-file=") > -1) {
85
+ var file = arg.substring(13);
86
+ if (file.length) {
87
+ opts.file = file;
88
+ process.argv.splice(i, 1);
89
+ }
90
+ }
91
+ else if (arg === "--spa") {
92
+ opts.middleware.push("spa");
93
+ process.argv.splice(i, 1);
94
+ }
95
+ else if (arg === "--quiet" || arg === "-q") {
96
+ opts.logLevel = 0;
97
+ process.argv.splice(i, 1);
98
+ }
99
+ else if (arg === "--verbose" || arg === "-V") {
100
+ opts.logLevel = 3;
101
+ process.argv.splice(i, 1);
102
+ }
103
+ else if (arg.indexOf("--mount=") > -1) {
104
+ // e.g. "--mount=/components:./node_modules" will be ['/components', '<process.cwd()>/node_modules']
105
+ // split only on the first ":", as the path may contain ":" as well (e.g. C:\file.txt)
106
+ var match = arg.substring(8).match(/([^:]+):(.+)$/);
107
+ match[2] = path.resolve(process.cwd(), match[2]);
108
+ opts.mount.push([ match[1], match[2] ]);
109
+ process.argv.splice(i, 1);
110
+ }
111
+ else if (arg.indexOf("--wait=") > -1) {
112
+ var waitString = arg.substring(7);
113
+ var waitNumber = parseInt(waitString, 10);
114
+ if (waitNumber === +waitString) {
115
+ opts.wait = waitNumber;
116
+ process.argv.splice(i, 1);
117
+ }
118
+ }
119
+ else if (arg === "--version" || arg === "-v") {
120
+ var packageJson = require('./package.json');
121
+ console.log(chalk.cyan.bold('\n 🚀 ' + packageJson.name) + chalk.gray(' v' + packageJson.version));
122
+ console.log(chalk.gray(' Modern development server with live reload\n'));
123
+ console.log(chalk.gray(' Author: ') + chalk.white(packageJson.author));
124
+ console.log(chalk.gray(' Repository: ') + chalk.blue(packageJson.repository.url.replace('.git', '')) + '\n');
125
+ process.exit();
126
+ }
127
+ else if (arg.indexOf("--htpasswd=") > -1) {
128
+ opts.htpasswd = arg.substring(11);
129
+ process.argv.splice(i, 1);
130
+ }
131
+ else if (arg === "--cors") {
132
+ opts.cors = true;
133
+ process.argv.splice(i, 1);
134
+ }
135
+ else if (arg.indexOf("--https=") > -1) {
136
+ opts.https = arg.substring(8);
137
+ process.argv.splice(i, 1);
138
+ }
139
+ else if (arg.indexOf("--https-module=") > -1) {
140
+ opts.httpsModule = arg.substring(15);
141
+ process.argv.splice(i, 1);
142
+ }
143
+ else if (arg.indexOf("--proxy=") > -1) {
144
+ // split only on the first ":", as the URL will contain ":" as well
145
+ var match = arg.substring(8).match(/([^:]+):(.+)$/);
146
+ opts.proxy.push([ match[1], match[2] ]);
147
+ process.argv.splice(i, 1);
148
+ }
149
+ else if (arg.indexOf("--middleware=") > -1) {
150
+ opts.middleware.push(arg.substring(13));
151
+ process.argv.splice(i, 1);
152
+ }
153
+ else if (arg === "--help" || arg === "-h") {
154
+ console.log(chalk.cyan.bold('\n 🚀 GIB-RUNS') + chalk.gray(' - Modern Development Server\n'));
155
+ console.log(chalk.white(' Usage: ') + chalk.yellow('gib-runs') + chalk.gray(' [options] [path]\n'));
156
+ console.log(chalk.white(' Options:\n'));
157
+ console.log(chalk.yellow(' -v, --version ') + chalk.gray('Display version'));
158
+ console.log(chalk.yellow(' -h, --help ') + chalk.gray('Show this help'));
159
+ console.log(chalk.yellow(' -q, --quiet ') + chalk.gray('Suppress logging'));
160
+ console.log(chalk.yellow(' -V, --verbose ') + chalk.gray('Verbose logging'));
161
+ console.log(chalk.yellow(' --port=PORT ') + chalk.gray('Set port (default: 8080)'));
162
+ console.log(chalk.yellow(' --host=HOST ') + chalk.gray('Set host (default: 0.0.0.0)'));
163
+ console.log(chalk.yellow(' --open=PATH ') + chalk.gray('Open browser to path'));
164
+ console.log(chalk.yellow(' --no-browser ') + chalk.gray('Suppress browser launch'));
165
+ console.log(chalk.yellow(' --browser=BROWSER ') + chalk.gray('Specify browser'));
166
+ console.log(chalk.yellow(' --ignore=PATH ') + chalk.gray('Ignore paths (comma-separated)'));
167
+ console.log(chalk.yellow(' --watch=PATH ') + chalk.gray('Watch specific paths'));
168
+ console.log(chalk.yellow(' --no-css-inject ') + chalk.gray('Reload on CSS change'));
169
+ console.log(chalk.yellow(' --entry-file=PATH ') + chalk.gray('Entry file for SPA'));
170
+ console.log(chalk.yellow(' --spa ') + chalk.gray('Single Page App mode'));
171
+ console.log(chalk.yellow(' --mount=ROUTE:PATH ') + chalk.gray('Mount directory to route'));
172
+ console.log(chalk.yellow(' --wait=MS ') + chalk.gray('Debounce reload (default: 100ms)'));
173
+ console.log(chalk.yellow(' --htpasswd=PATH ') + chalk.gray('Enable HTTP auth'));
174
+ console.log(chalk.yellow(' --cors ') + chalk.gray('Enable CORS'));
175
+ console.log(chalk.yellow(' --https=PATH ') + chalk.gray('HTTPS config module'));
176
+ console.log(chalk.yellow(' --https-module=MODULE ') + chalk.gray('Custom HTTPS module'));
177
+ console.log(chalk.yellow(' --proxy=ROUTE:URL ') + chalk.gray('Proxy requests'));
178
+ console.log(chalk.yellow(' --middleware=PATH ') + chalk.gray('Add middleware\n'));
179
+ console.log(chalk.gray(' Examples:\n'));
180
+ console.log(chalk.gray(' gib-runs'));
181
+ console.log(chalk.gray(' gib-runs --port=3000 --open=/index.html'));
182
+ console.log(chalk.gray(' gib-runs dist --spa --no-browser\n'));
183
+ process.exit();
184
+ }
185
+ else if (arg === "--test") {
186
+ // Hidden param for tests to exit automatically
187
+ setTimeout(gibRuns.shutdown, 500);
188
+ process.argv.splice(i, 1);
189
+ }
190
+ }
191
+
192
+ // Patch paths
193
+ var dir = opts.root = process.argv[2] || "";
194
+
195
+ if (opts.watch) {
196
+ opts.watch = opts.watch.map(function(relativePath) {
197
+ return path.join(dir, relativePath);
198
+ });
199
+ }
200
+ if (opts.ignore) {
201
+ opts.ignore = opts.ignore.map(function(relativePath) {
202
+ return path.join(dir, relativePath);
203
+ });
204
+ }
205
+
206
+ gibRuns.start(opts);
package/index.js ADDED
@@ -0,0 +1,464 @@
1
+ #!/usr/bin/env node
2
+ var fs = require('fs'),
3
+ connect = require('connect'),
4
+ serveIndex = require('serve-index'),
5
+ logger = require('morgan'),
6
+ WebSocket = require('faye-websocket'),
7
+ path = require('path'),
8
+ http = require('http'),
9
+ send = require('send'),
10
+ open = require('open'),
11
+ es = require("event-stream"),
12
+ compression = require('compression'),
13
+ os = require('os'),
14
+ chokidar = require('chokidar'),
15
+ chalk = require('chalk');
16
+
17
+ var INJECTED_CODE = fs.readFileSync(path.join(__dirname, "injected.html"), "utf8");
18
+
19
+ var GibRuns = {
20
+ server: null,
21
+ watcher: null,
22
+ logLevel: 2,
23
+ startTime: null,
24
+ requestCount: 0,
25
+ reloadCount: 0
26
+ };
27
+
28
+ function escape(html){
29
+ return String(html)
30
+ .replace(/&(?!\w+;)/g, '&amp;')
31
+ .replace(/</g, '&lt;')
32
+ .replace(/>/g, '&gt;')
33
+ .replace(/"/g, '&quot;');
34
+ }
35
+
36
+ // Based on connect.static(), but streamlined and with added code injecter
37
+ function staticServer(root) {
38
+ var isFile = false;
39
+ try { // For supporting mounting files instead of just directories
40
+ isFile = fs.statSync(root).isFile();
41
+ } catch (e) {
42
+ if (e.code !== "ENOENT") throw e;
43
+ }
44
+ return function(req, res, next) {
45
+ if (req.method !== "GET" && req.method !== "HEAD") return next();
46
+ var reqpath = isFile ? "" : new URL(req.url, 'http://localhost').pathname;
47
+ var hasNoOrigin = !req.headers.origin;
48
+ var injectCandidates = [ new RegExp("</body>", "i"), new RegExp("</svg>"), new RegExp("</head>", "i")];
49
+ var injectTag = null;
50
+
51
+ function directory() {
52
+ var pathname = new URL(req.originalUrl, 'http://localhost').pathname;
53
+ res.statusCode = 301;
54
+ res.setHeader('Location', pathname + '/');
55
+ res.end('Redirecting to ' + escape(pathname) + '/');
56
+ }
57
+
58
+ function file(filepath /*, stat*/) {
59
+ var x = path.extname(filepath).toLocaleLowerCase(), match,
60
+ possibleExtensions = [ "", ".html", ".htm", ".xhtml", ".php", ".svg" ];
61
+ if (hasNoOrigin && (possibleExtensions.indexOf(x) > -1)) {
62
+ // TODO: Sync file read here is not nice, but we need to determine if the html should be injected or not
63
+ var contents = fs.readFileSync(filepath, "utf8");
64
+ for (var i = 0; i < injectCandidates.length; ++i) {
65
+ match = injectCandidates[i].exec(contents);
66
+ if (match) {
67
+ injectTag = match[0];
68
+ break;
69
+ }
70
+ }
71
+ if (injectTag === null && GibRuns.logLevel >= 3) {
72
+ console.warn(chalk.yellow("⚠ Failed to inject refresh script!"),
73
+ "Couldn't find any of the tags", injectCandidates, "from", filepath);
74
+ }
75
+ }
76
+ }
77
+
78
+ function error(err) {
79
+ if (err.status === 404) return next();
80
+ next(err);
81
+ }
82
+
83
+ function inject(stream) {
84
+ if (injectTag) {
85
+ // We need to modify the length given to browser
86
+ var len = INJECTED_CODE.length + res.getHeader('Content-Length');
87
+ res.setHeader('Content-Length', len);
88
+ var originalPipe = stream.pipe;
89
+ stream.pipe = function(resp) {
90
+ originalPipe.call(stream, es.replace(new RegExp(injectTag, "i"), INJECTED_CODE + injectTag)).pipe(resp);
91
+ };
92
+ }
93
+ }
94
+
95
+ send(req, reqpath, { root: root })
96
+ .on('error', error)
97
+ .on('directory', directory)
98
+ .on('file', file)
99
+ .on('stream', inject)
100
+ .pipe(res);
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Rewrite request URL and pass it back to the static handler.
106
+ * @param staticHandler {function} Next handler
107
+ * @param file {string} Path to the entry point file
108
+ */
109
+ function entryPoint(staticHandler, file) {
110
+ if (!file) return function(req, res, next) { next(); };
111
+
112
+ return function(req, res, next) {
113
+ req.url = "/" + file;
114
+ staticHandler(req, res, next);
115
+ };
116
+ }
117
+
118
+ /**
119
+ * Start gib-run server with advanced features
120
+ * @param host {string} Address to bind to (default: 0.0.0.0)
121
+ * @param port {number} Port number (default: 8080)
122
+ * @param root {string} Path to root directory (default: cwd)
123
+ * @param watch {array} Paths to exclusively watch for changes
124
+ * @param ignore {array} Paths to ignore when watching files for changes
125
+ * @param ignorePattern {regexp} Ignore files by RegExp
126
+ * @param noCssInject Don't inject CSS changes, just reload as with any other file change
127
+ * @param open {(string|string[])} Subpath(s) to open in browser, use false to suppress launch (default: server root)
128
+ * @param mount {array} Mount directories onto a route, e.g. [['/components', './node_modules']].
129
+ * @param logLevel {number} 0 = errors only, 1 = some, 2 = lots, 3 = verbose
130
+ * @param file {string} Path to the entry point file
131
+ * @param wait {number} Server will wait for all changes, before reloading
132
+ * @param htpasswd {string} Path to htpasswd file to enable HTTP Basic authentication
133
+ * @param middleware {array} Append middleware to stack
134
+ * @param compression {boolean} Enable gzip compression (default: true)
135
+ */
136
+ GibRuns.start = function(options) {
137
+ options = options || {};
138
+ GibRuns.startTime = Date.now();
139
+ var host = options.host || '0.0.0.0';
140
+ var port = options.port !== undefined ? options.port : 8080; // 0 means random
141
+ var root = options.root || process.cwd();
142
+ var mount = options.mount || [];
143
+ var watchPaths = options.watch || [root];
144
+ GibRuns.logLevel = options.logLevel === undefined ? 2 : options.logLevel;
145
+ var openPath = (options.open === undefined || options.open === true) ?
146
+ "" : ((options.open === null || options.open === false) ? null : options.open);
147
+ if (options.noBrowser) openPath = null; // Backwards compatibility with 0.7.0
148
+ var file = options.file;
149
+ var staticServerHandler = staticServer(root);
150
+ var wait = options.wait === undefined ? 100 : options.wait;
151
+ var browser = options.browser || null;
152
+ var htpasswd = options.htpasswd || null;
153
+ var cors = options.cors || false;
154
+ var https = options.https || null;
155
+ var proxy = options.proxy || [];
156
+ var middleware = options.middleware || [];
157
+ var noCssInject = options.noCssInject;
158
+ var httpsModule = options.httpsModule;
159
+ var enableCompression = options.compression !== false;
160
+
161
+ if (httpsModule) {
162
+ try {
163
+ require.resolve(httpsModule);
164
+ } catch (e) {
165
+ console.error(chalk.red("HTTPS module \"" + httpsModule + "\" you've provided was not found."));
166
+ console.error("Did you do", "\"npm install " + httpsModule + "\"?");
167
+ return;
168
+ }
169
+ } else {
170
+ httpsModule = "https";
171
+ }
172
+
173
+ // Setup a web server
174
+ var app = connect();
175
+
176
+ // Enable compression for better performance
177
+ if (enableCompression) {
178
+ app.use(compression());
179
+ }
180
+
181
+ // Request counter middleware
182
+ app.use(function(req, res, next) {
183
+ GibRuns.requestCount++;
184
+ next();
185
+ });
186
+
187
+ // Add logger. Level 2 logs only errors
188
+ if (GibRuns.logLevel === 2) {
189
+ app.use(logger('dev', {
190
+ skip: function (req, res) { return res.statusCode < 400; }
191
+ }));
192
+ // Level 2 or above logs all requests
193
+ } else if (GibRuns.logLevel > 2) {
194
+ app.use(logger('dev'));
195
+ }
196
+ if (options.spa) {
197
+ middleware.push("spa");
198
+ }
199
+ // Add middleware
200
+ middleware.map(function(mw) {
201
+ if (typeof mw === "string") {
202
+ var ext = path.extname(mw).toLocaleLowerCase();
203
+ if (ext !== ".js") {
204
+ mw = require(path.join(__dirname, "middleware", mw + ".js"));
205
+ } else {
206
+ mw = require(mw);
207
+ }
208
+ }
209
+ app.use(mw);
210
+ });
211
+
212
+ // Use http-auth if configured
213
+ if (htpasswd !== null) {
214
+ var auth = require('http-auth');
215
+ var basic = auth.basic({
216
+ realm: "Please authorize",
217
+ file: htpasswd
218
+ });
219
+ // Create middleware wrapper for http-auth v4
220
+ app.use(function(req, res, next) {
221
+ var authHandler = basic.check(function() {
222
+ next();
223
+ });
224
+ authHandler(req, res);
225
+ });
226
+ }
227
+ if (cors) {
228
+ app.use(require("cors")({
229
+ origin: true, // reflecting request origin
230
+ credentials: true // allowing requests with credentials
231
+ }));
232
+ }
233
+ mount.forEach(function(mountRule) {
234
+ var mountPath = path.resolve(process.cwd(), mountRule[1]);
235
+ if (!options.watch) // Auto add mount paths to wathing but only if exclusive path option is not given
236
+ watchPaths.push(mountPath);
237
+ app.use(mountRule[0], staticServer(mountPath));
238
+ if (GibRuns.logLevel >= 1)
239
+ console.log('Mapping %s to "%s"', mountRule[0], mountPath);
240
+ });
241
+ proxy.forEach(function(proxyRule) {
242
+ var proxyUrl = new URL(proxyRule[1]);
243
+ var proxyOpts = {
244
+ protocol: proxyUrl.protocol,
245
+ host: proxyUrl.hostname,
246
+ port: proxyUrl.port,
247
+ pathname: proxyUrl.pathname,
248
+ via: true,
249
+ preserveHost: true
250
+ };
251
+ app.use(proxyRule[0], require('proxy-middleware')(proxyOpts));
252
+ if (GibRuns.logLevel >= 1)
253
+ console.log('Mapping %s to "%s"', proxyRule[0], proxyRule[1]);
254
+ });
255
+ app.use(staticServerHandler) // Custom static server
256
+ .use(entryPoint(staticServerHandler, file))
257
+ .use(serveIndex(root, { icons: true }));
258
+
259
+ var server, protocol;
260
+ if (https !== null) {
261
+ var httpsConfig = https;
262
+ if (typeof https === "string") {
263
+ httpsConfig = require(path.resolve(process.cwd(), https));
264
+ }
265
+ server = require(httpsModule).createServer(httpsConfig, app);
266
+ protocol = "https";
267
+ } else {
268
+ server = http.createServer(app);
269
+ protocol = "http";
270
+ }
271
+
272
+ // Handle server startup errors
273
+ server.addListener('error', function(e) {
274
+ if (e.code === 'EADDRINUSE') {
275
+ var serveURL = protocol + '://' + host + ':' + port;
276
+ console.log(chalk.yellow("⚠ " + serveURL + " is already in use. Trying another port..."));
277
+ setTimeout(function() {
278
+ server.listen(0, host);
279
+ }, 1000);
280
+ } else {
281
+ console.error(chalk.red("✖ Error: " + e.toString()));
282
+ GibRuns.shutdown();
283
+ }
284
+ });
285
+
286
+ // Handle successful server
287
+ server.addListener('listening', function(/*e*/) {
288
+ GibRuns.server = server;
289
+
290
+ var address = server.address();
291
+ var serveHost = address.address === "0.0.0.0" ? "127.0.0.1" : (host === "0.0.0.0" ? address.address : host);
292
+ var openHost = host === "0.0.0.0" ? "127.0.0.1" : host;
293
+
294
+ var serveURL = protocol + '://' + serveHost + ':' + address.port;
295
+ var openURL = protocol + '://' + openHost + ':' + address.port;
296
+
297
+ var serveURLs = [ serveURL ];
298
+ if (GibRuns.logLevel > 2 && address.address === "0.0.0.0") {
299
+ var ifaces = os.networkInterfaces();
300
+ serveURLs = Object.keys(ifaces)
301
+ .map(function(iface) {
302
+ return ifaces[iface];
303
+ })
304
+ // flatten address data, use only IPv4
305
+ .reduce(function(data, addresses) {
306
+ addresses.filter(function(addr) {
307
+ return addr.family === "IPv4";
308
+ }).forEach(function(addr) {
309
+ data.push(addr);
310
+ });
311
+ return data;
312
+ }, [])
313
+ .map(function(addr) {
314
+ return protocol + "://" + addr.address + ":" + address.port;
315
+ });
316
+ }
317
+
318
+ // Output with beautiful formatting
319
+ if (GibRuns.logLevel >= 1) {
320
+ console.log('\\n' + chalk.cyan.bold('━'.repeat(60)));
321
+ console.log(chalk.cyan.bold(' 🚀 GIB-RUNS') + chalk.gray(' v2.0.0'));
322
+ console.log(chalk.cyan.bold('━'.repeat(60)));
323
+ console.log(chalk.white(' 📁 Root: ') + chalk.yellow(root));
324
+ console.log(chalk.white(' 🌐 Local: ') + chalk.green(serveURL));
325
+
326
+ if (GibRuns.logLevel > 2 && serveURLs.length > 1) {
327
+ console.log(chalk.white(' 🔗 Network:'));
328
+ serveURLs.forEach(function(urlItem) {
329
+ if (urlItem !== serveURL) {
330
+ console.log(chalk.white(' ') + chalk.green(urlItem));
331
+ }
332
+ });
333
+ }
334
+
335
+ console.log(chalk.white(' 🔄 Live Reload:') + chalk.green(' Enabled'));
336
+ if (enableCompression) {
337
+ console.log(chalk.white(' 📦 Compression:') + chalk.green(' Enabled'));
338
+ }
339
+ if (cors) {
340
+ console.log(chalk.white(' 🔓 CORS: ') + chalk.green(' Enabled'));
341
+ }
342
+ if (https) {
343
+ console.log(chalk.white(' 🔒 HTTPS: ') + chalk.green(' Enabled'));
344
+ }
345
+ console.log(chalk.cyan.bold('━'.repeat(60)));
346
+ console.log(chalk.gray(' Press Ctrl+C to stop\\n'));
347
+ }
348
+
349
+ // Launch browser
350
+ if (openPath !== null)
351
+ if (typeof openPath === "object") {
352
+ openPath.forEach(function(p) {
353
+ open(openURL + p, {app: browser});
354
+ });
355
+ } else {
356
+ open(openURL + openPath, {app: browser});
357
+ }
358
+ });
359
+
360
+ // Setup server to listen at port
361
+ server.listen(port, host);
362
+
363
+ // WebSocket
364
+ var clients = [];
365
+ server.addListener('upgrade', function(request, socket, head) {
366
+ var ws = new WebSocket(request, socket, head);
367
+ ws.onopen = function() { ws.send('connected'); };
368
+
369
+ if (wait > 0) {
370
+ (function() {
371
+ var wssend = ws.send;
372
+ var waitTimeout;
373
+ ws.send = function() {
374
+ var args = arguments;
375
+ if (waitTimeout) clearTimeout(waitTimeout);
376
+ waitTimeout = setTimeout(function(){
377
+ wssend.apply(ws, args);
378
+ }, wait);
379
+ };
380
+ })();
381
+ }
382
+
383
+ ws.onclose = function() {
384
+ clients = clients.filter(function (x) {
385
+ return x !== ws;
386
+ });
387
+ };
388
+
389
+ clients.push(ws);
390
+ });
391
+
392
+ var ignored = [
393
+ function(testPath) { // Always ignore dotfiles (important e.g. because editor hidden temp files)
394
+ return testPath !== "." && /(^[.#]|(?:__|~)$)/.test(path.basename(testPath));
395
+ }
396
+ ];
397
+ if (options.ignore) {
398
+ ignored = ignored.concat(options.ignore);
399
+ }
400
+ if (options.ignorePattern) {
401
+ ignored.push(options.ignorePattern);
402
+ }
403
+ // Setup file watcher
404
+ GibRuns.watcher = chokidar.watch(watchPaths, {
405
+ ignored: ignored,
406
+ ignoreInitial: true
407
+ });
408
+ function handleChange(changePath) {
409
+ GibRuns.reloadCount++;
410
+ var cssChange = path.extname(changePath) === ".css" && !noCssInject;
411
+ var relPath = path.relative(root, changePath);
412
+ var timestamp = new Date().toLocaleTimeString();
413
+
414
+ if (GibRuns.logLevel >= 1) {
415
+ if (cssChange) {
416
+ console.log(chalk.magenta(' ⚡ [' + timestamp + '] CSS updated: ') + chalk.gray(relPath));
417
+ } else {
418
+ console.log(chalk.cyan(' 🔄 [' + timestamp + '] File changed: ') + chalk.gray(relPath));
419
+ }
420
+ }
421
+ clients.forEach(function(ws) {
422
+ if (ws)
423
+ ws.send(cssChange ? 'refreshcss' : 'reload');
424
+ });
425
+ }
426
+ GibRuns.watcher
427
+ .on("change", handleChange)
428
+ .on("add", handleChange)
429
+ .on("unlink", handleChange)
430
+ .on("addDir", handleChange)
431
+ .on("unlinkDir", handleChange)
432
+ .on("ready", function () {
433
+ if (GibRuns.logLevel >= 1)
434
+ console.log(chalk.cyan(" ✓ Watching for file changes...\\n"));
435
+ })
436
+ .on("error", function (err) {
437
+ console.log(chalk.red(" ✖ Watcher Error: ") + err);
438
+ });
439
+
440
+ return server;
441
+ };
442
+
443
+ GibRuns.shutdown = function() {
444
+ if (GibRuns.logLevel >= 1 && GibRuns.startTime) {
445
+ var uptime = ((Date.now() - GibRuns.startTime) / 1000).toFixed(2);
446
+ console.log('\\n' + chalk.cyan.bold('━'.repeat(60)));
447
+ console.log(chalk.yellow(' 👋 Shutting down GIB-RUNS...'));
448
+ console.log(chalk.gray(' 📊 Statistics:'));
449
+ console.log(chalk.gray(' • Uptime: ') + chalk.white(uptime + 's'));
450
+ console.log(chalk.gray(' • Requests: ') + chalk.white(GibRuns.requestCount));
451
+ console.log(chalk.gray(' • Reloads: ') + chalk.white(GibRuns.reloadCount));
452
+ console.log(chalk.cyan.bold('━'.repeat(60)) + '\\n');
453
+ }
454
+
455
+ var watcher = GibRuns.watcher;
456
+ if (watcher) {
457
+ watcher.close();
458
+ }
459
+ var server = GibRuns.server;
460
+ if (server)
461
+ server.close();
462
+ };
463
+
464
+ module.exports = GibRuns;