busyserver 1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SATPAL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,144 @@
1
+ # BusyServer
2
+
3
+ > **A lightweight, cross-platform Node.js development and static file server.**
4
+
5
+ BusyServer is a fast and production-quality static server designed for developers. It runs seamlessly anywhere Node.js is supportedโ€”including Linux, macOS, Windows, and Termux/Android environments. Whether you need an instant local server, reverse proxy, API mocks, or live-reload capabilities, BusyServer has you covered.
6
+
7
+ ## Features
8
+
9
+ * **โšก Live Reload**: Instant HTML, CSS, and Image hot-swapping without full page reloads via WebSockets.
10
+ * **๐Ÿ“‚ Directory Listing**: Modern, responsive dark/light UI with breadcrumb navigation, search/filter, and column sorting.
11
+ * **๐Ÿ”’ Local HTTPS**: Built-in TLS support for secure localhost testing (`--https`).
12
+ * **๐Ÿ”„ Reverse Proxy**: Forward requests to a backend server to bypass CORS (`--proxy /api=http://localhost:5000`).
13
+ * **๐ŸŽญ API Mocking**: Serve JSON mocks effortlessly from a directory (`--mock ./mocks`), including HTTP-method-specific mocks.
14
+ * **๐ŸŒ SPA Fallback**: Automatically serves `index.html` for missing files (`--spa`).
15
+ * **๐Ÿ“ฑ Real QR Code**: Scan and open on your mobile device instantly (`--qr`).
16
+ * **๐Ÿ—œ๏ธ Performance**: Automatic Brotli and Gzip compression, HTTP Range requests (206 Partial Content), and ETag/304 caching.
17
+ * **๐Ÿ› ๏ธ Developer Experience**: Smart port selection/fallback, host binding, custom error pages (404, 403, 405, 500), and custom headers.
18
+ * **๐Ÿ“Š Web Dashboard**: Inspect active connections and server stats live at `/__busyserver`.
19
+ * **โš™๏ธ Persistent Config**: Use `busyserver init` to generate a `.busyserverc.json`.
20
+ * **๐Ÿ›ก๏ธ Security**: Built-in protection against path traversal, null bytes, and unauthorized dotfile access.
21
+
22
+ ## Quick Start
23
+
24
+ Run it instantly without installing:
25
+ \`\`\`bash
26
+ npx busyserver
27
+ \`\`\`
28
+
29
+ Or install globally via npm:
30
+ \`\`\`bash
31
+ npm install -g busyserver
32
+ \`\`\`
33
+
34
+ ## CLI Usage
35
+
36
+ Serve the current directory on an available port:
37
+ \`\`\`bash
38
+ busyserver
39
+ \`\`\`
40
+
41
+ Serve a specific directory with a custom port and auto-open browser:
42
+ \`\`\`bash
43
+ busyserver ./public -p 8080 --open
44
+ \`\`\`
45
+
46
+ Bind to a specific network interface (e.g., localhost only):
47
+ \`\`\`bash
48
+ busyserver --host 127.0.0.1
49
+ \`\`\`
50
+
51
+ Enable Single Page App (SPA) mode:
52
+ \`\`\`bash
53
+ busyserver --spa
54
+ \`\`\`
55
+
56
+ Show a QR code for easy mobile LAN access:
57
+ \`\`\`bash
58
+ busyserver --qr
59
+ \`\`\`
60
+
61
+ ### API Mocking
62
+
63
+ Need a fake backend while developing? Enable mocking:
64
+
65
+ \`\`\`bash
66
+ busyserver --mock ./mocks
67
+ \`\`\`
68
+
69
+ Create files in your mock directory (`./mocks/api`):
70
+ - `./mocks/api/users.json` โ†’ responds to `GET /api/users`
71
+ - `./mocks/api/users.POST.json` โ†’ responds to `POST /api/users`
72
+ - `./mocks/api/index.json` โ†’ responds to `GET /api/`
73
+
74
+ ### Reverse Proxy
75
+
76
+ Forward API requests to a real backend to bypass CORS:
77
+
78
+ \`\`\`bash
79
+ busyserver --proxy /api=http://localhost:5000
80
+ \`\`\`
81
+
82
+ ### Local HTTPS (TLS)
83
+
84
+ Test features that require secure contexts (like Service Workers or Geolocation) using local HTTPS.
85
+ Ensure `key.pem` and `cert.pem` are present in the directory you are running the server from.
86
+
87
+ \`\`\`bash
88
+ busyserver --https
89
+ \`\`\`
90
+
91
+ ## Configuration
92
+
93
+ Generate a configuration file to save your project preferences:
94
+
95
+ \`\`\`bash
96
+ busyserver init
97
+ \`\`\`
98
+
99
+ This creates a `.busyserverc.json` in your project root:
100
+
101
+ \`\`\`json
102
+ {
103
+ "port": 3000,
104
+ "host": "0.0.0.0",
105
+ "open": false,
106
+ "reload": true,
107
+ "watch": true,
108
+ "spa": false,
109
+ "quiet": false,
110
+ "qr": false,
111
+ "https": false,
112
+ "compression": true,
113
+ "proxy": {},
114
+ "mock": "",
115
+ "ignore": [
116
+ "**/.git/**",
117
+ "**/node_modules/**"
118
+ ],
119
+ "headers": {
120
+ "X-Custom-Header": "BusyServer"
121
+ }
122
+ }
123
+ \`\`\`
124
+
125
+ ## Dashboard
126
+
127
+ Navigate to `/__busyserver` in your browser while the server is running to view the live developer dashboard, monitor uptime, and see active WebSocket clients.
128
+
129
+ ## Development & Testing
130
+
131
+ Clone the repository and install dependencies:
132
+
133
+ \`\`\`bash
134
+ npm install
135
+ \`\`\`
136
+
137
+ Run the comprehensive test suite (62 tests):
138
+
139
+ \`\`\`bash
140
+ npm test
141
+ \`\`\`
142
+
143
+ ---
144
+ *Built for modern cross-platform web development.*
@@ -0,0 +1,246 @@
1
+ #!/bin/sh
2
+ ":" //# comment; exec node "$0" "$@"
3
+
4
+ const fs = require("fs");
5
+ const os = require("os");
6
+ const path = require("path");
7
+ const { startBusyServer } = require("../src/server");
8
+ const { loadConfig } = require("../src/config");
9
+
10
+ const pkg = require("../package.json");
11
+ const args = process.argv.slice(2);
12
+
13
+ // โ”€โ”€ Init Command โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
14
+
15
+ if (args[0] === "init") {
16
+ const configPath = path.join(process.cwd(), ".busyserverc.json");
17
+ if (fs.existsSync(configPath)) {
18
+ console.error("โŒ Configuration file already exists: .busyserverc.json");
19
+ process.exit(1);
20
+ }
21
+
22
+ const defaultConfig = {
23
+ "port": 3000,
24
+ "host": "0.0.0.0",
25
+ "open": false,
26
+ "reload": true,
27
+ "watch": true,
28
+ "spa": false,
29
+ "quiet": false,
30
+ "qr": false,
31
+ "https": false,
32
+ "compression": true,
33
+ "proxy": {},
34
+ "mock": "",
35
+ "ignore": [
36
+ "**/.git/**",
37
+ "**/node_modules/**"
38
+ ],
39
+ "headers": {
40
+ "X-Custom-Header": "BusyServer"
41
+ }
42
+ };
43
+
44
+ fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2));
45
+ console.log("โœ… Created .busyserverc.json with default settings.");
46
+ process.exit(0);
47
+ }
48
+
49
+ // โ”€โ”€ Help โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
50
+
51
+ function showHelp() {
52
+ console.log(`
53
+ ๐Ÿš€ BusyServer v${pkg.version} โ€” Live Server for Termux
54
+
55
+ Usage:
56
+ busyserver [directory] [options]
57
+ busyserver init Create .busyserverc.json
58
+
59
+ Arguments:
60
+ directory Directory to serve (default: current directory)
61
+
62
+ Options:
63
+ -p, --port <number> Server port (default: 3000)
64
+ --host <address> Host to bind to (default: 0.0.0.0)
65
+ --open Open in browser automatically
66
+ --no-reload Disable live reload
67
+ --no-watch Disable file watching
68
+ --spa Enable SPA mode (fallback to index.html)
69
+ --quiet Suppress request logging
70
+ --qr Show QR code for network URL
71
+ --https Enable local HTTPS
72
+ --proxy <rule> Add a proxy rule (e.g. /api=http://localhost:5000)
73
+ --mock <dir> Enable API mocking from directory
74
+ -h, --help Show this help message
75
+ -v, --version Show version number
76
+
77
+ Examples:
78
+ busyserver Serve current directory
79
+ busyserver ./website Serve ./website directory
80
+ busyserver -p 8080 --qr Serve on port 8080 with QR
81
+ busyserver --proxy /api=http://... Proxy /api to another server
82
+
83
+ Config:
84
+ Place a .busyserverc.json in your project root for persistent settings.
85
+ CLI arguments override config file values.
86
+ `);
87
+ }
88
+
89
+ // โ”€โ”€ Parse Arguments โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
90
+
91
+ if (args.includes("-h") || args.includes("--help")) {
92
+ showHelp();
93
+ process.exit(0);
94
+ }
95
+
96
+ if (args.includes("-v") || args.includes("--version")) {
97
+ console.log(`busyserver v${pkg.version}`);
98
+ process.exit(0);
99
+ }
100
+
101
+ let directory = null;
102
+ let cliPort = null;
103
+ let cliHost = null;
104
+ let explicitPort = false;
105
+ let cliOpen = null;
106
+ let cliReload = null;
107
+ let cliWatch = null;
108
+ let cliSpa = null;
109
+ let cliQuiet = null;
110
+ let cliQr = null;
111
+ let cliHttps = null;
112
+ let cliMock = null;
113
+ let cliProxy = {};
114
+
115
+ for (let i = 0; i < args.length; i++) {
116
+ const arg = args[i];
117
+
118
+ if (arg === "-p" || arg === "--port") {
119
+ const value = args[i + 1];
120
+ if (!value || value.startsWith("-")) {
121
+ console.error("โŒ Port number missing after " + arg);
122
+ process.exit(1);
123
+ }
124
+ const num = Number(value);
125
+ if (!Number.isInteger(num) || num < 1 || num > 65535) {
126
+ console.error(`โŒ Invalid port number: \${value}`);
127
+ process.exit(1);
128
+ }
129
+ cliPort = num;
130
+ explicitPort = true;
131
+ i++;
132
+ } else if (arg === "--host") {
133
+ const value = args[i + 1];
134
+ if (!value || value.startsWith("-")) {
135
+ console.error("โŒ Host address missing after --host");
136
+ process.exit(1);
137
+ }
138
+ cliHost = value;
139
+ i++;
140
+ } else if (arg === "--proxy") {
141
+ const value = args[i + 1];
142
+ if (!value || value.startsWith("-") || !value.includes("=")) {
143
+ console.error("โŒ Proxy rule must be in format /path=http://target");
144
+ process.exit(1);
145
+ }
146
+ const [pathPrefix, target] = value.split("=");
147
+ cliProxy[pathPrefix] = target;
148
+ i++;
149
+ } else if (arg === "--mock") {
150
+ const value = args[i + 1];
151
+ if (!value || value.startsWith("-")) {
152
+ console.error("โŒ Mock directory missing after --mock");
153
+ process.exit(1);
154
+ }
155
+ cliMock = value;
156
+ i++;
157
+ } else if (arg === "--open") {
158
+ cliOpen = true;
159
+ } else if (arg === "--no-reload") {
160
+ cliReload = false;
161
+ } else if (arg === "--no-watch") {
162
+ cliWatch = false;
163
+ } else if (arg === "--spa") {
164
+ cliSpa = true;
165
+ } else if (arg === "--quiet") {
166
+ cliQuiet = true;
167
+ } else if (arg === "--qr") {
168
+ cliQr = true;
169
+ } else if (arg === "--https") {
170
+ cliHttps = true;
171
+ } else if (arg.startsWith("-")) {
172
+ console.error(`โŒ Unknown option: \${arg}`);
173
+ console.log("Use 'busyserver --help' for available options.");
174
+ process.exit(1);
175
+ } else {
176
+ if (directory !== null) {
177
+ console.error(`โŒ Unexpected argument: \${arg}`);
178
+ console.log("Use 'busyserver --help' for available options.");
179
+ process.exit(1);
180
+ }
181
+ directory = arg;
182
+ }
183
+ }
184
+
185
+ // โ”€โ”€ Resolve Directory โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
186
+
187
+ let resolvedDir;
188
+
189
+ if (directory) {
190
+ if (directory.startsWith("~")) {
191
+ directory = path.join(os.homedir(), directory.slice(1));
192
+ }
193
+ resolvedDir = path.resolve(process.cwd(), directory);
194
+ } else {
195
+ resolvedDir = process.cwd();
196
+ }
197
+
198
+ try {
199
+ const stat = fs.statSync(resolvedDir);
200
+ if (!stat.isDirectory()) {
201
+ console.error(`โŒ Path is not a directory: \${resolvedDir}`);
202
+ process.exit(1);
203
+ }
204
+ } catch (error) {
205
+ if (error.code === "ENOENT") {
206
+ console.error(`โŒ Directory not found: \${resolvedDir}`);
207
+ } else {
208
+ console.error(`โŒ Cannot access directory: \${resolvedDir}`);
209
+ }
210
+ process.exit(1);
211
+ }
212
+
213
+ // โ”€โ”€ Load Config โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
214
+
215
+ const config = loadConfig(resolvedDir);
216
+
217
+ // Merge proxy config
218
+ const finalProxy = { ...(config.proxy || {}), ...cliProxy };
219
+
220
+ // โ”€โ”€ Merge: defaults < config < CLI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
221
+
222
+ const options = {
223
+ root: resolvedDir,
224
+ port: cliPort !== null ? cliPort : config.port,
225
+ host: cliHost !== null ? cliHost : config.host,
226
+ autoPort: !explicitPort,
227
+ reload: cliReload !== null ? cliReload : config.reload,
228
+ watch: cliWatch !== null ? cliWatch : config.watch,
229
+ open: cliOpen !== null ? cliOpen : config.open,
230
+ spa: cliSpa !== null ? cliSpa : config.spa,
231
+ quiet: cliQuiet !== null ? cliQuiet : config.quiet,
232
+ qr: cliQr !== null ? cliQr : config.qr,
233
+ https: cliHttps !== null ? cliHttps : config.https,
234
+ mock: cliMock !== null ? cliMock : config.mock,
235
+ proxy: finalProxy,
236
+ compression: config.compression !== false,
237
+ headers: config.headers || {},
238
+ ignore: config.ignore || [],
239
+ };
240
+
241
+ // โ”€โ”€ Start Server โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
242
+
243
+ startBusyServer(options).catch((error) => {
244
+ console.error(`โŒ Failed to start server: \${error.message}`);
245
+ process.exit(1);
246
+ });
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "busyserver",
3
+ "version": "1.0.0",
4
+ "description": "A lightweight live-reload development server for Termux โ€” the VS Code Live Server equivalent for your terminal.",
5
+ "main": "src/server.js",
6
+ "bin": {
7
+ "busyserver": "bin/busyserver.js"
8
+ },
9
+ "files": [
10
+ "bin/",
11
+ "src/",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "scripts": {
16
+ "start": "node bin/busyserver.js",
17
+ "test": "node --test test/server.test.js"
18
+ },
19
+ "keywords": [
20
+ "termux",
21
+ "live-server",
22
+ "live-reload",
23
+ "dev-server",
24
+ "static-server",
25
+ "hot-reload",
26
+ "websocket",
27
+ "cli"
28
+ ],
29
+ "author": "debuggersatpal",
30
+ "license": "MIT",
31
+ "type": "commonjs",
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ },
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/debuggersatpal/busyserver.git"
38
+ },
39
+ "dependencies": {
40
+ "chokidar": "^5.0.0",
41
+ "qrcode-terminal": "^0.12.0",
42
+ "ws": "^8.21.3"
43
+ }
44
+ }
package/src/config.js ADDED
@@ -0,0 +1,60 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const DEFAULTS = {
5
+ port: 3000,
6
+ host: "0.0.0.0",
7
+ reload: true,
8
+ watch: true,
9
+ open: false,
10
+ spa: false,
11
+ quiet: false,
12
+ qr: false,
13
+ ignore: [],
14
+ };
15
+
16
+ const CONFIG_FILE = ".busyserverc.json";
17
+
18
+ /**
19
+ * Load configuration from .busyserverc.json in the given directory.
20
+ * Returns defaults merged with any file-based config.
21
+ */
22
+ function loadConfig(directory) {
23
+ const configPath = path.join(directory, CONFIG_FILE);
24
+ const config = { ...DEFAULTS };
25
+
26
+ try {
27
+ const stat = fs.statSync(configPath);
28
+ if (!stat.isFile()) return config;
29
+ } catch {
30
+ return config;
31
+ }
32
+
33
+ try {
34
+ const raw = fs.readFileSync(configPath, "utf-8");
35
+ const parsed = JSON.parse(raw);
36
+
37
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
38
+ console.warn(`โš ๏ธ Invalid config in ${CONFIG_FILE}: expected an object`);
39
+ return config;
40
+ }
41
+
42
+ // Merge only known keys
43
+ if (typeof parsed.port === "number") config.port = parsed.port;
44
+ if (typeof parsed.host === "string") config.host = parsed.host;
45
+ if (typeof parsed.reload === "boolean") config.reload = parsed.reload;
46
+ if (typeof parsed.watch === "boolean") config.watch = parsed.watch;
47
+ if (typeof parsed.open === "boolean") config.open = parsed.open;
48
+ if (typeof parsed.spa === "boolean") config.spa = parsed.spa;
49
+ if (typeof parsed.quiet === "boolean") config.quiet = parsed.quiet;
50
+ if (typeof parsed.qr === "boolean") config.qr = parsed.qr;
51
+ if (Array.isArray(parsed.ignore)) config.ignore = parsed.ignore;
52
+
53
+ return config;
54
+ } catch (error) {
55
+ console.warn(`โš ๏ธ Failed to parse ${CONFIG_FILE}: ${error.message}`);
56
+ return config;
57
+ }
58
+ }
59
+
60
+ module.exports = { loadConfig, DEFAULTS, CONFIG_FILE };
@@ -0,0 +1,32 @@
1
+ function generateDashboard(options, wss, watcher) {
2
+ const uptime = process.uptime();
3
+
4
+ return `<!DOCTYPE html>
5
+ <html lang="en">
6
+ <head>
7
+ <meta charset="UTF-8">
8
+ <title>BusyServer Dashboard</title>
9
+ <style>
10
+ body { font-family: system-ui; background: #0f172a; color: #f8fafc; padding: 2rem; margin: 0; }
11
+ .card { background: #1e293b; padding: 1.5rem; border-radius: 8px; margin-bottom: 1rem; border: 1px solid #334155; }
12
+ h1 { margin-top: 0; color: #38bdf8; }
13
+ .stat { display: flex; justify-content: space-between; padding: 0.5rem 0; border-bottom: 1px solid #334155; }
14
+ .stat:last-child { border: none; }
15
+ </style>
16
+ </head>
17
+ <body>
18
+ <div class="card">
19
+ <h1>BusyServer Dashboard</h1>
20
+ <div class="stat"><span>Root</span> <span>${options.root}</span></div>
21
+ <div class="stat"><span>Port</span> <span>${options.port}</span></div>
22
+ <div class="stat"><span>Live Reload</span> <span>${options.reload ? "ON" : "OFF"}</span></div>
23
+ <div class="stat"><span>Watcher</span> <span>${options.watch ? "ON" : "OFF"}</span></div>
24
+ <div class="stat"><span>SPA Mode</span> <span>${options.spa ? "ON" : "OFF"}</span></div>
25
+ <div class="stat"><span>Uptime</span> <span>${Math.floor(uptime)}s</span></div>
26
+ <div class="stat"><span>Connected Browsers</span> <span>${wss ? wss.clients.size : 0}</span></div>
27
+ </div>
28
+ </body>
29
+ </html>`;
30
+ }
31
+
32
+ module.exports = { generateDashboard };
package/src/mock.js ADDED
@@ -0,0 +1,45 @@
1
+ const path = require("path");
2
+ const fs = require("fs");
3
+
4
+ function handleMock(req, res, options) {
5
+ if (!options.mock) return false;
6
+
7
+ try {
8
+ const rawPath = req.url.split("?")[0];
9
+ const mockDir = path.resolve(process.cwd(), options.mock);
10
+
11
+ // Try Method-specific mock: mock/api/users.GET.json
12
+ let mockFile = path.join(mockDir, rawPath + "." + req.method + ".json");
13
+ if (fs.existsSync(mockFile) && fs.statSync(mockFile).isFile()) {
14
+ const data = fs.readFileSync(mockFile, "utf-8");
15
+ res.writeHead(200, { "Content-Type": "application/json" });
16
+ res.end(data);
17
+ return true;
18
+ }
19
+
20
+ // Convert e.g. /api/users to mock/api/users.json
21
+ mockFile = path.join(mockDir, rawPath + ".json");
22
+ if (fs.existsSync(mockFile) && fs.statSync(mockFile).isFile()) {
23
+ const data = fs.readFileSync(mockFile, "utf-8");
24
+ res.writeHead(200, { "Content-Type": "application/json" });
25
+ res.end(data);
26
+ return true;
27
+ }
28
+
29
+ // Try mock/api/users/index.json
30
+ mockFile = path.join(mockDir, rawPath, "index.json");
31
+ if (fs.existsSync(mockFile) && fs.statSync(mockFile).isFile()) {
32
+ const data = fs.readFileSync(mockFile, "utf-8");
33
+ res.writeHead(200, { "Content-Type": "application/json" });
34
+ res.end(data);
35
+ return true;
36
+ }
37
+
38
+ } catch (err) {
39
+ // ignore
40
+ }
41
+
42
+ return false;
43
+ }
44
+
45
+ module.exports = { handleMock };
package/src/proxy.js ADDED
@@ -0,0 +1,46 @@
1
+ const http = require("http");
2
+ const https = require("https");
3
+
4
+ function handleProxy(req, res, options) {
5
+ const proxyRules = options.proxy;
6
+ if (!proxyRules) return false;
7
+
8
+ const urlPath = req.url.split("?")[0];
9
+ let targetPrefix = null;
10
+ let targetUrl = null;
11
+
12
+ for (const [prefix, target] of Object.entries(proxyRules)) {
13
+ if (urlPath.startsWith(prefix)) {
14
+ targetPrefix = prefix;
15
+ targetUrl = target;
16
+ break;
17
+ }
18
+ }
19
+
20
+ if (!targetUrl) return false;
21
+
22
+ const client = targetUrl.startsWith("https") ? https : http;
23
+ const proxyUrl = new URL(targetUrl);
24
+ // Remove prefix if you want, but standard proxy often just forwards the path.
25
+ // Actually, standard proxy forwards the full path unless specified. Let's forward full path.
26
+ const proxyReq = client.request({
27
+ hostname: proxyUrl.hostname,
28
+ port: proxyUrl.port,
29
+ path: req.url,
30
+ method: req.method,
31
+ headers: { ...req.headers, host: proxyUrl.hostname }
32
+ }, (proxyRes) => {
33
+ res.writeHead(proxyRes.statusCode, proxyRes.headers);
34
+ proxyRes.pipe(res);
35
+ });
36
+
37
+ proxyReq.on("error", (err) => {
38
+ res.writeHead(502);
39
+ res.end("502 - Bad Gateway");
40
+ });
41
+
42
+ req.pipe(proxyReq);
43
+ return true;
44
+ }
45
+
46
+ module.exports = { handleProxy };