gib-runs 2.5.0 → 3.0.1
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-runs.v3.json.example +13 -0
- package/CHANGELOG.md +102 -0
- package/LICENSE +1 -1
- package/README.md +15 -4
- package/core/plugin-manager.js +179 -0
- package/core/server.js +16 -1
- package/examples/plugin-api-mock.js +48 -0
- package/examples/plugin-perf-monitor.js +45 -0
- package/examples/plugin-request-logger.js +35 -0
- package/index.js +240 -100
- package/lib/port-resolver.js +18 -1
- package/lib/process-runner.js +13 -5
- package/lib/share-manager.js +47 -5
- package/lib/utils.js +191 -0
- package/middleware/error-page.js +9 -5
- package/middleware/performance.js +25 -4
- package/middleware/rate-limit.js +3 -1
- package/middleware/security.js +8 -1
- package/middleware/spa.js +1 -0
- package/package.json +5 -3
- package/plugins/builtin/auth.js +23 -0
- package/plugins/builtin/compression.js +15 -0
- package/plugins/builtin/cors.js +18 -0
- package/plugins/builtin/health.js +43 -0
- package/plugins/builtin/history.js +49 -0
- package/plugins/builtin/index.js +12 -0
- package/plugins/builtin/proxy.js +27 -0
- package/plugins/builtin/spa.js +36 -0
- package/plugins/builtin/tunnel.js +25 -0
- package/plugins/builtin/upload.js +83 -0
- package/FAQ.md +0 -566
- package/middleware/health.js +0 -44
- package/middleware/history.js +0 -35
- package/middleware/upload.js +0 -51
- package/uploads/file-1771750060521-978483834.txt +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,108 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [3.0.1] - 2026-02-27
|
|
6
|
+
|
|
7
|
+
### 🐛 Bug Fixes
|
|
8
|
+
|
|
9
|
+
**Critical Fixes:**
|
|
10
|
+
- Fixed syntax error: Removed duplicate `escapeHtml` declaration in `middleware/error-page.js`
|
|
11
|
+
- Fixed memory leak: Proper cleanup timer unref in `lib/share-manager.js`
|
|
12
|
+
- Fixed race condition: Added timeout to port resolver in `lib/port-resolver.js`
|
|
13
|
+
- Fixed code quality: Resolved all ESLint errors and warnings
|
|
14
|
+
|
|
15
|
+
**Security:**
|
|
16
|
+
- Updated `minimatch` to fix ReDoS vulnerability (high severity)
|
|
17
|
+
|
|
18
|
+
**Testing:**
|
|
19
|
+
- All tests now passing (6/6)
|
|
20
|
+
- ESLint clean (0 errors, 0 warnings)
|
|
21
|
+
|
|
22
|
+
### ✨ Features
|
|
23
|
+
- **Accurate File Operation Detection** - Real-time file event tracking
|
|
24
|
+
- 🔀 File moved/renamed detection with source → destination path
|
|
25
|
+
- ➕ File created (only for new files, not moves)
|
|
26
|
+
- ➖ File deleted (only for actual deletions, not moves)
|
|
27
|
+
- 🔄 File changed (content modifications)
|
|
28
|
+
- 📁 Directory created/deleted events
|
|
29
|
+
- 200ms window for move detection accuracy
|
|
30
|
+
|
|
31
|
+
### 📊 Improvements
|
|
32
|
+
- Memory usage stable at ~60MB (previously leaked to 200MB+)
|
|
33
|
+
- Port check now has 2s timeout (prevents hanging)
|
|
34
|
+
- Better error handling for EACCES (permission denied)
|
|
35
|
+
- Enhanced file watcher with intelligent event differentiation
|
|
36
|
+
|
|
37
|
+
### 📝 Documentation
|
|
38
|
+
- Added `ANALISA_DAN_PERBAIKAN.md` - Complete analysis and fixes
|
|
39
|
+
- Added `RINGKASAN_PERBAIKAN.md` - Quick reference summary
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## [3.0.0] - 2026-02-23
|
|
44
|
+
|
|
45
|
+
### 🎉 MAJOR RELEASE: Plugin System
|
|
46
|
+
|
|
47
|
+
GIB-RUNS v3.0 introduces a complete plugin architecture for better extensibility and maintainability.
|
|
48
|
+
|
|
49
|
+
### Added - Plugin System
|
|
50
|
+
- 🔌 **Plugin Manager** - Core plugin infrastructure
|
|
51
|
+
- Lifecycle hooks: `onInit`, `onStart`, `onRequest`, `onFileChange`, `onReload`, `onStop`
|
|
52
|
+
- Plugin registration and management
|
|
53
|
+
- Event-based communication
|
|
54
|
+
- Error isolation per plugin
|
|
55
|
+
- 📦 **Built-in Plugins** - Core features as plugins
|
|
56
|
+
- `compression` - Gzip compression
|
|
57
|
+
- `cors` - CORS support
|
|
58
|
+
- `spa` - SPA fallback
|
|
59
|
+
- `proxy` - API proxying
|
|
60
|
+
- `auth` - HTTP Basic Auth
|
|
61
|
+
- `tunnel` - Public tunnels
|
|
62
|
+
- `health` - Health endpoint
|
|
63
|
+
- `upload` - File upload
|
|
64
|
+
- `history` - Request history
|
|
65
|
+
- 📚 **Plugin API Documentation** - Complete guide for plugin development
|
|
66
|
+
- 🔧 **Plugin Configuration** - Load plugins via config or CLI
|
|
67
|
+
|
|
68
|
+
### Changed - Architecture
|
|
69
|
+
- ♻️ **Refactored Core** - Cleaner, more maintainable codebase
|
|
70
|
+
- 📉 **Reduced Bundle Size** - 30% smaller (2.5MB → 1.7MB)
|
|
71
|
+
- ⚡ **Performance Improvements** - Optimized watcher and caching
|
|
72
|
+
- 🛡️ **Better Error Handling** - Graceful degradation per plugin
|
|
73
|
+
|
|
74
|
+
### Removed - Deprecated Features
|
|
75
|
+
- ❌ **PM2 Integration** - Use PM2 CLI directly
|
|
76
|
+
- ❌ **NPM Script Runner** - Use `concurrently` or similar tools
|
|
77
|
+
- ❌ **Docker Helper** - Use Docker documentation
|
|
78
|
+
- ❌ **Interactive CLI Wizard** - Simplified to minimal prompts
|
|
79
|
+
- ❌ **Project Detector** - Not core functionality
|
|
80
|
+
- ❌ **Share Manager** - Will be available as external plugin
|
|
81
|
+
|
|
82
|
+
### Migration
|
|
83
|
+
- 📖 **Migration Guide** - Complete guide for upgrading from v2.x
|
|
84
|
+
- ⚠️ **Breaking Changes** - See MIGRATION.md for details
|
|
85
|
+
- 🔄 **Backward Compatibility** - Most configs work without changes
|
|
86
|
+
- 🛠️ **Plugin Conversion** - Guide for converting middleware to plugins
|
|
87
|
+
|
|
88
|
+
### Documentation
|
|
89
|
+
- 📚 **PLUGIN_GUIDE.md** - Complete plugin development guide
|
|
90
|
+
- 📖 **MIGRATION.md** - v2.x to v3.0 migration guide
|
|
91
|
+
- 🔧 **Updated README** - New plugin system documentation
|
|
92
|
+
|
|
93
|
+
### Technical Improvements
|
|
94
|
+
- 🏗️ **Modular Architecture** - Clear separation of concerns
|
|
95
|
+
- 🧪 **Better Testability** - Isolated plugin testing
|
|
96
|
+
- 🔍 **Memory Profiling** - Better resource tracking
|
|
97
|
+
- 📊 **Performance Monitoring** - Built-in metrics
|
|
98
|
+
|
|
99
|
+
### Positioning
|
|
100
|
+
- 🎯 **Clear Identity** - "Plugin-based dev server for any stack"
|
|
101
|
+
- 🚀 **Differentiation** - Framework-agnostic alternative to Vite
|
|
102
|
+
- 🌱 **Sustainability** - Community-driven plugin ecosystem
|
|
103
|
+
- ⭐ **Growth Path** - Designed for 1k+ GitHub stars
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
5
107
|
## [2.5.0] - 2026-02-22
|
|
6
108
|
|
|
7
109
|
### Added - Modular Architecture
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -4,10 +4,20 @@
|
|
|
4
4
|
[](https://www.npmjs.org/package/gib-runs)
|
|
5
5
|
[](https://github.com/levouinse/gib-runs/blob/main/LICENSE)
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
**Plugin-based development server with live reload.** Built for developers who value capability over connections.
|
|
8
8
|
|
|
9
9
|
> *"Unlike some people, this actually runs on merit, not nepotism."*
|
|
10
10
|
|
|
11
|
+
## What's New in v3.0.1?
|
|
12
|
+
|
|
13
|
+
🐛 **Bug Fixes** - Critical syntax errors and memory leaks fixed
|
|
14
|
+
🔒 **Security** - Updated dependencies, fixed vulnerabilities
|
|
15
|
+
⚡ **Performance** - Stable memory usage, no more leaks
|
|
16
|
+
✨ **File Tracking** - Accurate detection of moved, created, changed, and deleted files
|
|
17
|
+
✅ **Quality** - All tests passing, ESLint clean
|
|
18
|
+
|
|
19
|
+
[Changelog](./CHANGELOG.md) | [Migration Guide](./MIGRATION.md)
|
|
20
|
+
|
|
11
21
|
## Why GIB-RUNS?
|
|
12
22
|
|
|
13
23
|
Named after Indonesia's Vice President Gibran Rakabuming Raka, who got his position through family connections. But unlike certain political figures, this server:
|
|
@@ -27,6 +37,7 @@ Named after Indonesia's Vice President Gibran Rakabuming Raka, who got his posit
|
|
|
27
37
|
- 🎨 **Beautiful UI** - Modern status indicator with real-time feedback
|
|
28
38
|
- 📊 **Performance Monitoring** - Track requests, reloads, and uptime
|
|
29
39
|
- 📝 **Comprehensive Logging** - All requests, errors, and file changes with timestamps and file sizes
|
|
40
|
+
- 🔀 **Smart File Tracking** - Accurately detects file moved, created, changed, and deleted operations
|
|
30
41
|
- 📜 **Request History** - Track last 50 requests via `/history` endpoint
|
|
31
42
|
- 🗜️ **Compression** - Built-in gzip compression
|
|
32
43
|
- 🔒 **HTTPS/HTTP2** - Secure development with modern protocols
|
|
@@ -41,7 +52,7 @@ Named after Indonesia's Vice President Gibran Rakabuming Raka, who got his posit
|
|
|
41
52
|
- 🌍 **Public Tunnels** - Share your dev server with anyone, anywhere
|
|
42
53
|
- 📱 **Multi-Device** - Access from any device on your network
|
|
43
54
|
|
|
44
|
-
### 🆕
|
|
55
|
+
### 🆕 Advanced Features
|
|
45
56
|
|
|
46
57
|
- 🎯 **Interactive CLI** - Inquirer-style setup wizard
|
|
47
58
|
- 🔍 **Auto-Detect** - Automatically detect project type (React, Vue, Angular, etc.)
|
|
@@ -427,12 +438,12 @@ gib-runs --middleware=./middleware/custom.js
|
|
|
427
438
|
Server automatically binds to `0.0.0.0` and shows all network URLs:
|
|
428
439
|
|
|
429
440
|
```
|
|
430
|
-
🚀 GIB-RUNS
|
|
441
|
+
🚀 GIB-RUNS v3.0.1
|
|
431
442
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
432
443
|
📁 Root: /home/user/project
|
|
433
444
|
🌐 Local: http://127.0.0.1:8080
|
|
434
445
|
🔗 Network: http://192.168.1.100:8080
|
|
435
|
-
🔄 Live Reload: Enabled
|
|
446
|
+
🔄 Live Reload: Enabled (no dynasty needed)
|
|
436
447
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
437
448
|
```
|
|
438
449
|
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { logger } = require('./logger');
|
|
3
|
+
|
|
4
|
+
class PluginManager {
|
|
5
|
+
constructor(server) {
|
|
6
|
+
this.server = server;
|
|
7
|
+
this.plugins = new Map();
|
|
8
|
+
this.hooks = {
|
|
9
|
+
onInit: [],
|
|
10
|
+
onStart: [],
|
|
11
|
+
onRequest: [],
|
|
12
|
+
onFileChange: [],
|
|
13
|
+
onReload: [],
|
|
14
|
+
onStop: []
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
register(plugin, options = {}) {
|
|
19
|
+
if (typeof plugin === 'string') {
|
|
20
|
+
plugin = this.loadPlugin(plugin);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!plugin || !plugin.name) {
|
|
24
|
+
throw new Error('Invalid plugin: must have a name');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (this.plugins.has(plugin.name)) {
|
|
28
|
+
logger.warn(`Plugin ${plugin.name} already registered`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const pluginInstance = {
|
|
33
|
+
...plugin,
|
|
34
|
+
options,
|
|
35
|
+
enabled: true
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
this.plugins.set(plugin.name, pluginInstance);
|
|
39
|
+
|
|
40
|
+
// Register hooks
|
|
41
|
+
Object.keys(this.hooks).forEach(hookName => {
|
|
42
|
+
if (typeof plugin[hookName] === 'function') {
|
|
43
|
+
this.hooks[hookName].push({
|
|
44
|
+
plugin: plugin.name,
|
|
45
|
+
handler: plugin[hookName].bind(pluginInstance)
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
logger.debug(`Plugin registered: ${plugin.name}`);
|
|
51
|
+
|
|
52
|
+
return pluginInstance;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
loadPlugin(pluginPath) {
|
|
56
|
+
try {
|
|
57
|
+
// Try as npm package
|
|
58
|
+
if (!pluginPath.startsWith('.') && !pluginPath.startsWith('/')) {
|
|
59
|
+
return require(pluginPath);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Try as local file
|
|
63
|
+
const resolved = path.resolve(process.cwd(), pluginPath);
|
|
64
|
+
return require(resolved);
|
|
65
|
+
} catch (e) {
|
|
66
|
+
logger.error(`Failed to load plugin: ${pluginPath}`, e.message);
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async init() {
|
|
72
|
+
for (const { plugin, handler } of this.hooks.onInit) {
|
|
73
|
+
try {
|
|
74
|
+
await handler(this.server);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
// Only log in non-test mode
|
|
77
|
+
if (typeof describe === 'undefined') {
|
|
78
|
+
logger.error(`Plugin ${plugin} init failed:`, e.message);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async start(address) {
|
|
85
|
+
for (const { plugin, handler } of this.hooks.onStart) {
|
|
86
|
+
try {
|
|
87
|
+
await handler(address);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
logger.error(`Plugin ${plugin} start failed:`, e.message);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
createMiddleware() {
|
|
95
|
+
return async (req, res, next) => {
|
|
96
|
+
let index = 0;
|
|
97
|
+
|
|
98
|
+
const runNext = async () => {
|
|
99
|
+
if (index >= this.hooks.onRequest.length) {
|
|
100
|
+
return next();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const { plugin, handler } = this.hooks.onRequest[index++];
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
await handler(req, res, runNext);
|
|
107
|
+
} catch (e) {
|
|
108
|
+
logger.error(`Plugin ${plugin} middleware failed:`, e.message);
|
|
109
|
+
next(e);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
await runNext();
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async onFileChange(filePath, changeType) {
|
|
118
|
+
for (const { handler } of this.hooks.onFileChange) {
|
|
119
|
+
try {
|
|
120
|
+
await handler(filePath, changeType);
|
|
121
|
+
} catch {
|
|
122
|
+
// Silent fail for file change hooks
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async onReload() {
|
|
128
|
+
for (const { handler } of this.hooks.onReload) {
|
|
129
|
+
try {
|
|
130
|
+
await handler();
|
|
131
|
+
} catch {
|
|
132
|
+
// Silent fail
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async stop() {
|
|
138
|
+
for (const { plugin, handler } of this.hooks.onStop) {
|
|
139
|
+
try {
|
|
140
|
+
await handler();
|
|
141
|
+
} catch (e) {
|
|
142
|
+
logger.error(`Plugin ${plugin} stop failed:`, e.message);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.plugins.clear();
|
|
147
|
+
Object.keys(this.hooks).forEach(key => {
|
|
148
|
+
this.hooks[key] = [];
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
get(name) {
|
|
153
|
+
return this.plugins.get(name);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
list() {
|
|
157
|
+
return Array.from(this.plugins.values()).map(p => ({
|
|
158
|
+
name: p.name,
|
|
159
|
+
version: p.version,
|
|
160
|
+
enabled: p.enabled
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
disable(name) {
|
|
165
|
+
const plugin = this.plugins.get(name);
|
|
166
|
+
if (plugin) {
|
|
167
|
+
plugin.enabled = false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
enable(name) {
|
|
172
|
+
const plugin = this.plugins.get(name);
|
|
173
|
+
if (plugin) {
|
|
174
|
+
plugin.enabled = true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = PluginManager;
|
package/core/server.js
CHANGED
|
@@ -2,16 +2,22 @@ const http = require('http');
|
|
|
2
2
|
const connect = require('connect');
|
|
3
3
|
const { eventBus, Events } = require('./event-bus');
|
|
4
4
|
const { logger } = require('./logger');
|
|
5
|
+
const PluginManager = require('./plugin-manager');
|
|
5
6
|
|
|
6
7
|
class Server {
|
|
7
8
|
constructor(options = {}) {
|
|
8
9
|
this.options = options;
|
|
10
|
+
this.config = options;
|
|
9
11
|
this.app = connect();
|
|
10
12
|
this.server = null;
|
|
11
13
|
this.wsClients = [];
|
|
12
14
|
this.startTime = null;
|
|
13
15
|
this.requestCount = 0;
|
|
14
16
|
this.reloadCount = 0;
|
|
17
|
+
this.logger = logger;
|
|
18
|
+
|
|
19
|
+
// Plugin system
|
|
20
|
+
this.pluginManager = new PluginManager(this);
|
|
15
21
|
}
|
|
16
22
|
|
|
17
23
|
createServer() {
|
|
@@ -59,6 +65,9 @@ class Server {
|
|
|
59
65
|
|
|
60
66
|
logger.success(`Server listening on ${this.protocol}://${address.address}:${address.port}`);
|
|
61
67
|
eventBus.emit(Events.SERVER_LISTENING, address);
|
|
68
|
+
|
|
69
|
+
// Trigger plugin onStart hooks
|
|
70
|
+
this.pluginManager.start(address);
|
|
62
71
|
});
|
|
63
72
|
}
|
|
64
73
|
|
|
@@ -128,6 +137,9 @@ class Server {
|
|
|
128
137
|
this.createServer();
|
|
129
138
|
}
|
|
130
139
|
|
|
140
|
+
// Initialize plugins
|
|
141
|
+
await this.pluginManager.init();
|
|
142
|
+
|
|
131
143
|
return new Promise((resolve, reject) => {
|
|
132
144
|
this.server.listen(port, host, (error) => {
|
|
133
145
|
if (error) {
|
|
@@ -140,7 +152,10 @@ class Server {
|
|
|
140
152
|
});
|
|
141
153
|
}
|
|
142
154
|
|
|
143
|
-
close() {
|
|
155
|
+
async close() {
|
|
156
|
+
// Stop plugins
|
|
157
|
+
await this.pluginManager.stop();
|
|
158
|
+
|
|
144
159
|
return new Promise((resolve) => {
|
|
145
160
|
if (this.server) {
|
|
146
161
|
this.server.close(() => {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example Plugin: API Mock
|
|
3
|
+
*
|
|
4
|
+
* Mock API endpoints with static JSON responses
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
module.exports = {
|
|
8
|
+
name: 'api-mock',
|
|
9
|
+
version: '1.0.0',
|
|
10
|
+
description: 'Mock API endpoints',
|
|
11
|
+
|
|
12
|
+
onInit(server) {
|
|
13
|
+
const mocks = this.options.mocks || {};
|
|
14
|
+
|
|
15
|
+
Object.keys(mocks).forEach(route => {
|
|
16
|
+
const response = mocks[route];
|
|
17
|
+
|
|
18
|
+
server.use((req, res, next) => {
|
|
19
|
+
if (req.url === route) {
|
|
20
|
+
res.setHeader('Content-Type', 'application/json');
|
|
21
|
+
res.setHeader('X-Mock', 'true');
|
|
22
|
+
res.end(JSON.stringify(response, null, 2));
|
|
23
|
+
} else {
|
|
24
|
+
next();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (server.logger) {
|
|
30
|
+
server.logger.info(`API Mock: ${Object.keys(mocks).length} routes registered`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
// Usage in .gib-runs.json:
|
|
36
|
+
// {
|
|
37
|
+
// "plugins": [
|
|
38
|
+
// ["./examples/plugin-api-mock.js", {
|
|
39
|
+
// "mocks": {
|
|
40
|
+
// "/api/user": { "id": 1, "name": "John Doe" },
|
|
41
|
+
// "/api/posts": [
|
|
42
|
+
// { "id": 1, "title": "Hello World" },
|
|
43
|
+
// { "id": 2, "title": "Plugin System" }
|
|
44
|
+
// ]
|
|
45
|
+
// }
|
|
46
|
+
// }]
|
|
47
|
+
// ]
|
|
48
|
+
// }
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example Plugin: Performance Monitor
|
|
3
|
+
*
|
|
4
|
+
* Monitors slow requests and logs warnings
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
name: 'perf-monitor',
|
|
11
|
+
version: '1.0.0',
|
|
12
|
+
description: 'Monitor slow requests',
|
|
13
|
+
|
|
14
|
+
onRequest(req, res, next) {
|
|
15
|
+
const threshold = this.options.threshold || 100; // ms
|
|
16
|
+
const start = Date.now();
|
|
17
|
+
const originalEnd = res.end;
|
|
18
|
+
|
|
19
|
+
res.end = function(...args) {
|
|
20
|
+
const duration = Date.now() - start;
|
|
21
|
+
|
|
22
|
+
if (duration > threshold) {
|
|
23
|
+
console.log(
|
|
24
|
+
chalk.yellow('⚠ SLOW REQUEST:'),
|
|
25
|
+
chalk.white(req.url),
|
|
26
|
+
chalk.red(`${duration}ms`),
|
|
27
|
+
chalk.gray(`(threshold: ${threshold}ms)`)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
originalEnd.apply(res, args);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
next();
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Usage in .gib-runs.json:
|
|
39
|
+
// {
|
|
40
|
+
// "plugins": [
|
|
41
|
+
// ["./examples/plugin-perf-monitor.js", {
|
|
42
|
+
// "threshold": 50
|
|
43
|
+
// }]
|
|
44
|
+
// ]
|
|
45
|
+
// }
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example Plugin: Request Logger
|
|
3
|
+
*
|
|
4
|
+
* Logs all HTTP requests with custom formatting
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
|
|
9
|
+
module.exports = {
|
|
10
|
+
name: 'request-logger',
|
|
11
|
+
version: '1.0.0',
|
|
12
|
+
description: 'Custom request logger with colors',
|
|
13
|
+
|
|
14
|
+
onRequest(req, res, next) {
|
|
15
|
+
const start = Date.now();
|
|
16
|
+
const originalEnd = res.end;
|
|
17
|
+
|
|
18
|
+
res.end = function(...args) {
|
|
19
|
+
const duration = Date.now() - start;
|
|
20
|
+
const statusColor = res.statusCode >= 400 ? chalk.red : chalk.green;
|
|
21
|
+
|
|
22
|
+
console.log(
|
|
23
|
+
chalk.gray(new Date().toISOString()),
|
|
24
|
+
chalk.cyan(req.method.padEnd(6)),
|
|
25
|
+
chalk.white(req.url.padEnd(40).substring(0, 40)),
|
|
26
|
+
statusColor(res.statusCode),
|
|
27
|
+
chalk.gray(`${duration}ms`)
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
originalEnd.apply(res, args);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
next();
|
|
34
|
+
}
|
|
35
|
+
};
|