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/CHANGELOG.md +50 -0
- package/LICENSE +21 -0
- package/README.md +441 -0
- package/gib-run.js +206 -0
- package/index.js +464 -0
- package/injected.html +116 -0
- package/middleware/example.js +5 -0
- package/middleware/spa-ignore-assets.js +14 -0
- package/middleware/spa.js +13 -0
- package/package.json +87 -0
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, '&')
|
|
31
|
+
.replace(/</g, '<')
|
|
32
|
+
.replace(/>/g, '>')
|
|
33
|
+
.replace(/"/g, '"');
|
|
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;
|