@mahidsec/nest 1.0.1 → 1.0.3
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/README.md +7 -3
- package/bin/nest.js +6 -1
- package/dist/server.js +3 -14
- package/package.json +2 -2
- package/src/server.ts +4 -17
package/README.md
CHANGED
|
@@ -43,15 +43,19 @@ The server starts at **http://localhost:6969**. Open it from any device on your
|
|
|
43
43
|
## Preview
|
|
44
44
|
|
|
45
45
|
<p align="center">
|
|
46
|
-
<img src="https://raw.githubusercontent.com/mahidsec/nest/main/screenshots/
|
|
46
|
+
<img src="https://raw.githubusercontent.com/mahidsec/nest/main/screenshots/home.png" alt="Home View" width="640" />
|
|
47
47
|
</p>
|
|
48
48
|
|
|
49
49
|
<p align="center">
|
|
50
|
-
<img src="https://raw.githubusercontent.com/mahidsec/nest/main/screenshots/
|
|
50
|
+
<img src="https://raw.githubusercontent.com/mahidsec/nest/main/screenshots/course-adding.png" alt="Course Adding" width="640" />
|
|
51
51
|
</p>
|
|
52
52
|
|
|
53
53
|
<p align="center">
|
|
54
|
-
<img src="https://raw.githubusercontent.com/mahidsec/nest/main/screenshots/
|
|
54
|
+
<img src="https://raw.githubusercontent.com/mahidsec/nest/main/screenshots/dashboard.png" alt="Nest Dashboard" width="640" />
|
|
55
|
+
</p>
|
|
56
|
+
|
|
57
|
+
<p align="center">
|
|
58
|
+
<img src="https://raw.githubusercontent.com/mahidsec/nest/main/screenshots/navigation.png" alt="Navigation" width="640" />
|
|
55
59
|
</p>
|
|
56
60
|
|
|
57
61
|
## Features
|
package/bin/nest.js
CHANGED
|
@@ -314,7 +314,12 @@ async function handleSelect() {
|
|
|
314
314
|
console.log(" \x1b[90mServer: http://localhost:" + PORT + "\x1b[0m");
|
|
315
315
|
console.log();
|
|
316
316
|
|
|
317
|
-
|
|
317
|
+
// Redirect server stdout/stderr so logs don't leak to terminal
|
|
318
|
+
if (serverProcess) {
|
|
319
|
+
serverProcess.stdout?.destroy();
|
|
320
|
+
serverProcess.stderr?.destroy();
|
|
321
|
+
serverProcess.unref();
|
|
322
|
+
}
|
|
318
323
|
|
|
319
324
|
spawn("node", [__filename, "--tray"], {
|
|
320
325
|
stdio: "ignore",
|
package/dist/server.js
CHANGED
|
@@ -198,15 +198,12 @@ function findCloudflared() {
|
|
|
198
198
|
} catch {
|
|
199
199
|
}
|
|
200
200
|
if (fs.existsSync(CLOUDFLARED_PATH)) return CLOUDFLARED_PATH;
|
|
201
|
-
console.log("[Tunnel] cloudflared not found \u2014 downloading...");
|
|
202
201
|
try {
|
|
203
202
|
if (!fs.existsSync(NEST_BIN_DIR)) fs.mkdirSync(NEST_BIN_DIR, { recursive: true });
|
|
204
203
|
execSync(`curl -fSL -o "${CLOUDFLARED_PATH}" "${getCloudflaredDownloadUrl()}"`, { stdio: "inherit" });
|
|
205
204
|
fs.chmodSync(CLOUDFLARED_PATH, 493);
|
|
206
|
-
console.log("[Tunnel] cloudflared installed");
|
|
207
205
|
return CLOUDFLARED_PATH;
|
|
208
206
|
} catch {
|
|
209
|
-
console.error("[Tunnel] Failed to download cloudflared");
|
|
210
207
|
return null;
|
|
211
208
|
}
|
|
212
209
|
}
|
|
@@ -286,8 +283,7 @@ app.get("/api/courses", async (_req, res) => {
|
|
|
286
283
|
totalVideos: await getCachedVideoCount(c.localPath)
|
|
287
284
|
})));
|
|
288
285
|
res.json(enriched);
|
|
289
|
-
} catch
|
|
290
|
-
console.error("[Courses] List error:", err);
|
|
286
|
+
} catch {
|
|
291
287
|
res.status(500).json({ error: "Failed to load courses" });
|
|
292
288
|
}
|
|
293
289
|
});
|
|
@@ -317,7 +313,6 @@ app.post("/api/courses", async (req, res) => {
|
|
|
317
313
|
courses.push(course);
|
|
318
314
|
await saveCourses(courses);
|
|
319
315
|
invalidateVideoCount(resolved);
|
|
320
|
-
console.log(`[Courses] Added "${name}" \u2192 ${resolved}`);
|
|
321
316
|
res.json({ success: true, course });
|
|
322
317
|
});
|
|
323
318
|
app.delete("/api/courses/:id", async (req, res) => {
|
|
@@ -326,7 +321,6 @@ app.delete("/api/courses/:id", async (req, res) => {
|
|
|
326
321
|
if (!target) return res.status(404).json({ error: "Course not found" });
|
|
327
322
|
invalidateVideoCount(target.localPath);
|
|
328
323
|
await saveCourses(courses.filter((c) => c.id !== req.params.id));
|
|
329
|
-
console.log(`[Courses] Removed course ${req.params.id}`);
|
|
330
324
|
res.json({ success: true });
|
|
331
325
|
});
|
|
332
326
|
app.get("/api/courses/:id/browse", async (req, res) => {
|
|
@@ -342,8 +336,7 @@ app.get("/api/courses/:id/browse", async (req, res) => {
|
|
|
342
336
|
const result = await scanDirectory(course.localPath, course.localPath);
|
|
343
337
|
invalidateVideoCount(course.localPath);
|
|
344
338
|
res.json({ ...course, ...result });
|
|
345
|
-
} catch
|
|
346
|
-
console.error("[Courses] Browse error:", err);
|
|
339
|
+
} catch {
|
|
347
340
|
res.status(500).json({ error: "Failed to scan directory" });
|
|
348
341
|
}
|
|
349
342
|
});
|
|
@@ -442,8 +435,7 @@ app.get("/api/courses/:id/file", async (req, res) => {
|
|
|
442
435
|
"Cache-Control": "public, max-age=3600"
|
|
443
436
|
});
|
|
444
437
|
safePipe(fs.createReadStream(realResolved), res);
|
|
445
|
-
} catch
|
|
446
|
-
console.error("[Courses] File error:", err);
|
|
438
|
+
} catch {
|
|
447
439
|
res.status(500).json({ error: "Failed to serve file" });
|
|
448
440
|
}
|
|
449
441
|
});
|
|
@@ -472,9 +464,6 @@ app.get("*", (_req, res) => {
|
|
|
472
464
|
}
|
|
473
465
|
});
|
|
474
466
|
httpServer.listen(PORT, "0.0.0.0", () => {
|
|
475
|
-
console.log(`[Nest] Server running on http://localhost:${PORT}`);
|
|
476
|
-
console.log(`[Nest] Data dir: ${DATA_DIR}`);
|
|
477
|
-
if (IS_TUNNEL) console.log(`[Nest] Tunnel mode: enabled`);
|
|
478
467
|
});
|
|
479
468
|
var shuttingDown = false;
|
|
480
469
|
var shutdown = (signal) => {
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mahidsec/nest",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Standalone local course viewer with Moonlight themes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"
|
|
7
|
+
"nestviewer": "./bin/nest.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"build": "cd frontend && npm run build && cd .. && node esbuild.config.mjs",
|
package/src/server.ts
CHANGED
|
@@ -199,16 +199,12 @@ function findCloudflared(): string | null {
|
|
|
199
199
|
} catch {}
|
|
200
200
|
// 2. Check ~/.nest/bin/cloudflared
|
|
201
201
|
if (fs.existsSync(CLOUDFLARED_PATH)) return CLOUDFLARED_PATH;
|
|
202
|
-
// 3. Auto-download
|
|
203
|
-
console.log('[Tunnel] cloudflared not found — downloading...');
|
|
204
202
|
try {
|
|
205
203
|
if (!fs.existsSync(NEST_BIN_DIR)) fs.mkdirSync(NEST_BIN_DIR, { recursive: true });
|
|
206
204
|
execSync(`curl -fSL -o "${CLOUDFLARED_PATH}" "${getCloudflaredDownloadUrl()}"`, { stdio: 'inherit' });
|
|
207
205
|
fs.chmodSync(CLOUDFLARED_PATH, 0o755);
|
|
208
|
-
console.log('[Tunnel] cloudflared installed');
|
|
209
206
|
return CLOUDFLARED_PATH;
|
|
210
207
|
} catch {
|
|
211
|
-
console.error('[Tunnel] Failed to download cloudflared');
|
|
212
208
|
return null;
|
|
213
209
|
}
|
|
214
210
|
}
|
|
@@ -295,8 +291,7 @@ app.get('/api/courses', async (_req, res) => {
|
|
|
295
291
|
totalVideos: await getCachedVideoCount(c.localPath),
|
|
296
292
|
})));
|
|
297
293
|
res.json(enriched);
|
|
298
|
-
} catch
|
|
299
|
-
console.error('[Courses] List error:', err);
|
|
294
|
+
} catch {
|
|
300
295
|
res.status(500).json({ error: 'Failed to load courses' });
|
|
301
296
|
}
|
|
302
297
|
});
|
|
@@ -333,7 +328,6 @@ app.post('/api/courses', async (req, res) => {
|
|
|
333
328
|
courses.push(course);
|
|
334
329
|
await saveCourses(courses);
|
|
335
330
|
invalidateVideoCount(resolved);
|
|
336
|
-
console.log(`[Courses] Added "${name}" → ${resolved}`);
|
|
337
331
|
res.json({ success: true, course });
|
|
338
332
|
});
|
|
339
333
|
|
|
@@ -343,7 +337,6 @@ app.delete('/api/courses/:id', async (req, res) => {
|
|
|
343
337
|
if (!target) return res.status(404).json({ error: 'Course not found' });
|
|
344
338
|
invalidateVideoCount(target.localPath);
|
|
345
339
|
await saveCourses(courses.filter((c) => c.id !== req.params.id));
|
|
346
|
-
console.log(`[Courses] Removed course ${req.params.id}`);
|
|
347
340
|
res.json({ success: true });
|
|
348
341
|
});
|
|
349
342
|
|
|
@@ -362,8 +355,7 @@ app.get('/api/courses/:id/browse', async (req, res) => {
|
|
|
362
355
|
const result = await scanDirectory(course.localPath, course.localPath);
|
|
363
356
|
invalidateVideoCount(course.localPath);
|
|
364
357
|
res.json({ ...course, ...result });
|
|
365
|
-
} catch
|
|
366
|
-
console.error('[Courses] Browse error:', err);
|
|
358
|
+
} catch {
|
|
367
359
|
res.status(500).json({ error: 'Failed to scan directory' });
|
|
368
360
|
}
|
|
369
361
|
});
|
|
@@ -463,8 +455,7 @@ app.get('/api/courses/:id/file', async (req, res) => {
|
|
|
463
455
|
'Cache-Control': 'public, max-age=3600',
|
|
464
456
|
});
|
|
465
457
|
safePipe(fs.createReadStream(realResolved), res);
|
|
466
|
-
} catch
|
|
467
|
-
console.error('[Courses] File error:', err);
|
|
458
|
+
} catch {
|
|
468
459
|
res.status(500).json({ error: 'Failed to serve file' });
|
|
469
460
|
}
|
|
470
461
|
});
|
|
@@ -500,11 +491,7 @@ app.get('*', (_req, res) => {
|
|
|
500
491
|
});
|
|
501
492
|
|
|
502
493
|
// ─── Start ───
|
|
503
|
-
httpServer.listen(PORT, '0.0.0.0', () => {
|
|
504
|
-
console.log(`[Nest] Server running on http://localhost:${PORT}`);
|
|
505
|
-
console.log(`[Nest] Data dir: ${DATA_DIR}`);
|
|
506
|
-
if (IS_TUNNEL) console.log(`[Nest] Tunnel mode: enabled`);
|
|
507
|
-
});
|
|
494
|
+
httpServer.listen(PORT, '0.0.0.0', () => {});
|
|
508
495
|
|
|
509
496
|
// ─── Graceful Shutdown ───
|
|
510
497
|
let shuttingDown = false;
|