@chenpu17/serve-here 1.0.0 → 1.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/bin/serve-here.js +255 -2
- package/package.json +1 -1
package/bin/serve-here.js
CHANGED
|
@@ -4,12 +4,212 @@ const fs = require('fs');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const path = require('path');
|
|
6
6
|
const process = require('process');
|
|
7
|
+
const { spawn } = require('child_process');
|
|
7
8
|
|
|
8
9
|
const { createStaticServer } = require('../src/server');
|
|
9
10
|
const pkg = require('../package.json');
|
|
10
11
|
|
|
11
12
|
const DEFAULT_PORT = 8080;
|
|
12
13
|
const DEFAULT_HOST = '0.0.0.0';
|
|
14
|
+
const PID_DIR = path.join(os.homedir(), '.serve-here');
|
|
15
|
+
const LOG_DIR = path.join(os.homedir(), '.serve-here', 'logs');
|
|
16
|
+
|
|
17
|
+
function ensureDirectories() {
|
|
18
|
+
if (!fs.existsSync(PID_DIR)) {
|
|
19
|
+
fs.mkdirSync(PID_DIR, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
if (!fs.existsSync(LOG_DIR)) {
|
|
22
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function getPidFile(port) {
|
|
27
|
+
return path.join(PID_DIR, `serve-here-${port}.pid`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getLogFile(port) {
|
|
31
|
+
return path.join(LOG_DIR, `serve-here-${port}.log`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function readPidFile(port) {
|
|
35
|
+
const pidFile = getPidFile(port);
|
|
36
|
+
if (fs.existsSync(pidFile)) {
|
|
37
|
+
try {
|
|
38
|
+
const content = fs.readFileSync(pidFile, 'utf8').trim();
|
|
39
|
+
const lines = content.split('\n');
|
|
40
|
+
const pid = parseInt(lines[0], 10);
|
|
41
|
+
const rootDir = lines[1] || '';
|
|
42
|
+
return { pid, rootDir };
|
|
43
|
+
} catch (error) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function writePidFile(port, pid, rootDir) {
|
|
51
|
+
const pidFile = getPidFile(port);
|
|
52
|
+
fs.writeFileSync(pidFile, `${pid}\n${rootDir}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function removePidFile(port) {
|
|
56
|
+
const pidFile = getPidFile(port);
|
|
57
|
+
if (fs.existsSync(pidFile)) {
|
|
58
|
+
fs.unlinkSync(pidFile);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isProcessRunning(pid) {
|
|
63
|
+
try {
|
|
64
|
+
process.kill(pid, 0);
|
|
65
|
+
return true;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function startDaemon(options) {
|
|
72
|
+
ensureDirectories();
|
|
73
|
+
|
|
74
|
+
const rootDir = path.resolve(options.directory || process.cwd());
|
|
75
|
+
const port = options.port || DEFAULT_PORT;
|
|
76
|
+
const host = options.host || DEFAULT_HOST;
|
|
77
|
+
|
|
78
|
+
// Check if already running
|
|
79
|
+
const pidInfo = readPidFile(port);
|
|
80
|
+
if (pidInfo && isProcessRunning(pidInfo.pid)) {
|
|
81
|
+
console.error(`Error: Server already running on port ${port} (PID: ${pidInfo.pid})`);
|
|
82
|
+
console.error(`Serving: ${pidInfo.rootDir}`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Validate directory
|
|
87
|
+
let stats;
|
|
88
|
+
try {
|
|
89
|
+
stats = fs.statSync(rootDir);
|
|
90
|
+
} catch (error) {
|
|
91
|
+
console.error(`Error: directory "${rootDir}" does not exist or is not accessible.`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
if (!stats.isDirectory()) {
|
|
95
|
+
console.error(`Error: path "${rootDir}" is not a directory.`);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const logFile = getLogFile(port);
|
|
100
|
+
const out = fs.openSync(logFile, 'a');
|
|
101
|
+
const err = fs.openSync(logFile, 'a');
|
|
102
|
+
|
|
103
|
+
const child = spawn(process.execPath, [__filename, '--daemon-child', '-d', rootDir, '-p', String(port), '-H', host], {
|
|
104
|
+
detached: true,
|
|
105
|
+
stdio: ['ignore', out, err],
|
|
106
|
+
env: process.env
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
child.unref();
|
|
110
|
+
|
|
111
|
+
writePidFile(port, child.pid, rootDir);
|
|
112
|
+
|
|
113
|
+
console.log(`Server started in background (PID: ${child.pid})`);
|
|
114
|
+
console.log(`Serving: ${rootDir}`);
|
|
115
|
+
console.log(`Listening on port: ${port}`);
|
|
116
|
+
console.log(`Log file: ${logFile}`);
|
|
117
|
+
console.log(`\nTo stop: serve-here --stop -p ${port}`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function stopDaemon(port) {
|
|
121
|
+
const pidInfo = readPidFile(port);
|
|
122
|
+
if (!pidInfo) {
|
|
123
|
+
console.error(`No server running on port ${port}`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!isProcessRunning(pidInfo.pid)) {
|
|
128
|
+
console.log(`Server (PID: ${pidInfo.pid}) is not running, cleaning up...`);
|
|
129
|
+
removePidFile(port);
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
process.kill(pidInfo.pid, 'SIGTERM');
|
|
135
|
+
console.log(`Stopping server (PID: ${pidInfo.pid})...`);
|
|
136
|
+
|
|
137
|
+
// Wait for process to stop
|
|
138
|
+
let attempts = 0;
|
|
139
|
+
const checkInterval = setInterval(() => {
|
|
140
|
+
attempts++;
|
|
141
|
+
if (!isProcessRunning(pidInfo.pid)) {
|
|
142
|
+
clearInterval(checkInterval);
|
|
143
|
+
removePidFile(port);
|
|
144
|
+
console.log('Server stopped.');
|
|
145
|
+
process.exit(0);
|
|
146
|
+
} else if (attempts >= 10) {
|
|
147
|
+
clearInterval(checkInterval);
|
|
148
|
+
console.error('Server did not stop gracefully, force killing...');
|
|
149
|
+
try {
|
|
150
|
+
process.kill(pidInfo.pid, 'SIGKILL');
|
|
151
|
+
} catch (e) {}
|
|
152
|
+
removePidFile(port);
|
|
153
|
+
process.exit(0);
|
|
154
|
+
}
|
|
155
|
+
}, 500);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.error(`Failed to stop server: ${error.message}`);
|
|
158
|
+
removePidFile(port);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function showStatus(port) {
|
|
164
|
+
if (port) {
|
|
165
|
+
const pidInfo = readPidFile(port);
|
|
166
|
+
if (!pidInfo) {
|
|
167
|
+
console.log(`No server running on port ${port}`);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const running = isProcessRunning(pidInfo.pid);
|
|
172
|
+
console.log(`Port ${port}:`);
|
|
173
|
+
console.log(` PID: ${pidInfo.pid}`);
|
|
174
|
+
console.log(` Status: ${running ? 'running' : 'stopped'}`);
|
|
175
|
+
console.log(` Directory: ${pidInfo.rootDir}`);
|
|
176
|
+
console.log(` Log: ${getLogFile(port)}`);
|
|
177
|
+
|
|
178
|
+
if (!running) {
|
|
179
|
+
removePidFile(port);
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
// Show all
|
|
183
|
+
ensureDirectories();
|
|
184
|
+
const files = fs.readdirSync(PID_DIR).filter(f => f.endsWith('.pid'));
|
|
185
|
+
|
|
186
|
+
if (files.length === 0) {
|
|
187
|
+
console.log('No servers running.');
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
console.log('Running servers:\n');
|
|
192
|
+
for (const file of files) {
|
|
193
|
+
const match = file.match(/serve-here-(\d+)\.pid/);
|
|
194
|
+
if (match) {
|
|
195
|
+
const p = parseInt(match[1], 10);
|
|
196
|
+
const pidInfo = readPidFile(p);
|
|
197
|
+
if (pidInfo) {
|
|
198
|
+
const running = isProcessRunning(pidInfo.pid);
|
|
199
|
+
console.log(`Port ${p}:`);
|
|
200
|
+
console.log(` PID: ${pidInfo.pid}`);
|
|
201
|
+
console.log(` Status: ${running ? 'running' : 'stopped'}`);
|
|
202
|
+
console.log(` Directory: ${pidInfo.rootDir}`);
|
|
203
|
+
console.log('');
|
|
204
|
+
|
|
205
|
+
if (!running) {
|
|
206
|
+
removePidFile(p);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
13
213
|
|
|
14
214
|
function main() {
|
|
15
215
|
try {
|
|
@@ -25,6 +225,22 @@ function main() {
|
|
|
25
225
|
process.exit(0);
|
|
26
226
|
}
|
|
27
227
|
|
|
228
|
+
// Handle daemon commands
|
|
229
|
+
if (options.stop) {
|
|
230
|
+
stopDaemon(options.port || DEFAULT_PORT);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (options.status) {
|
|
235
|
+
showStatus(options.port);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (options.daemon && !options.daemonChild) {
|
|
240
|
+
startDaemon(options);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
28
244
|
const rootDir = path.resolve(options.directory || process.cwd());
|
|
29
245
|
|
|
30
246
|
let stats;
|
|
@@ -56,10 +272,19 @@ function main() {
|
|
|
56
272
|
...formatListeningAddresses(host, port)
|
|
57
273
|
];
|
|
58
274
|
console.log(messageLines.join('\n '));
|
|
275
|
+
|
|
276
|
+
// Write PID file when running as daemon child
|
|
277
|
+
if (options.daemonChild) {
|
|
278
|
+
ensureDirectories();
|
|
279
|
+
writePidFile(port, process.pid, rootDir);
|
|
280
|
+
}
|
|
59
281
|
});
|
|
60
282
|
|
|
61
283
|
const shutdown = () => {
|
|
62
284
|
console.log('\nShutting down...');
|
|
285
|
+
if (options.daemonChild) {
|
|
286
|
+
removePidFile(port);
|
|
287
|
+
}
|
|
63
288
|
server.close(() => process.exit(0));
|
|
64
289
|
};
|
|
65
290
|
|
|
@@ -77,7 +302,11 @@ function parseArguments(args) {
|
|
|
77
302
|
port: undefined,
|
|
78
303
|
host: undefined,
|
|
79
304
|
help: false,
|
|
80
|
-
version: false
|
|
305
|
+
version: false,
|
|
306
|
+
daemon: false,
|
|
307
|
+
daemonChild: false,
|
|
308
|
+
stop: false,
|
|
309
|
+
status: false
|
|
81
310
|
};
|
|
82
311
|
|
|
83
312
|
for (let i = 0; i < args.length; i += 1) {
|
|
@@ -105,6 +334,19 @@ function parseArguments(args) {
|
|
|
105
334
|
case '--host':
|
|
106
335
|
options.host = requireValue(args, ++i, '--host');
|
|
107
336
|
break;
|
|
337
|
+
case '-D':
|
|
338
|
+
case '--daemon':
|
|
339
|
+
options.daemon = true;
|
|
340
|
+
break;
|
|
341
|
+
case '--daemon-child':
|
|
342
|
+
options.daemonChild = true;
|
|
343
|
+
break;
|
|
344
|
+
case '--stop':
|
|
345
|
+
options.stop = true;
|
|
346
|
+
break;
|
|
347
|
+
case '--status':
|
|
348
|
+
options.status = true;
|
|
349
|
+
break;
|
|
108
350
|
default:
|
|
109
351
|
if (arg.startsWith('-')) {
|
|
110
352
|
throw new Error(`Unknown option: ${arg}`);
|
|
@@ -167,8 +409,19 @@ Options:
|
|
|
167
409
|
-d, --dir <path> Directory to serve (defaults to current working directory)
|
|
168
410
|
-p, --port <number> Port to listen on (default: ${DEFAULT_PORT})
|
|
169
411
|
-H, --host <address> Hostname or IP to bind (default: ${DEFAULT_HOST})
|
|
412
|
+
-D, --daemon Run as a background daemon (does not occupy terminal)
|
|
413
|
+
--stop Stop a running daemon (use with -p to specify port)
|
|
414
|
+
--status Show status of running daemon(s)
|
|
170
415
|
-h, --help Show this help message
|
|
171
|
-
-V, --version Show version
|
|
416
|
+
-V, --version Show version
|
|
417
|
+
|
|
418
|
+
Examples:
|
|
419
|
+
serve-here Start server in foreground on port 8080
|
|
420
|
+
serve-here -D Start server as daemon on port 8080
|
|
421
|
+
serve-here -D -p 3000 Start daemon on port 3000
|
|
422
|
+
serve-here --stop Stop daemon on port 8080
|
|
423
|
+
serve-here --stop -p 3000 Stop daemon on port 3000
|
|
424
|
+
serve-here --status Show all running daemons`);
|
|
172
425
|
}
|
|
173
426
|
|
|
174
427
|
main();
|