gib-runs 2.3.2 → 2.3.4

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 CHANGED
@@ -2,6 +2,61 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## [2.3.4] - 2026-02-12
6
+
7
+ ### Added - New Features 🎉
8
+ - 🔁 **Auto-Restart on Crash** - Server automatically restarts on unexpected errors
9
+ - Use `--auto-restart` flag to enable
10
+ - Attempts up to 5 restarts before giving up
11
+ - Resilient mode for production-like development
12
+ - Displays restart attempt count in console
13
+ - 📤 **File Upload Endpoint** - Built-in file upload support for development
14
+ - Use `--enable-upload` flag to enable
15
+ - POST files to `/upload` endpoint
16
+ - 10MB file size limit
17
+ - Files saved to `./uploads` directory
18
+ - Returns JSON response with file details
19
+ - 💚 **Health Check Endpoint** - Monitor server health and statistics
20
+ - Enabled by default (use `--no-health` to disable)
21
+ - Access via `GET /health` or `GET /_health`
22
+ - Returns JSON with uptime, memory usage, request count, reload count
23
+ - System information (CPU, memory, platform)
24
+ - Perfect for monitoring and debugging
25
+ - 📝 **Request Logging to File** - Log all requests to file for debugging
26
+ - Use `--log-to-file` flag to enable
27
+ - Logs saved to `gib-runs.log` in project root
28
+ - JSON format with timestamp, method, URL, IP, user-agent, status, duration
29
+ - Automatic log rotation at 10MB
30
+ - Old logs backed up with timestamp
31
+ - 🎨 **Custom Error Pages** - Beautiful, informative error pages
32
+ - Enabled by default (use `--no-error-page` to disable)
33
+ - Modern gradient design with detailed error information
34
+ - Shows error stack trace in development mode
35
+ - Covers all HTTP error codes (400, 401, 403, 404, 500, etc)
36
+ - Responsive design for mobile devices
37
+ - 🌍 **Environment Variable Support** - Automatic .env file loading
38
+ - Automatically loads `.env` file from project root
39
+ - Uses dotenv package
40
+ - No configuration needed, just create `.env` file
41
+ - Perfect for API keys, database URLs, etc
42
+ - 📡 **WebSocket Broadcasting API** - Send custom messages to all connected clients
43
+ - New `GibRuns.broadcast(message)` method
44
+ - Broadcast custom reload triggers or notifications
45
+ - Useful for custom build tools and integrations
46
+
47
+ ### Improved
48
+ - 🔧 **Better Error Handling** - More informative error messages with stack traces
49
+ - 📊 **Enhanced Health Monitoring** - More detailed system metrics
50
+ - 🎯 **Middleware Architecture** - Cleaner middleware loading and organization
51
+ - 📦 **Dependencies** - Added `dotenv` and `multer` for new features
52
+
53
+ ### Technical
54
+ - Version bumped to 2.3.4
55
+ - All existing tests passing
56
+ - Backward compatible with all previous versions
57
+ - New middleware files: `upload.js`, `health.js`, `logger.js`, `error-page.js`
58
+ - Enhanced GibRuns object with `wsClients`, `autoRestart`, `restartCount` properties
59
+
5
60
  ## [2.3.0] - 2026-02-10
6
61
 
