@mahidsec/nest 1.0.2 → 1.0.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/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
- if (serverProcess) serverProcess.unref();
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
@@ -141,11 +141,14 @@ var countVideoFiles = async (dirPath) => {
141
141
  const videoExts = [".mp4", ".mkv", ".avi", ".mov", ".webm", ".m4v"];
142
142
  try {
143
143
  const entries = await readdir(dirPath, { withFileTypes: true });
144
+ const promises = [];
144
145
  for (const entry of entries) {
145
146
  if (entry.name.startsWith(".")) continue;
146
- if (entry.isDirectory()) count += await countVideoFiles(path.join(dirPath, entry.name));
147
+ if (entry.isDirectory()) promises.push(countVideoFiles(path.join(dirPath, entry.name)));
147
148
  else if (videoExts.includes(path.extname(entry.name).toLowerCase())) count++;
148
149
  }
150
+ const results = await Promise.all(promises);
151
+ count += results.reduce((a, b) => a + b, 0);
149
152
  } catch {
150
153
  }
151
154
  return count;
@@ -153,16 +156,47 @@ var countVideoFiles = async (dirPath) => {
153
156
  var videoCountCache = /* @__PURE__ */ new Map();
154
157
  var CACHE_TTL = 3e4;
155
158
  var CACHE_MAX = 200;
156
- var getCachedVideoCount = async (dirPath) => {
159
+ var triggerVideoCountUpdate = async (course) => {
160
+ const dirPath = course.localPath;
161
+ let cached = videoCountCache.get(dirPath);
162
+ if (!cached) {
163
+ cached = { count: course.totalVideos || 0, ts: 0, updating: true };
164
+ if (videoCountCache.size >= CACHE_MAX) {
165
+ const oldest = videoCountCache.keys().next().value;
166
+ if (oldest) videoCountCache.delete(oldest);
167
+ }
168
+ videoCountCache.set(dirPath, cached);
169
+ } else {
170
+ cached.updating = true;
171
+ }
172
+ try {
173
+ const count = await countVideoFiles(dirPath);
174
+ videoCountCache.set(dirPath, { count, ts: Date.now(), updating: false });
175
+ if (course.totalVideos !== count) {
176
+ const allCourses = await getCourses();
177
+ const target = allCourses.find((c) => c.id === course.id);
178
+ if (target && target.totalVideos !== count) {
179
+ target.totalVideos = count;
180
+ await saveCourses(allCourses);
181
+ }
182
+ }
183
+ } catch (err) {
184
+ if (cached) cached.updating = false;
185
+ }
186
+ };
187
+ var getCachedVideoCount = (course) => {
188
+ const dirPath = course.localPath;
157
189
  const cached = videoCountCache.get(dirPath);
158
- if (cached && Date.now() - cached.ts < CACHE_TTL) return cached.count;
159
- const count = await countVideoFiles(dirPath);
160
- if (videoCountCache.size >= CACHE_MAX) {
161
- const oldest = videoCountCache.keys().next().value;
162
- if (oldest) videoCountCache.delete(oldest);
190
+ if (cached) {
191
+ if (Date.now() - cached.ts > CACHE_TTL && !cached.updating) {
192
+ triggerVideoCountUpdate(course).catch(() => {
193
+ });
194
+ }
195
+ return cached.count;
163
196
  }
164
- videoCountCache.set(dirPath, { count, ts: Date.now() });
165
- return count;
197
+ triggerVideoCountUpdate(course).catch(() => {
198
+ });
199
+ return course.totalVideos || 0;
166
200
  };
167
201
  var invalidateVideoCount = (dirPath) => {
168
202
  videoCountCache.delete(dirPath);
@@ -198,15 +232,12 @@ function findCloudflared() {
198
232
  } catch {
199
233
  }
200
234
  if (fs.existsSync(CLOUDFLARED_PATH)) return CLOUDFLARED_PATH;
201
- console.log("[Tunnel] cloudflared not found \u2014 downloading...");
202
235
  try {
203
236
  if (!fs.existsSync(NEST_BIN_DIR)) fs.mkdirSync(NEST_BIN_DIR, { recursive: true });
204
237
  execSync(`curl -fSL -o "${CLOUDFLARED_PATH}" "${getCloudflaredDownloadUrl()}"`, { stdio: "inherit" });
205
238
  fs.chmodSync(CLOUDFLARED_PATH, 493);
206
- console.log("[Tunnel] cloudflared installed");
207
239
  return CLOUDFLARED_PATH;
208
240
  } catch {
209
- console.error("[Tunnel] Failed to download cloudflared");
210
241
  return null;
211
242
  }
212
243
  }
@@ -281,13 +312,12 @@ app.post("/api/tunnel/stop", (_req, res) => {
281
312
  app.get("/api/courses", async (_req, res) => {
282
313
  try {
283
314
  const courses = await getCourses();
284
- const enriched = await Promise.all(courses.map(async (c) => ({
315
+ const enriched = courses.map((c) => ({
285
316
  ...c,
286
- totalVideos: await getCachedVideoCount(c.localPath)
287
- })));
317
+ totalVideos: getCachedVideoCount(c)
318
+ }));
288
319
  res.json(enriched);
289
- } catch (err) {
290
- console.error("[Courses] List error:", err);
320
+ } catch {
291
321
  res.status(500).json({ error: "Failed to load courses" });
292
322
  }
293
323
  });
@@ -317,7 +347,6 @@ app.post("/api/courses", async (req, res) => {
317
347
  courses.push(course);
318
348
  await saveCourses(courses);
319
349
  invalidateVideoCount(resolved);
320
- console.log(`[Courses] Added "${name}" \u2192 ${resolved}`);
321
350
  res.json({ success: true, course });
322
351
  });
323
352
  app.delete("/api/courses/:id", async (req, res) => {
@@ -326,9 +355,16 @@ app.delete("/api/courses/:id", async (req, res) => {
326
355
  if (!target) return res.status(404).json({ error: "Course not found" });
327
356
  invalidateVideoCount(target.localPath);
328
357
  await saveCourses(courses.filter((c) => c.id !== req.params.id));
329
- console.log(`[Courses] Removed course ${req.params.id}`);
330
358
  res.json({ success: true });
331
359
  });
360
+ app.get("/api/courses/progress", async (_req, res) => {
361
+ const all = await getCourseProgressData();
362
+ const result = {};
363
+ for (const [courseId, files] of Object.entries(all)) {
364
+ result[courseId] = Object.keys(files).length;
365
+ }
366
+ res.json(result);
367
+ });
332
368
  app.get("/api/courses/:id/browse", async (req, res) => {
333
369
  const courses = await getCourses();
334
370
  const course = courses.find((c) => c.id === req.params.id);
@@ -342,8 +378,7 @@ app.get("/api/courses/:id/browse", async (req, res) => {
342
378
  const result = await scanDirectory(course.localPath, course.localPath);
343
379
  invalidateVideoCount(course.localPath);
344
380
  res.json({ ...course, ...result });
345
- } catch (err) {
346
- console.error("[Courses] Browse error:", err);
381
+ } catch {
347
382
  res.status(500).json({ error: "Failed to scan directory" });
348
383
  }
349
384
  });
@@ -442,8 +477,7 @@ app.get("/api/courses/:id/file", async (req, res) => {
442
477
  "Cache-Control": "public, max-age=3600"
443
478
  });
444
479
  safePipe(fs.createReadStream(realResolved), res);
445
- } catch (err) {
446
- console.error("[Courses] File error:", err);
480
+ } catch {
447
481
  res.status(500).json({ error: "Failed to serve file" });
448
482
  }
449
483
  });
@@ -472,9 +506,6 @@ app.get("*", (_req, res) => {
472
506
  }
473
507
  });
474
508
  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
509
  });
479
510
  var shuttingDown = false;
480
511
  var shutdown = (signal) => {