gib-runs 2.3.8 → 2.4.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 +27 -0
- package/README.md +73 -55
- package/index.js +124 -101
- package/lib/process-runner.js +94 -96
- package/lib/tunnel.js +73 -136
- package/middleware/error-page.js +2 -4
- package/middleware/health.js +37 -28
- package/middleware/history.js +38 -0
- package/middleware/logger.js +4 -5
- package/middleware/performance.js +10 -14
- package/middleware/rate-limit.js +14 -27
- package/middleware/security.js +7 -21
- package/middleware/spa-ignore-assets.js +9 -8
- package/middleware/spa.js +7 -7
- package/middleware/upload.js +29 -31
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [2.4.0] - 2026-02-15
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- 📜 **Request History Tracking** - New `/history` endpoint
|
|
9
|
+
- Tracks last 50 HTTP requests with full details
|
|
10
|
+
- Includes timestamp, method, URL, status, duration, and IP
|
|
11
|
+
- Perfect for debugging and monitoring
|
|
12
|
+
- 📊 **Detailed File Change Notifications** - Enhanced file watching
|
|
13
|
+
- Shows file size on changes: `File changed: app.js (12.45KB)`
|
|
14
|
+
- Separate notifications for file add/delete events
|
|
15
|
+
- ➕ File added (logLevel >= 2)
|
|
16
|
+
- ➖ File deleted (logLevel >= 2)
|
|
17
|
+
- 📝 **Comprehensive Logging** - Complete request and error logging
|
|
18
|
+
- All requests logged with timestamp and IP address
|
|
19
|
+
- Consistent ISO 8601 timestamps across all logs
|
|
20
|
+
- Works in static, npm-script, and exec modes
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
- 🔧 **Improved Error Handling** - Better error messages with timestamps
|
|
24
|
+
- Server errors include timestamp and stack trace
|
|
25
|
+
- Process errors in npm/exec mode properly logged
|
|
26
|
+
- Watcher errors show detailed information
|
|
27
|
+
- ⚡ **Enhanced Process Runner** - Better npm-script and exec mode
|
|
28
|
+
- Stderr properly captured and logged with timestamps
|
|
29
|
+
- Exit codes and signals logged with context
|
|
30
|
+
- Cleaner output formatting
|
|
31
|
+
|
|
5
32
|
## [2.3.8] - 2026-02-15
|
|
6
33
|
|
|
7
34
|
### Changed
|
package/README.md
CHANGED
|
@@ -4,19 +4,19 @@
|
|
|
4
4
|
[](https://www.npmjs.org/package/gib-runs)
|
|
5
5
|
[](https://github.com/levouinse/gib-runs/blob/main/LICENSE)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
A modern development server with live reload and hot module replacement. Built for developers who value capability over connections.
|
|
8
8
|
|
|
9
|
-
> *"Unlike some people, this actually runs on
|
|
9
|
+
> *"Unlike some people, this actually runs on merit, not nepotism."*
|
|
10
10
|
|
|
11
11
|
## Why GIB-RUNS?
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Named after Indonesia's Vice President Gibran Rakabuming Raka, who got his position through family connections. But unlike certain political figures, this server:
|
|
14
14
|
|
|
15
|
-
- ✅ Earned its position through
|
|
16
|
-
- ✅ Works
|
|
15
|
+
- ✅ Earned its position through real features
|
|
16
|
+
- ✅ Works without shortcuts or special treatment
|
|
17
17
|
- ✅ Serves everyone equally
|
|
18
|
-
- ✅ Transparent about what it does
|
|
19
|
-
- ✅ Performance based on
|
|
18
|
+
- ✅ Transparent and honest about what it does
|
|
19
|
+
- ✅ Performance based on actual metrics
|
|
20
20
|
|
|
21
21
|
*"When your dev server has more integrity than some vice presidents."*
|
|
22
22
|
|
|
@@ -26,6 +26,8 @@ The name playfully references Indonesia's Vice President Gibran Rakabuming Raka,
|
|
|
26
26
|
- ⚡ **Hot CSS Injection** - Update styles without full page reload
|
|
27
27
|
- 🎨 **Beautiful UI** - Modern status indicator with real-time feedback
|
|
28
28
|
- 📊 **Performance Monitoring** - Track requests, reloads, and uptime
|
|
29
|
+
- 📝 **Comprehensive Logging** - All requests, errors, and file changes with timestamps and file sizes
|
|
30
|
+
- 📜 **Request History** - Track last 50 requests via `/history` endpoint
|
|
29
31
|
- 🗜️ **Compression** - Built-in gzip compression
|
|
30
32
|
- 🔒 **HTTPS/HTTP2** - Secure development with modern protocols
|
|
31
33
|
- 🌐 **CORS Support** - Easy cross-origin development
|
|
@@ -34,7 +36,7 @@ The name playfully references Indonesia's Vice President Gibran Rakabuming Raka,
|
|
|
34
36
|
- 🔌 **Proxy Support** - Proxy API requests during development
|
|
35
37
|
- 📦 **Middleware** - Extend functionality with custom middleware
|
|
36
38
|
- 🎭 **Mount Directories** - Serve multiple directories on different routes
|
|
37
|
-
- 🚀 **NPM Scripts** - Run npm
|
|
39
|
+
- 🚀 **NPM Scripts** - Run npm scripts alongside the server
|
|
38
40
|
- 🔄 **PM2 Integration** - Production-ready process management
|
|
39
41
|
- 🌍 **Public Tunnels** - Share your dev server with anyone, anywhere
|
|
40
42
|
- 📱 **Multi-Device** - Access from any device on your network
|
|
@@ -68,44 +70,16 @@ gib-runs --spa
|
|
|
68
70
|
gib-runs --https=./config/https.conf.js
|
|
69
71
|
```
|
|
70
72
|
|
|
71
|
-
##
|
|
73
|
+
## Common Use Cases
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
|--------|-------------|---------|
|
|
75
|
-
| `--port=NUMBER` | Port to use | `8080` |
|
|
76
|
-
| `--host=ADDRESS` | Address to bind to | `0.0.0.0` |
|
|
77
|
-
| `--open=PATH` | Path to open in browser | `/` |
|
|
78
|
-
| `--no-browser` | Suppress browser launch | `false` |
|
|
79
|
-
| `-q, --quiet` | Suppress logging | `false` |
|
|
80
|
-
| `-V, --verbose` | Verbose logging | `false` |
|
|
81
|
-
| `--watch=PATH` | Paths to watch (comma-separated) | All files |
|
|
82
|
-
| `--ignore=PATH` | Paths to ignore (comma-separated) | None |
|
|
83
|
-
| `--no-css-inject` | Reload page on CSS change | `false` |
|
|
84
|
-
| `--spa` | Single Page App mode | `false` |
|
|
85
|
-
| `--cors` | Enable CORS | `false` |
|
|
86
|
-
| `--https=PATH` | HTTPS config module | None |
|
|
87
|
-
| `--proxy=ROUTE:URL` | Proxy requests | None |
|
|
88
|
-
| `--middleware=PATH` | Custom middleware | None |
|
|
89
|
-
| `--htpasswd=PATH` | HTTP auth file | None |
|
|
90
|
-
| `--tunnel` | Create public tunnel | `false` |
|
|
91
|
-
| `--tunnel-service=NAME` | Tunnel service (lt, cf, ngrok) | `localtunnel` |
|
|
92
|
-
| `--npm-script=SCRIPT` | Run npm script (dev, start, etc) | None |
|
|
93
|
-
| `--exec=COMMAND` | Run custom command | None |
|
|
94
|
-
| `--pm2` | Use PM2 process manager | `false` |
|
|
95
|
-
| `--auto-restart` | Auto-restart server on crash | `false` |
|
|
96
|
-
| `--enable-upload` | Enable file upload endpoint | `false` |
|
|
97
|
-
| `--log-to-file` | Log requests to file | `false` |
|
|
98
|
-
|
|
99
|
-
## Usage Examples
|
|
100
|
-
|
|
101
|
-
### Basic Server
|
|
75
|
+
### Basic Development Server
|
|
102
76
|
|
|
103
77
|
```bash
|
|
104
|
-
# Serve current directory
|
|
78
|
+
# Serve current directory with live reload
|
|
105
79
|
gib-runs
|
|
106
80
|
|
|
107
|
-
#
|
|
108
|
-
gib-runs --port=3000
|
|
81
|
+
# Custom port and verbose logging
|
|
82
|
+
gib-runs --port=3000 --verbose
|
|
109
83
|
|
|
110
84
|
# Serve specific directory
|
|
111
85
|
gib-runs ./public
|
|
@@ -117,7 +91,7 @@ gib-runs ./public
|
|
|
117
91
|
# SPA mode (redirects all routes to index.html)
|
|
118
92
|
gib-runs --spa
|
|
119
93
|
|
|
120
|
-
#
|
|
94
|
+
# SPA with custom port
|
|
121
95
|
gib-runs --spa --port=8000
|
|
122
96
|
```
|
|
123
97
|
|
|
@@ -147,7 +121,7 @@ gib-runs --mount=/static:./assets --mount=/lib:./node_modules
|
|
|
147
121
|
# Run npm dev script with live reload
|
|
148
122
|
gib-runs --npm-script=dev
|
|
149
123
|
|
|
150
|
-
# Run with PM2
|
|
124
|
+
# Run with PM2 process manager
|
|
151
125
|
gib-runs --npm-script=dev --pm2
|
|
152
126
|
|
|
153
127
|
# Run custom command
|
|
@@ -163,7 +137,7 @@ gib-runs --tunnel
|
|
|
163
137
|
# Use Cloudflare Tunnel
|
|
164
138
|
gib-runs --tunnel-service=cloudflared
|
|
165
139
|
|
|
166
|
-
# Use Ngrok
|
|
140
|
+
# Use Ngrok (requires auth token)
|
|
167
141
|
gib-runs --tunnel-service=ngrok --tunnel-authtoken=YOUR_TOKEN
|
|
168
142
|
```
|
|
169
143
|
|
|
@@ -187,15 +161,33 @@ Then run:
|
|
|
187
161
|
gib-runs --https=./https.conf.js
|
|
188
162
|
```
|
|
189
163
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
```bash
|
|
193
|
-
# Create htpasswd file
|
|
194
|
-
htpasswd -c .htpasswd username
|
|
164
|
+
## CLI Options
|
|
195
165
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
166
|
+
| Option | Description | Default |
|
|
167
|
+
|--------|-------------|---------|
|
|
168
|
+
| `--port=NUMBER` | Port to use | `8080` |
|
|
169
|
+
| `--host=ADDRESS` | Address to bind to | `0.0.0.0` |
|
|
170
|
+
| `--open=PATH` | Path to open in browser | `/` |
|
|
171
|
+
| `--no-browser` | Suppress browser launch | `false` |
|
|
172
|
+
| `-q, --quiet` | Suppress logging | `false` |
|
|
173
|
+
| `-V, --verbose` | Verbose logging | `false` |
|
|
174
|
+
| `--watch=PATH` | Paths to watch (comma-separated) | All files |
|
|
175
|
+
| `--ignore=PATH` | Paths to ignore (comma-separated) | None |
|
|
176
|
+
| `--no-css-inject` | Reload page on CSS change | `false` |
|
|
177
|
+
| `--spa` | Single Page App mode | `false` |
|
|
178
|
+
| `--cors` | Enable CORS | `false` |
|
|
179
|
+
| `--https=PATH` | HTTPS config module | None |
|
|
180
|
+
| `--proxy=ROUTE:URL` | Proxy requests | None |
|
|
181
|
+
| `--middleware=PATH` | Custom middleware | None |
|
|
182
|
+
| `--htpasswd=PATH` | HTTP auth file | None |
|
|
183
|
+
| `--tunnel` | Create public tunnel | `false` |
|
|
184
|
+
| `--tunnel-service=NAME` | Tunnel service (lt, cf, ngrok) | `localtunnel` |
|
|
185
|
+
| `--npm-script=SCRIPT` | Run npm script | None |
|
|
186
|
+
| `--exec=COMMAND` | Run custom command | None |
|
|
187
|
+
| `--pm2` | Use PM2 process manager | `false` |
|
|
188
|
+
| `--auto-restart` | Auto-restart server on crash | `false` |
|
|
189
|
+
| `--enable-upload` | Enable file upload endpoint | `false` |
|
|
190
|
+
| `--log-to-file` | Log requests to file | `false` |
|
|
199
191
|
|
|
200
192
|
## Node.js API
|
|
201
193
|
|
|
@@ -263,10 +255,37 @@ Or `.gib-runs.json` in your project root (overrides global config):
|
|
|
263
255
|
}
|
|
264
256
|
```
|
|
265
257
|
|
|
266
|
-
**Priority**:
|
|
258
|
+
**Priority**: CLI arguments > Project config > Global config > Defaults
|
|
267
259
|
|
|
268
260
|
## Advanced Features
|
|
269
261
|
|
|
262
|
+
### Request History
|
|
263
|
+
|
|
264
|
+
Track all HTTP requests for debugging:
|
|
265
|
+
|
|
266
|
+
```bash
|
|
267
|
+
# View request history
|
|
268
|
+
curl http://localhost:8080/history
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Response:
|
|
272
|
+
|
|
273
|
+
```json
|
|
274
|
+
{
|
|
275
|
+
"total": 10,
|
|
276
|
+
"requests": [
|
|
277
|
+
{
|
|
278
|
+
"timestamp": "2026-02-15T06:09:17.875Z",
|
|
279
|
+
"method": "GET",
|
|
280
|
+
"url": "/index.html",
|
|
281
|
+
"status": 200,
|
|
282
|
+
"duration": "5ms",
|
|
283
|
+
"ip": "127.0.0.1"
|
|
284
|
+
}
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
270
289
|
### Environment Variables
|
|
271
290
|
|
|
272
291
|
Automatically loads `.env` file from project root:
|
|
@@ -357,7 +376,7 @@ cat gib-runs.log | jq 'select(.duration | tonumber > 100)'
|
|
|
357
376
|
Create `middleware/custom.js`:
|
|
358
377
|
|
|
359
378
|
```javascript
|
|
360
|
-
module.exports =
|
|
379
|
+
module.exports = (req, res, next) => {
|
|
361
380
|
console.log('Request:', req.url);
|
|
362
381
|
next();
|
|
363
382
|
};
|
|
@@ -379,7 +398,6 @@ Server automatically binds to `0.0.0.0` and shows all network URLs:
|
|
|
379
398
|
📁 Root: /home/user/project
|
|
380
399
|
🌐 Local: http://127.0.0.1:8080
|
|
381
400
|
🔗 Network: http://192.168.1.100:8080
|
|
382
|
-
🔗 Network: http://10.0.0.5:8080
|
|
383
401
|
🔄 Live Reload: Enabled
|
|
384
402
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
385
403
|
```
|
package/index.js
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const connect = require('connect');
|
|
4
|
+
const serveIndex = require('serve-index');
|
|
5
|
+
const logger = require('morgan');
|
|
6
|
+
const WebSocket = require('faye-websocket');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const http = require('http');
|
|
9
|
+
const send = require('send');
|
|
10
|
+
const open = require('open');
|
|
11
|
+
const es = require('event-stream');
|
|
12
|
+
const compression = require('compression');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
const chokidar = require('chokidar');
|
|
15
|
+
const chalk = require('chalk');
|
|
16
16
|
|
|
17
17
|
try {
|
|
18
18
|
require('dotenv').config({ path: path.join(process.cwd(), '.env') });
|
|
19
19
|
} catch (e) {}
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
const INJECTED_CODE = fs.readFileSync(path.join(__dirname, 'injected.html'), 'utf8');
|
|
22
|
+
const packageJson = require('./package.json');
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
const GibRuns = {
|
|
25
25
|
server: null,
|
|
26
26
|
watcher: null,
|
|
27
27
|
logLevel: 2,
|
|
@@ -35,81 +35,76 @@ var GibRuns = {
|
|
|
35
35
|
restartCount: 0
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
.replace(/"/g, '"');
|
|
44
|
-
}
|
|
38
|
+
const escape = (html) => String(html)
|
|
39
|
+
.replace(/&(?!\w+;)/g, '&')
|
|
40
|
+
.replace(/</g, '<')
|
|
41
|
+
.replace(/>/g, '>')
|
|
42
|
+
.replace(/"/g, '"');
|
|
45
43
|
|
|
46
|
-
// Based on connect.static(), but streamlined and with added code injecter
|
|
47
44
|
function staticServer(root) {
|
|
48
|
-
|
|
49
|
-
try {
|
|
45
|
+
let isFile = false;
|
|
46
|
+
try {
|
|
50
47
|
isFile = fs.statSync(root).isFile();
|
|
51
48
|
} catch (e) {
|
|
52
|
-
if (e.code !==
|
|
49
|
+
if (e.code !== 'ENOENT') throw e;
|
|
53
50
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
51
|
+
|
|
52
|
+
return (req, res, next) => {
|
|
53
|
+
if (req.method !== 'GET' && req.method !== 'HEAD') return next();
|
|
54
|
+
|
|
55
|
+
const reqpath = isFile ? '' : new URL(req.url, 'http://localhost').pathname;
|
|
56
|
+
const hasNoOrigin = !req.headers.origin;
|
|
57
|
+
const injectCandidates = [/<\/body>/i, /<\/svg>/i, /<\/head>/i];
|
|
58
|
+
let injectTag = null;
|
|
59
|
+
|
|
60
|
+
const directory = () => {
|
|
61
|
+
const pathname = new URL(req.originalUrl, 'http://localhost').pathname;
|
|
63
62
|
res.statusCode = 301;
|
|
64
63
|
res.setHeader('Location', pathname + '/');
|
|
65
64
|
res.end('Redirecting to ' + escape(pathname) + '/');
|
|
66
|
-
}
|
|
65
|
+
};
|
|
67
66
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (injectTag === null && GibRuns.logLevel >= 3) {
|
|
82
|
-
console.warn(chalk.yellow("⚠ Failed to inject refresh script!"),
|
|
83
|
-
"Couldn't find any of the tags", injectCandidates, "from", filepath);
|
|
67
|
+
const file = (filepath) => {
|
|
68
|
+
const ext = path.extname(filepath).toLowerCase();
|
|
69
|
+
const injectableExts = ['', '.html', '.htm', '.xhtml', '.php', '.svg'];
|
|
70
|
+
|
|
71
|
+
if (hasNoOrigin && injectableExts.includes(ext)) {
|
|
72
|
+
const contents = fs.readFileSync(filepath, 'utf8');
|
|
73
|
+
const match = injectCandidates.find(regex => regex.test(contents));
|
|
74
|
+
|
|
75
|
+
if (match) {
|
|
76
|
+
injectTag = contents.match(match)[0];
|
|
77
|
+
} else if (GibRuns.logLevel >= 3) {
|
|
78
|
+
console.warn(chalk.yellow('⚠ Failed to inject refresh script!'),
|
|
79
|
+
"Couldn't find any of the tags", injectCandidates, 'from', filepath);
|
|
84
80
|
}
|
|
85
81
|
}
|
|
86
|
-
}
|
|
82
|
+
};
|
|
87
83
|
|
|
88
|
-
|
|
84
|
+
const error = (err) => {
|
|
89
85
|
if (err.status === 404) return next();
|
|
90
86
|
next(err);
|
|
91
|
-
}
|
|
87
|
+
};
|
|
92
88
|
|
|
93
|
-
|
|
89
|
+
const inject = (stream) => {
|
|
94
90
|
if (injectTag) {
|
|
95
|
-
|
|
96
|
-
var len = INJECTED_CODE.length + res.getHeader('Content-Length');
|
|
91
|
+
const len = INJECTED_CODE.length + res.getHeader('Content-Length');
|
|
97
92
|
res.setHeader('Content-Length', len);
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
93
|
+
const originalPipe = stream.pipe;
|
|
94
|
+
|
|
95
|
+
stream.pipe = (resp) => {
|
|
96
|
+
const envReplacer = es.mapSync((data) =>
|
|
97
|
+
data.toString().replace(/\$\{([^}]+)\}/g, (match, varName) =>
|
|
98
|
+
process.env[varName] || ''
|
|
99
|
+
)
|
|
100
|
+
);
|
|
101
|
+
const codeInject = es.replace(new RegExp(injectTag, 'i'), INJECTED_CODE + injectTag);
|
|
107
102
|
originalPipe.call(stream, envReplacer).pipe(codeInject).pipe(resp);
|
|
108
103
|
};
|
|
109
104
|
}
|
|
110
|
-
}
|
|
105
|
+
};
|
|
111
106
|
|
|
112
|
-
send(req, reqpath, { root
|
|
107
|
+
send(req, reqpath, { root })
|
|
113
108
|
.on('error', error)
|
|
114
109
|
.on('directory', directory)
|
|
115
110
|
.on('file', file)
|
|
@@ -118,16 +113,11 @@ function staticServer(root) {
|
|
|
118
113
|
};
|
|
119
114
|
}
|
|
120
115
|
|
|
121
|
-
/**
|
|
122
|
-
* Rewrite request URL and pass it back to the static handler.
|
|
123
|
-
* @param staticHandler {function} Next handler
|
|
124
|
-
* @param file {string} Path to the entry point file
|
|
125
|
-
*/
|
|
126
116
|
function entryPoint(staticHandler, file) {
|
|
127
|
-
if (!file) return
|
|
117
|
+
if (!file) return (req, res, next) => next();
|
|
128
118
|
|
|
129
|
-
return
|
|
130
|
-
req.url =
|
|
119
|
+
return (req, res, next) => {
|
|
120
|
+
req.url = '/' + file;
|
|
131
121
|
staticHandler(req, res, next);
|
|
132
122
|
};
|
|
133
123
|
}
|
|
@@ -228,23 +218,29 @@ GibRuns.start = function(options) {
|
|
|
228
218
|
app.use(compression());
|
|
229
219
|
}
|
|
230
220
|
|
|
231
|
-
// Request counter middleware
|
|
221
|
+
// Request counter and logging middleware
|
|
232
222
|
if (serveStatic) {
|
|
233
223
|
app.use(function(req, res, next) {
|
|
234
224
|
GibRuns.requestCount++;
|
|
225
|
+
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
|
226
|
+
const timestamp = new Date().toISOString();
|
|
235
227
|
|
|
236
|
-
// Log
|
|
237
|
-
if (GibRuns.logLevel >=
|
|
238
|
-
|
|
239
|
-
console.log(chalk.gray(' [' + timestamp + '] ') +
|
|
228
|
+
// Log all incoming requests
|
|
229
|
+
if (GibRuns.logLevel >= 1) {
|
|
230
|
+
console.log(chalk.gray(` [${timestamp}] `) +
|
|
240
231
|
chalk.cyan(req.method) + ' ' +
|
|
241
232
|
chalk.white(req.url) + ' ' +
|
|
242
|
-
chalk.gray(
|
|
233
|
+
chalk.gray(`from ${ip}`));
|
|
243
234
|
}
|
|
244
235
|
|
|
245
236
|
next();
|
|
246
237
|
});
|
|
247
238
|
|
|
239
|
+
// Add request history tracking
|
|
240
|
+
const historyMiddleware = require('./middleware/history')();
|
|
241
|
+
app.use(historyMiddleware);
|
|
242
|
+
GibRuns.getRequestHistory = require('./middleware/history').getHistory;
|
|
243
|
+
|
|
248
244
|
// Add health check endpoint
|
|
249
245
|
if (enableHealth) {
|
|
250
246
|
app.use(require('./middleware/health')(GibRuns));
|
|
@@ -377,25 +373,26 @@ GibRuns.start = function(options) {
|
|
|
377
373
|
|
|
378
374
|
// Handle server startup errors
|
|
379
375
|
server.addListener('error', function(e) {
|
|
376
|
+
const timestamp = new Date().toISOString();
|
|
380
377
|
if (e.code === 'EADDRINUSE') {
|
|
381
378
|
var serveURL = protocol + '://' + host + ':' + port;
|
|
382
|
-
console.log(chalk.yellow(
|
|
383
|
-
if (GibRuns.logLevel >=
|
|
384
|
-
console.log(chalk.gray(
|
|
379
|
+
console.log(chalk.yellow(` ⚠ [${timestamp}] ${serveURL} is already in use. Trying another port...`));
|
|
380
|
+
if (GibRuns.logLevel >= 2) {
|
|
381
|
+
console.log(chalk.gray(` 💡 Port ${port} is occupied, searching for available port...`));
|
|
385
382
|
}
|
|
386
383
|
setTimeout(function() {
|
|
387
384
|
server.listen(0, host);
|
|
388
385
|
}, 1000);
|
|
389
386
|
} else {
|
|
390
|
-
console.error(chalk.red(
|
|
391
|
-
if (GibRuns.logLevel >=
|
|
387
|
+
console.error(chalk.red(` ✖ [${timestamp}] Server Error: ${e.toString()}`));
|
|
388
|
+
if (GibRuns.logLevel >= 2) {
|
|
392
389
|
console.error(chalk.gray(' Stack trace:'), e.stack);
|
|
393
390
|
}
|
|
394
391
|
|
|
395
392
|
// Auto-restart on crash if enabled
|
|
396
393
|
if (autoRestart && GibRuns.restartCount < 5) {
|
|
397
394
|
GibRuns.restartCount++;
|
|
398
|
-
console.log(chalk.yellow(
|
|
395
|
+
console.log(chalk.yellow(` 🔄 [${timestamp}] Auto-restarting server (attempt ${GibRuns.restartCount}/5)...`));
|
|
399
396
|
setTimeout(function() {
|
|
400
397
|
GibRuns.start(options);
|
|
401
398
|
}, 2000);
|
|
@@ -647,26 +644,48 @@ GibRuns.start = function(options) {
|
|
|
647
644
|
});
|
|
648
645
|
function handleChange(changePath) {
|
|
649
646
|
GibRuns.reloadCount++;
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
647
|
+
const cssChange = path.extname(changePath) === ".css" && !noCssInject;
|
|
648
|
+
const relPath = path.relative(root, changePath);
|
|
649
|
+
const timestamp = new Date().toISOString();
|
|
650
|
+
const fileSize = fs.existsSync(changePath) ? fs.statSync(changePath).size : 0;
|
|
653
651
|
|
|
654
652
|
if (GibRuns.logLevel >= 1) {
|
|
655
653
|
if (cssChange) {
|
|
656
|
-
console.log(chalk.magenta(
|
|
654
|
+
console.log(chalk.magenta(` ⚡ [${timestamp}] CSS updated: `) + chalk.gray(relPath) +
|
|
655
|
+
chalk.dim(` (${(fileSize / 1024).toFixed(2)}KB)`));
|
|
657
656
|
} else {
|
|
658
|
-
console.log(chalk.cyan(
|
|
657
|
+
console.log(chalk.cyan(` 🔄 [${timestamp}] File changed: `) + chalk.gray(relPath) +
|
|
658
|
+
chalk.dim(` (${(fileSize / 1024).toFixed(2)}KB)`));
|
|
659
659
|
}
|
|
660
660
|
}
|
|
661
|
+
|
|
661
662
|
clients.forEach(function(ws) {
|
|
662
|
-
if (ws)
|
|
663
|
-
ws.send(cssChange ? 'refreshcss' : 'reload');
|
|
663
|
+
if (ws) ws.send(cssChange ? 'refreshcss' : 'reload');
|
|
664
664
|
});
|
|
665
665
|
}
|
|
666
|
+
|
|
667
|
+
function handleAdd(addPath) {
|
|
668
|
+
const relPath = path.relative(root, addPath);
|
|
669
|
+
const timestamp = new Date().toISOString();
|
|
670
|
+
if (GibRuns.logLevel >= 2) {
|
|
671
|
+
console.log(chalk.green(` ➕ [${timestamp}] File added: `) + chalk.gray(relPath));
|
|
672
|
+
}
|
|
673
|
+
handleChange(addPath);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
function handleUnlink(unlinkPath) {
|
|
677
|
+
const relPath = path.relative(root, unlinkPath);
|
|
678
|
+
const timestamp = new Date().toISOString();
|
|
679
|
+
if (GibRuns.logLevel >= 2) {
|
|
680
|
+
console.log(chalk.red(` ➖ [${timestamp}] File deleted: `) + chalk.gray(relPath));
|
|
681
|
+
}
|
|
682
|
+
handleChange(unlinkPath);
|
|
683
|
+
}
|
|
684
|
+
|
|
666
685
|
GibRuns.watcher
|
|
667
686
|
.on("change", handleChange)
|
|
668
|
-
.on("add",
|
|
669
|
-
.on("unlink",
|
|
687
|
+
.on("add", handleAdd)
|
|
688
|
+
.on("unlink", handleUnlink)
|
|
670
689
|
.on("addDir", handleChange)
|
|
671
690
|
.on("unlinkDir", handleChange)
|
|
672
691
|
.on("ready", function () {
|
|
@@ -674,7 +693,11 @@ GibRuns.start = function(options) {
|
|
|
674
693
|
console.log(chalk.cyan(" ✓ Watching for file changes...\n"));
|
|
675
694
|
})
|
|
676
695
|
.on("error", function (err) {
|
|
677
|
-
|
|
696
|
+
const timestamp = new Date().toISOString();
|
|
697
|
+
console.log(chalk.red(` ✖ [${timestamp}] Watcher Error: `) + err.message);
|
|
698
|
+
if (GibRuns.logLevel >= 2) {
|
|
699
|
+
console.error(err.stack);
|
|
700
|
+
}
|
|
678
701
|
});
|
|
679
702
|
|
|
680
703
|
return server;
|