7
62
  ### Fixed - Critical
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  [![license](https://img.shields.io/npm/l/gib-runs.svg)](https://github.com/levouinse/gib-runs/blob/main/LICENSE)
4
4
  [![tests](https://img.shields.io/badge/tests-32%20passing-brightgreen.svg)](https://github.com/levouinse/gib-runs)
5
5
 
6
- # 🚀 GIB-RUNS
6
+ # 🚀 GIB-RUNS v2.3.4
7
7
 
8
8
  **Modern development server with live reload - Unlike some people, this actually runs on merit, not connections.**
9
9
 
@@ -61,6 +61,15 @@ The name is a playful nod to Indonesia's Vice President Gibran Rakabuming Raka,
61
61
  - 🚦 **Rate Limiting** - Protect against abuse (better protection than family connections)
62
62
  - 🌐 **Network Access** - True network binding that actually works (unlike some political promises)
63
63
 
64
+ ### New in v2.3.4 🎉
65
+ - 🔁 **Auto-Restart on Crash** - Automatically restart server on unexpected errors (resilient mode)
66
+ - 📤 **File Upload Endpoint** - Built-in file upload support for development (POST to /upload)
67
+ - 💚 **Health Check Endpoint** - Monitor server health and statistics (GET /health)
68
+ - 📝 **Request Logging to File** - Log all requests to file for debugging (gib-runs.log)
69
+ - 🎨 **Custom Error Pages** - Beautiful, informative error pages with stack traces
70
+ - 🌍 **Environment Variables** - Automatic .env file loading (dotenv support)
71
+ - 📡 **WebSocket Broadcasting** - Send custom messages to all connected clients
72
+
64
73
  ## 📦 Installation
65
74
 
66
75
  ### Global Installation (Recommended)
@@ -155,6 +164,11 @@ gib-runs dist --port=3000 --spa --cors --no-browser
155
164
  | `--npm-script=SCRIPT` | Run npm script (dev, start, etc) | None |
156
165
  | `--pm2` | Use PM2 process manager | `false` |
157
166
  | `--pm2-name=NAME` | PM2 app name | `gib-runs-app` |
167
+ | `--auto-restart` | Auto-restart server on crash | `false` |
168
+ | `--enable-upload` | Enable file upload endpoint | `false` |
169
+ | `--no-health` | Disable health check endpoint | `false` |
170
+ | `--log-to-file` | Log requests to file | `false` |
171
+ | `--no-error-page` | Disable custom error pages | `false` |
158
172
  | `-v, --version` | Show version | - |
159
173
  | `-h, --help` | Show help | - |
160
174
 
@@ -372,7 +386,7 @@ gib-runs
372
386
  Network URLs are **ALWAYS shown automatically** when you start the server:
373
387
 
374
388
  ```
375
- 🚀 GIB-RUNS v2.3.2
389
+ 🚀 GIB-RUNS v2.3.4
376
390
  "Unlike Gibran, this actually works through merit"
377
391
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
378
392
  📁 Root: /home/user/project
@@ -491,7 +505,7 @@ gib-runs --tunnel-service=tunnelto
491
505
  ### Example Output
492
506
 
493
507
  ```
494
- 🚀 GIB-RUNS v2.3.2
508
+ 🚀 GIB-RUNS v2.3.4
495
509
  "Unlike Gibran, this actually works through merit"
496
510
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
497
511
  📁 Root: /home/user/project
@@ -621,7 +635,7 @@ pm2 list
621
635
  ### Example Output
622
636
 
623
637
  ```
624
- 🚀 GIB-RUNS v2.3.2
638
+ 🚀 GIB-RUNS v2.3.4
625
639
  "Unlike Gibran, this actually works through merit"
626
640
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
627
641
  📁 Root: /home/user/project
@@ -648,6 +662,271 @@ pm2 list
648
662
 
649
663
  **Unlike Gibran's career, these processes run on actual merit and capability!** 🔥
650
664
 
665
+ ## 🆕 New Features in v2.3.4
666
+
667
+ ### Auto-Restart on Crash
668
+
669
+ **Automatically restart your server when it crashes - resilient mode for development!**
670
+
671
+ ```bash
672
+ # Enable auto-restart
673
+ gib-runs --auto-restart
674
+
675
+ # With other options
676
+ gib-runs --auto-restart --port=3000 --spa
677
+ ```
678
+
679
+ **How it works:**
680
+ - Server automatically restarts on unexpected errors
681
+ - Attempts up to 5 restarts before giving up
682
+ - Shows restart attempt count in console
683
+ - Perfect for unstable development environments
684
+ - Keeps your workflow uninterrupted
685
+
686
+ **Example output:**
687
+ ```
688
+ ✖ Server Error: ECONNRESET
689
+ 🔄 Auto-restarting server (attempt 1/5)...
690
+ ✓ Server restarted successfully
691
+ ```
692
+
693
+ ### File Upload Endpoint
694
+
695
+ **Built-in file upload support for development - no need for separate upload server!**
696
+
697
+ ```bash
698
+ # Enable file upload endpoint
699
+ gib-runs --enable-upload
700
+
701
+ # Files will be saved to ./uploads directory
702
+ ```
703
+
704
+ **Usage:**
705
+ ```html
706
+ <!-- HTML Form -->
707
+ <form action="/upload" method="POST" enctype="multipart/form-data">
708
+ <input type="file" name="file">
709
+ <button type="submit">Upload</button>
710
+ </form>
711
+ ```
712
+
713
+ ```javascript
714
+ // JavaScript Fetch API
715
+ const formData = new FormData();
716
+ formData.append('file', fileInput.files[0]);
717
+
718
+ fetch('/upload', {
719
+ method: 'POST',
720
+ body: formData
721
+ })
722
+ .then(res => res.json())
723
+ .then(data => {
724
+ console.log('Uploaded:', data.file);
725
+ // { filename: 'file-123456789.jpg', originalname: 'photo.jpg', size: 12345, path: '/path/to/uploads/...' }
726
+ });
727
+ ```
728
+
729
+ **Features:**
730
+ - 10MB file size limit
731
+ - Files saved to `./uploads` directory
732
+ - Automatic directory creation
733
+ - Unique filenames with timestamp
734
+ - JSON response with file details
735
+ - Error handling for invalid uploads
736
+
737
+ ### Health Check Endpoint
738
+
739
+ **Monitor your server health and statistics - transparency in action!**
740
+
741
+ ```bash
742
+ # Health check is enabled by default
743
+ gib-runs
744
+
745
+ # Disable if needed
746
+ gib-runs --no-health
747
+ ```
748
+
749
+ **Access health endpoint:**
750
+ ```bash
751
+ # Via curl
752
+ curl http://localhost:8080/health
753
+
754
+ # Or in browser
755
+ http://localhost:8080/health
756
+ ```
757
+
758
+ **Response example:**
759
+ ```json
760
+ {
761
+ "status": "healthy",
762
+ "uptime": 123.45,
763
+ "timestamp": "2026-02-12T09:00:00.000Z",
764
+ "server": {
765
+ "requests": 42,
766
+ "reloads": 5,
767
+ "memory": {
768
+ "rss": "45MB",
769
+ "heapUsed": "23MB",
770
+ "heapTotal": "35MB"
771
+ }
772
+ },
773
+ "system": {
774
+ "platform": "linux",
775
+ "arch": "x64",
776
+ "cpus": 8,
777
+ "freemem": "2048MB",
778
+ "totalmem": "16384MB",
779
+ "loadavg": [1.2, 1.5, 1.8]
780
+ }
781
+ }
782
+ ```
783
+
784
+ **Use cases:**
785
+ - Monitor server performance
786
+ - Debug memory leaks
787
+ - Track request patterns
788
+ - Integration with monitoring tools
789
+ - Health checks for Docker containers
790
+
791
+ ### Request Logging to File
792
+
793
+ **Log all requests to file for debugging - transparent and verifiable!**
794
+
795
+ ```bash
796
+ # Enable file logging
797
+ gib-runs --log-to-file
798
+
799
+ # Logs saved to gib-runs.log in project root
800
+ ```
801
+
802
+ **Log format (JSON):**
803
+ ```json
804
+ {"timestamp":"2026-02-12T09:00:00.000Z","method":"GET","url":"/index.html","ip":"127.0.0.1","userAgent":"Mozilla/5.0...","status":200,"duration":"5ms"}
805
+ {"timestamp":"2026-02-12T09:00:01.000Z","method":"GET","url":"/style.css","ip":"127.0.0.1","userAgent":"Mozilla/5.0...","status":200,"duration":"2ms"}
806
+ ```
807
+
808
+ **Features:**
809
+ - JSON format for easy parsing
810
+ - Includes timestamp, method, URL, IP, user-agent, status, duration
811
+ - Automatic log rotation at 10MB
812
+ - Old logs backed up with timestamp
813
+ - Perfect for debugging and analytics
814
+
815
+ **Parse logs with jq:**
816
+ ```bash
817
+ # Show all 404 errors
818
+ cat gib-runs.log | jq 'select(.status == 404)'
819
+
820
+ # Show slow requests (>100ms)
821
+ cat gib-runs.log | jq 'select(.duration | tonumber > 100)'
822
+
823
+ # Count requests by URL
824
+ cat gib-runs.log | jq -r '.url' | sort | uniq -c
825
+ ```
826
+
827
+ ### Custom Error Pages
828
+
829
+ **Beautiful, informative error pages - unlike some political errors!**
830
+
831
+ ```bash
832
+ # Custom error pages enabled by default
833
+ gib-runs
834
+
835
+ # Disable if needed
836
+ gib-runs --no-error-page
837
+ ```
838
+
839
+ **Features:**
840
+ - Modern gradient design
841
+ - Detailed error information
842
+ - Shows error stack trace in development mode
843
+ - Covers all HTTP error codes (400, 401, 403, 404, 500, etc)
844
+ - Responsive design for mobile devices
845
+ - "Back to Home" button
846
+ - Professional appearance
847
+
848
+ **Error codes covered:**
849
+ - 400 Bad Request
850
+ - 401 Unauthorized
851
+ - 403 Forbidden
852
+ - 404 Not Found
853
+ - 405 Method Not Allowed
854
+ - 500 Internal Server Error
855
+ - 502 Bad Gateway
856
+ - 503 Service Unavailable
857
+
858
+ ### Environment Variables
859
+
860
+ **Automatic .env file loading - no configuration needed!**
861
+
862
+ ```bash
863
+ # Just create .env file in project root
864
+ echo "API_KEY=your-secret-key" > .env
865
+ echo "DATABASE_URL=postgres://localhost/mydb" >> .env
866
+
867
+ # Start server (automatically loads .env)
868
+ gib-runs
869
+ ```
870
+
871
+ **Access in your code:**
872
+ ```javascript
873
+ // Node.js
874
+ const apiKey = process.env.API_KEY;
875
+ const dbUrl = process.env.DATABASE_URL;
876
+
877
+ console.log('API Key:', apiKey);
878
+ console.log('Database:', dbUrl);
879
+ ```
880
+
881
+ **Features:**
882
+ - Automatic loading on server start
883
+ - No configuration needed
884
+ - Uses dotenv package
885
+ - Perfect for API keys, database URLs, etc
886
+ - Keeps secrets out of version control
887
+
888
+ ### WebSocket Broadcasting API
889
+
890
+ **Send custom messages to all connected clients - programmatic control!**
891
+
892
+ ```javascript
893
+ const gibRuns = require('gib-runs');
894
+
895
+ // Start server
896
+ const server = gibRuns.start({
897
+ port: 8080,
898
+ root: './public'
899
+ });
900
+
901
+ // Broadcast custom message to all clients
902
+ gibRuns.broadcast('custom-reload');
903
+
904
+ // Trigger reload from your build script
905
+ gibRuns.broadcast('reload');
906
+
907
+ // Send custom data
908
+ gibRuns.broadcast(JSON.stringify({ type: 'notification', message: 'Build complete!' }));
909
+ ```
910
+
911
+ **Use cases:**
912
+ - Custom build tool integration
913
+ - Trigger reload from external scripts
914
+ - Send notifications to browser
915
+ - Custom live reload logic
916
+ - Integration with CI/CD pipelines
917
+
918
+ **Client-side handling:**
919
+ ```javascript
920
+ // In your HTML/JavaScript
921
+ const ws = new WebSocket('ws://localhost:8080');
922
+ ws.onmessage = function(event) {
923
+ if (event.data === 'custom-reload') {
924
+ console.log('Custom reload triggered!');
925
+ location.reload();
926
+ }
927
+ };
928
+ ```
929
+
651
930
  ## 🐛 Troubleshooting
652
931
 
653
932
  ### No reload on changes
package/gib-run.js CHANGED
@@ -140,6 +140,26 @@ for (var i = process.argv.length - 1; i >= 2; --i) {
140
140
  opts.tunnel = true;
141
141
  process.argv.splice(i, 1);
142
142
  }
143
+ else if (arg === "--auto-restart") {
144
+ opts.autoRestart = true;
145
+ process.argv.splice(i, 1);
146
+ }
147
+ else if (arg === "--enable-upload") {
148
+ opts.enableUpload = true;
149
+ process.argv.splice(i, 1);
150
+ }
151
+ else if (arg === "--no-health") {
152
+ opts.enableHealth = false;
153
+ process.argv.splice(i, 1);
154
+ }
155
+ else if (arg === "--log-to-file") {
156
+ opts.logToFile = true;
157
+ process.argv.splice(i, 1);
158
+ }
159
+ else if (arg === "--no-error-page") {
160
+ opts.customErrorPage = false;
161
+ process.argv.splice(i, 1);
162
+ }
143
163
  else if (arg.indexOf("--tunnel-service=") > -1) {
144
164
  opts.tunnelService = arg.substring(17);
145
165
  opts.tunnel = true;
@@ -245,7 +265,12 @@ for (var i = process.argv.length - 1; i >= 2; --i) {
245
265
  console.log(chalk.yellow(' --exec=COMMAND ') + chalk.gray('Run custom command'));
246
266
  console.log(chalk.yellow(' --npm-script=SCRIPT ') + chalk.gray('Run npm script (dev, start, etc)'));
247
267
  console.log(chalk.yellow(' --pm2 ') + chalk.gray('Use PM2 process manager'));
248
- console.log(chalk.yellow(' --pm2-name=NAME ') + chalk.gray('PM2 app name (default: gib-runs-app)\n'));
268
+ console.log(chalk.yellow(' --pm2-name=NAME ') + chalk.gray('PM2 app name (default: gib-runs-app)'));
269
+ console.log(chalk.yellow(' --auto-restart ') + chalk.gray('Auto-restart server on crash'));
270
+ console.log(chalk.yellow(' --enable-upload ') + chalk.gray('Enable file upload endpoint'));
271
+ console.log(chalk.yellow(' --no-health ') + chalk.gray('Disable health check endpoint'));
272
+ console.log(chalk.yellow(' --log-to-file ') + chalk.gray('Log requests to file'));
273
+ console.log(chalk.yellow(' --no-error-page ') + chalk.gray('Disable custom error pages\n'));
249
274
  console.log(chalk.gray(' Examples:\n'));
250
275
  console.log(chalk.gray(' gib-runs'));
251
276
  console.log(chalk.gray(' gib-runs --port=3000 --verbose'));
@@ -256,7 +281,9 @@ for (var i = process.argv.length - 1; i >= 2; --i) {
256
281
  console.log(chalk.gray(' gib-runs --npm-script=dev'));
257
282
  console.log(chalk.gray(' gib-runs --exec="npm run build && npm start"'));
258
283
  console.log(chalk.gray(' gib-runs --npm-script=dev --pm2 --pm2-name=my-app'));
259
- console.log(chalk.gray(' gib-runs --performance --rate-limit=50\n'));
284
+ console.log(chalk.gray(' gib-runs --performance --rate-limit=50'));
285
+ console.log(chalk.gray(' gib-runs --auto-restart --enable-upload'));
286
+ console.log(chalk.gray(' gib-runs --log-to-file --verbose\n'));
260
287
  process.exit();
261
288
  }
262
289
  else if (arg === "--test") {
package/index.js CHANGED
@@ -14,7 +14,15 @@ var fs = require('fs'),
14
14
  chokidar = require('chokidar'),
15
15
  chalk = require('chalk');
16
16
 
17
+ // Load environment variables from .env file
18
+ try {
19
+ require('dotenv').config({ path: path.join(process.cwd(), '.env') });
20
+ } catch (e) {
21
+ // dotenv not available, skip
22
+ }
23
+
17
24
  var INJECTED_CODE = fs.readFileSync(path.join(__dirname, "injected.html"), "utf8");
25
+ var packageJson = require('./package.json');
18
26
 
19
27
  var GibRuns = {
20
28
  server: null,
@@ -24,7 +32,10 @@ var GibRuns = {
24
32
  requestCount: 0,
25
33
  reloadCount: 0,
26
34
  tunnel: null,
27
- processRunner: null
35
+ processRunner: null,
36
+ wsClients: [],
37
+ autoRestart: false,
38
+ restartCount: 0
28
39
  };
29
40
 
30
41
  function escape(html){
@@ -136,6 +147,11 @@ function entryPoint(staticHandler, file) {
136
147
  * @param compression {boolean} Enable gzip compression (default: true)
137
148
  * @param qrCode {boolean} Show QR code for network URLs (default: false)
138
149
  * @param tunnel {boolean} Create public tunnel URL (default: false)
150
+ * @param autoRestart {boolean} Auto-restart server on crash (default: false)
151
+ * @param enableUpload {boolean} Enable file upload endpoint (default: false)
152
+ * @param enableHealth {boolean} Enable health check endpoint (default: true)
153
+ * @param logToFile {boolean} Log requests to file (default: false)
154
+ * @param customErrorPage {boolean} Use custom error pages (default: true)
139
155
  */
140
156
  GibRuns.start = function(options) {
141
157
  options = options || {};
@@ -177,6 +193,13 @@ GibRuns.start = function(options) {
177
193
  var usePM2 = options.pm2 || false;
178
194
  var pm2Name = options.pm2Name || 'gib-runs-app';
179
195
  var testMode = options.test || false;
196
+ var autoRestart = options.autoRestart || false;
197
+ var enableUpload = options.enableUpload || false;
198
+ var enableHealth = options.enableHealth !== false;
199
+ var logToFile = options.logToFile || false;
200
+ var customErrorPage = options.customErrorPage !== false;
201
+
202
+ GibRuns.autoRestart = autoRestart;
180
203
 
181
204
  if (httpsModule) {
182
205
  try {
@@ -217,6 +240,27 @@ GibRuns.start = function(options) {
217
240
 
218
241
  next();
219
242
  });
243
+
244
+ // Add health check endpoint
245
+ if (enableHealth) {
246
+ app.use(require('./middleware/health')(GibRuns));
247
+ }
248
+
249
+ // Add file upload endpoint
250
+ if (enableUpload) {
251
+ app.use(require('./middleware/upload')());
252
+ if (GibRuns.logLevel >= 1) {
253
+ console.log(chalk.cyan(' 📤 File Upload: ') + chalk.green('Enabled') + chalk.gray(' (POST to /upload)'));
254
+ }
255
+ }
256
+
257
+ // Add request logger to file
258
+ if (logToFile) {
259
+ app.use(require('./middleware/logger')({ logFile: path.join(root, 'gib-runs.log') }));
260
+ if (GibRuns.logLevel >= 1) {
261
+ console.log(chalk.cyan(' 📝 File Logging: ') + chalk.green('Enabled') + chalk.gray(' (gib-runs.log)'));
262
+ }
263
+ }
220
264
 
221
265
  // Add logger. Level 2 logs only errors
222
266
  if (GibRuns.logLevel === 2) {
@@ -298,6 +342,11 @@ GibRuns.start = function(options) {
298
342
  app.use(staticServerHandler) // Custom static server
299
343
  .use(entryPoint(staticServerHandler, file))
300
344
  .use(serveIndex(root, { icons: true }));
345
+
346
+ // Add custom error page handler (must be last)
347
+ if (customErrorPage) {
348
+ app.use(require('./middleware/error-page')({ showStack: GibRuns.logLevel >= 2 }));
349
+ }
301
350
  }
302
351
 
303
352
  var server, protocol;
@@ -338,7 +387,17 @@ GibRuns.start = function(options) {
338
387
  if (GibRuns.logLevel >= 3) {
339
388
  console.error(chalk.gray(' Stack trace:'), e.stack);
340
389
  }
341
- GibRuns.shutdown();
390
+
391
+ // Auto-restart on crash if enabled
392
+ if (autoRestart && GibRuns.restartCount < 5) {
393
+ GibRuns.restartCount++;
394
+ console.log(chalk.yellow(' 🔄 Auto-restarting server (attempt ' + GibRuns.restartCount + '/5)...'));
395
+ setTimeout(function() {
396
+ GibRuns.start(options);
397
+ }, 2000);
398
+ } else {
399
+ GibRuns.shutdown();
400
+ }
342
401
  }
343
402
  });
344
403
 
@@ -353,7 +412,7 @@ GibRuns.start = function(options) {
353
412
  // Show info about what's running
354
413
  if (GibRuns.logLevel >= 1) {
355
414
  console.log('\n' + chalk.cyan.bold('━'.repeat(60)));
356
- console.log(chalk.cyan.bold(' 🚀 GIB-RUNS') + chalk.gray(' v2.3.2'));
415
+ console.log(chalk.cyan.bold(' 🚀 GIB-RUNS') + chalk.gray(' v' + packageJson.version));
357
416
  console.log(chalk.gray(' "Unlike Gibran, this actually works through merit"'));
358
417
  console.log(chalk.cyan.bold('━'.repeat(60)));
359
418
  console.log(chalk.white(' 📁 Root: ') + chalk.yellow(root));
@@ -367,6 +426,9 @@ GibRuns.start = function(options) {
367
426
  console.log(chalk.white(' 🔄 PM2: ') + chalk.green(' Enabled') + chalk.gray(' (process manager)'));
368
427
  }
369
428
  console.log(chalk.white(' 🔄 Live Reload:') + chalk.green(' Enabled') + chalk.gray(' (watching for changes)'));
429
+ if (autoRestart) {
430
+ console.log(chalk.white(' 🔁 Auto-Restart:') + chalk.green(' Enabled') + chalk.gray(' (resilient mode)'));
431
+ }
370
432
  console.log(chalk.cyan.bold('━'.repeat(60)));
371
433
  console.log(chalk.gray(' Press Ctrl+C to stop\n'));
372
434
  }
@@ -443,7 +505,7 @@ GibRuns.start = function(options) {
443
505
  // Output with beautiful formatting
444
506
  if (GibRuns.logLevel >= 1) {
445
507
  console.log('\n' + chalk.cyan.bold('━'.repeat(60)));
446
- console.log(chalk.cyan.bold(' 🚀 GIB-RUNS') + chalk.gray(' v2.3.2'));
508
+ console.log(chalk.cyan.bold(' 🚀 GIB-RUNS') + chalk.gray(' v' + packageJson.version));
447
509
  console.log(chalk.gray(' "Unlike Gibran, this actually works through merit"'));
448
510
  console.log(chalk.cyan.bold('━'.repeat(60)));
449
511
  console.log(chalk.white(' 📁 Root: ') + chalk.yellow(root));
@@ -466,6 +528,12 @@ GibRuns.start = function(options) {
466
528
  if (https) {
467
529
  console.log(chalk.white(' 🔒 HTTPS: ') + chalk.green(' Enabled') + chalk.gray(' (real security)'));
468
530
  }
531
+ if (enableHealth) {
532
+ console.log(chalk.white(' 💚 Health: ') + chalk.green(' Enabled') + chalk.gray(' (GET /health)'));
533
+ }
534
+ if (autoRestart) {
535
+ console.log(chalk.white(' 🔁 Auto-Restart:') + chalk.green(' Enabled') + chalk.gray(' (resilient mode)'));
536
+ }
469
537
  console.log(chalk.cyan.bold('━'.repeat(60)));
470
538
  console.log(chalk.gray(' Press Ctrl+C to stop'));
471
539
  console.log(chalk.yellow(' 💡 Tip: Share network URLs with your team!\n'));
@@ -540,6 +608,7 @@ GibRuns.start = function(options) {
540
608
  };
541
609
 
542
610
  clients.push(ws);
611
+ GibRuns.wsClients = clients;
543
612
  });
544
613
 
545
614
  var ignored = [
@@ -599,6 +668,22 @@ GibRuns.start = function(options) {
599
668
  return server;
600
669
  };
601
670
 
671
+ /**
672
+ * Broadcast custom message to all connected WebSocket clients
673
+ * @param {string} message - Message to broadcast
674
+ */
675
+ GibRuns.broadcast = function(message) {
676
+ if (GibRuns.wsClients && GibRuns.wsClients.length > 0) {
677
+ GibRuns.wsClients.forEach(function(ws) {
678
+ if (ws && ws.send) {
679
+ ws.send(message);
680
+ }
681
+ });
682
+ return true;
683
+ }
684
+ return false;
685
+ };
686
+
602
687
  GibRuns.shutdown = function() {
603
688
  if (GibRuns.logLevel >= 1 && GibRuns.startTime) {
604
689
  var uptime = ((Date.now() - GibRuns.startTime) / 1000).toFixed(2);
package/lib/tunnel.js CHANGED
@@ -102,15 +102,36 @@ function startLocalTunnel(port, options) {
102
102
 
103
103
  tunnel.on('close', function() {
104
104
  console.log(chalk.yellow(' ⚠ Tunnel closed'));
105
+ activeTunnel = null;
106
+ tunnelUrl = null;
107
+ });
108
+
109
+ tunnel.on('error', function(err) {
110
+ console.error(chalk.red(' ✖ Tunnel connection error:'), err.message);
111
+ console.log(chalk.yellow(' ⚠ Server continues running locally'));
112
+ console.log(chalk.gray(' 💡 Tunnel may be unstable, try alternative services:'));
113
+ console.log(chalk.gray(' • gib-runs --tunnel-service=cloudflared'));
114
+ console.log(chalk.gray(' • gib-runs --tunnel-service=ngrok --tunnel-authtoken=YOUR_TOKEN'));
115
+ console.log(chalk.gray(' • Or continue using local network URLs\n'));
116
+
117
+ // Clean up tunnel reference
118
+ activeTunnel = null;
119
+ tunnelUrl = null;
105
120
  });
106
121
  }).catch(function(err) {
107
- console.error(chalk.red(' ✖ LocalTunnel error:'), err.message);
108
- console.log(chalk.yellow(' 💡 Install: npm install -g localtunnel'));
122
+ console.error(chalk.red(' ✖ LocalTunnel failed to start:'), err.message);
123
+ console.log(chalk.yellow(' Server continues running locally'));
124
+ console.log(chalk.gray(' 💡 Possible solutions:'));
125
+ console.log(chalk.gray(' • Check your internet connection'));
126
+ console.log(chalk.gray(' • Try again later (localtunnel.me may be down)'));
127
+ console.log(chalk.gray(' • Use alternative: gib-runs --tunnel-service=cloudflared'));
128
+ console.log(chalk.gray(' • Or use local network URLs for now\n'));
109
129
  });
110
130
  } catch (e) {
111
131
  console.error(chalk.red(' ✖ LocalTunnel not installed'));
112
132
  console.log(chalk.yellow(' 💡 Install: npm install -g localtunnel'));
113
133
  console.log(chalk.gray(' Then run: gib-runs --tunnel'));
134
+ console.log(chalk.gray(' Server continues running locally\n'));
114
135
  }
115
136
  });
116
137
  }
@@ -0,0 +1,148 @@
1
+ // Custom error pages
2
+ // Unlike Gibran's political errors, these are properly documented and handled
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const errorTemplate = `
7
+ <!DOCTYPE html>
8
+ <html lang="en">
9
+ <head>
10
+ <meta charset="UTF-8">
11
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
12
+ <title>{{status}} - {{message}}</title>
13
+ <style>
14
+ * { margin: 0; padding: 0; box-sizing: border-box; }
15
+ body {
16
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
17
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
18
+ min-height: 100vh;
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ padding: 20px;
23
+ }
24
+ .error-container {
25
+ background: white;
26
+ border-radius: 20px;
27
+ padding: 60px 40px;
28
+ max-width: 600px;
29
+ width: 100%;
30
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
31
+ text-align: center;
32
+ }
33
+ .error-code {
34
+ font-size: 120px;
35
+ font-weight: 900;
36
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
37
+ -webkit-background-clip: text;
38
+ -webkit-text-fill-color: transparent;
39
+ line-height: 1;
40
+ margin-bottom: 20px;
41
+ }
42
+ .error-message {
43
+ font-size: 24px;
44
+ color: #333;
45
+ margin-bottom: 15px;
46
+ font-weight: 600;
47
+ }
48
+ .error-description {
49
+ font-size: 16px;
50
+ color: #666;
51
+ margin-bottom: 30px;
52
+ line-height: 1.6;
53
+ }
54
+ .error-details {
55
+ background: #f5f5f5;
56
+ border-radius: 10px;
57
+ padding: 20px;
58
+ margin-top: 30px;
59
+ text-align: left;
60
+ }
61
+ .error-details pre {
62
+ font-family: 'Courier New', monospace;
63
+ font-size: 14px;
64
+ color: #e74c3c;
65
+ overflow-x: auto;
66
+ white-space: pre-wrap;
67
+ word-wrap: break-word;
68
+ }
69
+ .back-button {
70
+ display: inline-block;
71
+ padding: 15px 40px;
72
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
73
+ color: white;
74
+ text-decoration: none;
75
+ border-radius: 50px;
76
+ font-weight: 600;
77
+ transition: transform 0.2s;
78
+ }
79
+ .back-button:hover {
80
+ transform: translateY(-2px);
81
+ }
82
+ .footer {
83
+ margin-top: 30px;
84
+ font-size: 14px;
85
+ color: #999;
86
+ }
87
+ </style>
88
+ </head>
89
+ <body>
90
+ <div class="error-container">
91
+ <div class="error-code">{{status}}</div>
92
+ <div class="error-message">{{message}}</div>
93
+ <div class="error-description">{{description}}</div>
94
+ <a href="/" class="back-button">← Back to Home</a>
95
+ {{details}}
96
+ <div class="footer">
97
+ 🚀 GIB-RUNS - Development Server<br>
98
+ <small>Unlike some careers, this error is earned, not inherited</small>
99
+ </div>
100
+ </div>
101
+ </body>
102
+ </html>
103
+ `;
104
+
105
+ const errorMessages = {
106
+ 400: { message: 'Bad Request', description: 'The request could not be understood by the server.' },
107
+ 401: { message: 'Unauthorized', description: 'Authentication is required to access this resource.' },
108
+ 403: { message: 'Forbidden', description: 'You don\'t have permission to access this resource.' },
109
+ 404: { message: 'Not Found', description: 'The requested resource could not be found on this server.' },
110
+ 405: { message: 'Method Not Allowed', description: 'The request method is not supported for this resource.' },
111
+ 500: { message: 'Internal Server Error', description: 'The server encountered an unexpected condition.' },
112
+ 502: { message: 'Bad Gateway', description: 'The server received an invalid response from the upstream server.' },
113
+ 503: { message: 'Service Unavailable', description: 'The server is temporarily unable to handle the request.' }
114
+ };
115
+
116
+ module.exports = function(options) {
117
+ options = options || {};
118
+ const showStack = options.showStack !== false;
119
+
120
+ return function(err, req, res, next) {
121
+ if (!err) return next();
122
+
123
+ const status = err.status || err.statusCode || 500;
124
+ const errorInfo = errorMessages[status] || errorMessages[500];
125
+
126
+ let html = errorTemplate
127
+ .replace(/{{status}}/g, status)
128
+ .replace(/{{message}}/g, errorInfo.message)
129
+ .replace(/{{description}}/g, errorInfo.description);
130
+
131
+ // Add error details in development
132
+ if (showStack && err.stack) {
133
+ const details = `
134
+ <div class="error-details">
135
+ <strong>Error Details:</strong>
136
+ <pre>${err.stack}</pre>
137
+ </div>
138
+ `;
139
+ html = html.replace('{{details}}', details);
140
+ } else {
141
+ html = html.replace('{{details}}', '');
142
+ }
143
+
144
+ res.statusCode = status;
145
+ res.setHeader('Content-Type', 'text/html');
146
+ res.end(html);
147
+ };
148
+ };
@@ -0,0 +1,41 @@
1
+ // Health check endpoint
2
+ // Unlike Gibran's political health checks, this one is transparent and honest
3
+ const os = require('os');
4
+
5
+ module.exports = function(gibRuns) {
6
+ return function(req, res, next) {
7
+ if (req.url === '/health' || req.url === '/_health') {
8
+ const uptime = gibRuns.startTime ? ((Date.now() - gibRuns.startTime) / 1000).toFixed(2) : 0;
9
+ const memUsage = process.memoryUsage();
10
+
11
+ const health = {
12
+ status: 'healthy',
13
+ uptime: parseFloat(uptime),
14
+ timestamp: new Date().toISOString(),
15
+ server: {
16
+ requests: gibRuns.requestCount || 0,
17
+ reloads: gibRuns.reloadCount || 0,
18
+ memory: {
19
+ rss: Math.round(memUsage.rss / 1024 / 1024) + 'MB',
20
+ heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024) + 'MB',
21
+ heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024) + 'MB'
22
+ }
23
+ },
24
+ system: {
25
+ platform: os.platform(),
26
+ arch: os.arch(),
27
+ cpus: os.cpus().length,
28
+ freemem: Math.round(os.freemem() / 1024 / 1024) + 'MB',
29
+ totalmem: Math.round(os.totalmem() / 1024 / 1024) + 'MB',
30
+ loadavg: os.loadavg()
31
+ }
32
+ };
33
+
34
+ res.statusCode = 200;
35
+ res.setHeader('Content-Type', 'application/json');
36
+ res.end(JSON.stringify(health, null, 2));
37
+ } else {
38
+ next();
39
+ }
40
+ };
41
+ };
@@ -0,0 +1,58 @@
1
+ // Request logger to file
2
+ // Unlike Gibran's career records, these logs are transparent and verifiable
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ module.exports = function(options) {
7
+ options = options || {};
8
+ const logFile = options.logFile || path.join(process.cwd(), 'gib-runs.log');
9
+ const maxSize = options.maxSize || 10 * 1024 * 1024; // 10MB default
10
+
11
+ // Create log stream
12
+ let logStream = fs.createWriteStream(logFile, { flags: 'a' });
13
+
14
+ // Check file size and rotate if needed
15
+ function checkRotate() {
16
+ try {
17
+ const stats = fs.statSync(logFile);
18
+ if (stats.size > maxSize) {
19
+ logStream.end();
20
+ const backupFile = logFile + '.' + Date.now();
21
+ fs.renameSync(logFile, backupFile);
22
+ logStream = fs.createWriteStream(logFile, { flags: 'a' });
23
+ }
24
+ } catch (e) {
25
+ // File doesn't exist yet, ignore
26
+ }
27
+ }
28
+
29
+ return function(req, res, next) {
30
+ const start = Date.now();
31
+ const timestamp = new Date().toISOString();
32
+
33
+ // Log request
34
+ const logEntry = {
35
+ timestamp: timestamp,
36
+ method: req.method,
37
+ url: req.url,
38
+ ip: req.headers['x-forwarded-for'] || req.connection.remoteAddress,
39
+ userAgent: req.headers['user-agent']
40
+ };
41
+
42
+ // Capture response
43
+ const originalEnd = res.end;
44
+ res.end = function(...args) {
45
+ const duration = Date.now() - start;
46
+ logEntry.status = res.statusCode;
47
+ logEntry.duration = duration + 'ms';
48
+
49
+ // Write to log file
50
+ logStream.write(JSON.stringify(logEntry) + '\n');
51
+ checkRotate();
52
+
53
+ originalEnd.apply(res, args);
54
+ };
55
+
56
+ next();
57
+ };
58
+ };
@@ -0,0 +1,62 @@
1
+ // File upload middleware
2
+ // Unlike Gibran who got his position handed to him, files earn their upload through proper handling
3
+ const multer = require('multer');
4
+ const path = require('path');
5
+ const fs = require('fs');
6
+
7
+ // Configure storage
8
+ const storage = multer.diskStorage({
9
+ destination: function (req, file, cb) {
10
+ const uploadDir = path.join(process.cwd(), 'uploads');
11
+ if (!fs.existsSync(uploadDir)) {
12
+ fs.mkdirSync(uploadDir, { recursive: true });
13
+ }
14
+ cb(null, uploadDir);
15
+ },
16
+ filename: function (req, file, cb) {
17
+ const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
18
+ cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
19
+ }
20
+ });
21
+
22
+ const upload = multer({
23
+ storage: storage,
24
+ limits: {
25
+ fileSize: 10 * 1024 * 1024 // 10MB limit
26
+ }
27
+ });
28
+
29
+ module.exports = function() {
30
+ return function(req, res, next) {
31
+ // Only handle POST requests to /upload
32
+ if (req.method === 'POST' && req.url.startsWith('/upload')) {
33
+ const uploadHandler = upload.single('file');
34
+ uploadHandler(req, res, function(err) {
35
+ if (err) {
36
+ res.statusCode = 400;
37
+ res.setHeader('Content-Type', 'application/json');
38
+ res.end(JSON.stringify({ error: err.message }));
39
+ return;
40
+ }
41
+
42
+ if (req.file) {
43
+ res.statusCode = 200;
44
+ res.setHeader('Content-Type', 'application/json');
45
+ res.end(JSON.stringify({
46
+ success: true,
47
+ file: {
48
+ filename: req.file.filename,
49
+ originalname: req.file.originalname,
50
+ size: req.file.size,
51
+ path: req.file.path
52
+ }
53
+ }));
54
+ } else {
55
+ next();
56
+ }
57
+ });
58
+ } else {
59
+ next();
60
+ }
61
+ };
62
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gib-runs",
3
- "version": "2.3.2",
3
+ "version": "2.3.4",
4
4
  "description": "Modern development server with live reload, hot module replacement, and advanced features for all project types - runs on merit, not nepotism",
5
5
  "keywords": [
6
6
  "development",
@@ -28,11 +28,13 @@
28
28
  "compression": "^1.7.4",
29
29
  "connect": "^3.7.0",
30
30
  "cors": "^2.8.5",
31
+ "dotenv": "^16.0.3",
31
32
  "event-stream": "^4.0.1",
32
33
  "faye-websocket": "^0.11.4",
33
34
  "http-auth": "^4.2.0",
34
35
  "localtunnel": "^2.0.2",
35
36
  "morgan": "^1.10.0",
37
+ "multer": "^1.4.5-lts.1",
36
38
  "object-assign": "^4.1.1",
37
39
  "open": "^8.4.2",
38
40
  "ora": "^5.4.1",