@zhin.js/console 1.0.48 → 1.0.50

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/lib/index.js CHANGED
@@ -2,14 +2,43 @@ import { usePlugin } from '@zhin.js/core';
2
2
  import mime from 'mime';
3
3
  import * as fs from 'fs';
4
4
  import fs__default from 'fs';
5
- import * as path3 from 'path';
6
- import path3__default from 'path';
5
+ import * as path from 'path';
6
+ import path__default from 'path';
7
+ import * as crypto from 'crypto';
7
8
  import WebSocket from 'ws';
8
9
  import { transform } from 'esbuild';
9
10
 
10
11
  // src/index.ts
11
12
  var { root, logger } = usePlugin();
12
13
  var ENV_WHITELIST = [".env", ".env.development", ".env.production"];
14
+ var FILE_MANAGER_ALLOWED = [
15
+ "src",
16
+ "plugins",
17
+ "client",
18
+ "package.json",
19
+ "tsconfig.json",
20
+ "zhin.config.yml",
21
+ ".env",
22
+ ".env.development",
23
+ ".env.production",
24
+ "README.md"
25
+ ];
26
+ var FILE_MANAGER_BLOCKED = /* @__PURE__ */ new Set([
27
+ "node_modules",
28
+ ".git",
29
+ ".env.local",
30
+ "data",
31
+ "lib",
32
+ "dist",
33
+ "coverage"
34
+ ]);
35
+ function isPathAllowed(relativePath) {
36
+ if (relativePath.includes("..") || path__default.isAbsolute(relativePath)) return false;
37
+ const normalized = relativePath.replace(/\\/g, "/").replace(/^\.\//, "");
38
+ const firstSegment = normalized.split("/")[0];
39
+ if (FILE_MANAGER_BLOCKED.has(firstSegment)) return false;
40
+ return FILE_MANAGER_ALLOWED.some((p) => normalized === p || normalized.startsWith(p + "/"));
41
+ }
13
42
  function resolveConfigKey(pluginName) {
14
43
  const schemaService = root.inject("schema");
15
44
  return schemaService?.resolveConfigKey(pluginName) ?? pluginName;
@@ -24,7 +53,7 @@ function getPluginKeys() {
24
53
  return Array.from(keys);
25
54
  }
26
55
  function getConfigFilePath() {
27
- return path3__default.resolve(process.cwd(), "zhin.config.yml");
56
+ return path__default.resolve(process.cwd(), "zhin.config.yml");
28
57
  }
29
58
  function setupWebSocket(webServer) {
30
59
  webServer.ws.on("connection", (ws) => {
@@ -201,7 +230,7 @@ async function handleWebSocketMessage(ws, message, webServer) {
201
230
  const cwd = process.cwd();
202
231
  const files = ENV_WHITELIST.map((name) => ({
203
232
  name,
204
- exists: fs__default.existsSync(path3__default.resolve(cwd, name))
233
+ exists: fs__default.existsSync(path__default.resolve(cwd, name))
205
234
  }));
206
235
  ws.send(JSON.stringify({ requestId, data: { files } }));
207
236
  } catch (error) {
@@ -215,7 +244,7 @@ async function handleWebSocketMessage(ws, message, webServer) {
215
244
  ws.send(JSON.stringify({ requestId, error: `Invalid env file: ${filename}` }));
216
245
  break;
217
246
  }
218
- const envPath = path3__default.resolve(process.cwd(), filename);
247
+ const envPath = path__default.resolve(process.cwd(), filename);
219
248
  const content = fs__default.existsSync(envPath) ? fs__default.readFileSync(envPath, "utf-8") : "";
220
249
  ws.send(JSON.stringify({ requestId, data: { content } }));
221
250
  } catch (error) {
@@ -233,17 +262,420 @@ async function handleWebSocketMessage(ws, message, webServer) {
233
262
  ws.send(JSON.stringify({ requestId, error: "content field is required" }));
234
263
  break;
235
264
  }
236
- const envPath = path3__default.resolve(process.cwd(), filename);
265
+ const envPath = path__default.resolve(process.cwd(), filename);
237
266
  fs__default.writeFileSync(envPath, content, "utf-8");
238
267
  ws.send(JSON.stringify({ requestId, data: { success: true, message: "\u73AF\u5883\u53D8\u91CF\u5DF2\u4FDD\u5B58\uFF0C\u9700\u91CD\u542F\u751F\u6548" } }));
239
268
  } catch (error) {
240
269
  ws.send(JSON.stringify({ requestId, error: `Failed to save env file: ${error.message}` }));
241
270
  }
242
271
  break;
272
+ // ================================================================
273
+ // 文件管理
274
+ // ================================================================
275
+ case "files:tree":
276
+ try {
277
+ const cwd = process.cwd();
278
+ const tree = buildFileTree(cwd, "", FILE_MANAGER_ALLOWED);
279
+ ws.send(JSON.stringify({ requestId, data: { tree } }));
280
+ } catch (error) {
281
+ ws.send(JSON.stringify({ requestId, error: `Failed to build file tree: ${error.message}` }));
282
+ }
283
+ break;
284
+ case "files:read":
285
+ try {
286
+ const { filePath: fp } = message;
287
+ if (!fp || !isPathAllowed(fp)) {
288
+ ws.send(JSON.stringify({ requestId, error: `Access denied: ${fp}` }));
289
+ break;
290
+ }
291
+ const absPath = path__default.resolve(process.cwd(), fp);
292
+ if (!fs__default.existsSync(absPath)) {
293
+ ws.send(JSON.stringify({ requestId, error: `File not found: ${fp}` }));
294
+ break;
295
+ }
296
+ const stat = fs__default.statSync(absPath);
297
+ if (!stat.isFile()) {
298
+ ws.send(JSON.stringify({ requestId, error: `Not a file: ${fp}` }));
299
+ break;
300
+ }
301
+ if (stat.size > 1024 * 1024) {
302
+ ws.send(JSON.stringify({ requestId, error: `File too large: ${(stat.size / 1024).toFixed(0)}KB (max 1MB)` }));
303
+ break;
304
+ }
305
+ const fileContent = fs__default.readFileSync(absPath, "utf-8");
306
+ ws.send(JSON.stringify({ requestId, data: { content: fileContent, size: stat.size } }));
307
+ } catch (error) {
308
+ ws.send(JSON.stringify({ requestId, error: `Failed to read file: ${error.message}` }));
309
+ }
310
+ break;
311
+ case "files:save":
312
+ try {
313
+ const { filePath: fp, content: fileContent } = message;
314
+ if (!fp || !isPathAllowed(fp)) {
315
+ ws.send(JSON.stringify({ requestId, error: `Access denied: ${fp}` }));
316
+ break;
317
+ }
318
+ if (typeof fileContent !== "string") {
319
+ ws.send(JSON.stringify({ requestId, error: "content field is required" }));
320
+ break;
321
+ }
322
+ const absPath = path__default.resolve(process.cwd(), fp);
323
+ const dir = path__default.dirname(absPath);
324
+ if (!fs__default.existsSync(dir)) {
325
+ fs__default.mkdirSync(dir, { recursive: true });
326
+ }
327
+ fs__default.writeFileSync(absPath, fileContent, "utf-8");
328
+ ws.send(JSON.stringify({ requestId, data: { success: true, message: `\u6587\u4EF6\u5DF2\u4FDD\u5B58: ${fp}` } }));
329
+ } catch (error) {
330
+ ws.send(JSON.stringify({ requestId, error: `Failed to save file: ${error.message}` }));
331
+ }
332
+ break;
333
+ // ================================================================
334
+ // 数据库管理
335
+ // ================================================================
336
+ case "db:info":
337
+ try {
338
+ const dbInfo = getDatabaseInfo();
339
+ ws.send(JSON.stringify({ requestId, data: dbInfo }));
340
+ } catch (error) {
341
+ ws.send(JSON.stringify({ requestId, error: `Failed to get db info: ${error.message}` }));
342
+ }
343
+ break;
344
+ case "db:tables":
345
+ try {
346
+ const tables = getDatabaseTables();
347
+ ws.send(JSON.stringify({ requestId, data: { tables } }));
348
+ } catch (error) {
349
+ ws.send(JSON.stringify({ requestId, error: `Failed to list tables: ${error.message}` }));
350
+ }
351
+ break;
352
+ case "db:select":
353
+ try {
354
+ const { table, page = 1, pageSize = 50, where } = message;
355
+ if (!table) {
356
+ ws.send(JSON.stringify({ requestId, error: "table is required" }));
357
+ break;
358
+ }
359
+ const selectResult = await dbSelect(table, page, pageSize, where);
360
+ ws.send(JSON.stringify({ requestId, data: selectResult }));
361
+ } catch (error) {
362
+ ws.send(JSON.stringify({ requestId, error: `Failed to select: ${error.message}` }));
363
+ }
364
+ break;
365
+ case "db:insert":
366
+ try {
367
+ const { table, row } = message;
368
+ if (!table || !row) {
369
+ ws.send(JSON.stringify({ requestId, error: "table and row are required" }));
370
+ break;
371
+ }
372
+ await dbInsert(table, row);
373
+ ws.send(JSON.stringify({ requestId, data: { success: true } }));
374
+ } catch (error) {
375
+ ws.send(JSON.stringify({ requestId, error: `Failed to insert: ${error.message}` }));
376
+ }
377
+ break;
378
+ case "db:update":
379
+ try {
380
+ const { table, row, where: updateWhere } = message;
381
+ if (!table || !row || !updateWhere) {
382
+ ws.send(JSON.stringify({ requestId, error: "table, row, and where are required" }));
383
+ break;
384
+ }
385
+ const affected = await dbUpdate(table, row, updateWhere);
386
+ ws.send(JSON.stringify({ requestId, data: { success: true, affected } }));
387
+ } catch (error) {
388
+ ws.send(JSON.stringify({ requestId, error: `Failed to update: ${error.message}` }));
389
+ }
390
+ break;
391
+ case "db:delete":
392
+ try {
393
+ const { table, where: deleteWhere } = message;
394
+ if (!table || !deleteWhere) {
395
+ ws.send(JSON.stringify({ requestId, error: "table and where are required" }));
396
+ break;
397
+ }
398
+ const deleted = await dbDelete(table, deleteWhere);
399
+ ws.send(JSON.stringify({ requestId, data: { success: true, deleted } }));
400
+ } catch (error) {
401
+ ws.send(JSON.stringify({ requestId, error: `Failed to delete: ${error.message}` }));
402
+ }
403
+ break;
404
+ case "db:drop-table":
405
+ try {
406
+ const { table: dropTableName } = message;
407
+ if (!dropTableName) {
408
+ ws.send(JSON.stringify({ requestId, error: "table is required" }));
409
+ break;
410
+ }
411
+ await dbDropTable(dropTableName);
412
+ ws.send(JSON.stringify({ requestId, data: { success: true } }));
413
+ } catch (error) {
414
+ ws.send(JSON.stringify({ requestId, error: `Failed to drop table: ${error.message}` }));
415
+ }
416
+ break;
417
+ // KV 专用操作
418
+ case "db:kv:get":
419
+ try {
420
+ const { table, key } = message;
421
+ if (!table || !key) {
422
+ ws.send(JSON.stringify({ requestId, error: "table and key are required" }));
423
+ break;
424
+ }
425
+ const kvValue = await kvGet(table, key);
426
+ ws.send(JSON.stringify({ requestId, data: { key, value: kvValue } }));
427
+ } catch (error) {
428
+ ws.send(JSON.stringify({ requestId, error: `Failed to get kv: ${error.message}` }));
429
+ }
430
+ break;
431
+ case "db:kv:set":
432
+ try {
433
+ const { table, key, value, ttl } = message;
434
+ if (!table || !key) {
435
+ ws.send(JSON.stringify({ requestId, error: "table and key are required" }));
436
+ break;
437
+ }
438
+ await kvSet(table, key, value, ttl);
439
+ ws.send(JSON.stringify({ requestId, data: { success: true } }));
440
+ } catch (error) {
441
+ ws.send(JSON.stringify({ requestId, error: `Failed to set kv: ${error.message}` }));
442
+ }
443
+ break;
444
+ case "db:kv:delete":
445
+ try {
446
+ const { table, key } = message;
447
+ if (!table || !key) {
448
+ ws.send(JSON.stringify({ requestId, error: "table and key are required" }));
449
+ break;
450
+ }
451
+ await kvDelete(table, key);
452
+ ws.send(JSON.stringify({ requestId, data: { success: true } }));
453
+ } catch (error) {
454
+ ws.send(JSON.stringify({ requestId, error: `Failed to delete kv: ${error.message}` }));
455
+ }
456
+ break;
457
+ case "db:kv:entries":
458
+ try {
459
+ const { table } = message;
460
+ if (!table) {
461
+ ws.send(JSON.stringify({ requestId, error: "table is required" }));
462
+ break;
463
+ }
464
+ const kvEntries = await kvGetEntries(table);
465
+ ws.send(JSON.stringify({ requestId, data: { entries: kvEntries } }));
466
+ } catch (error) {
467
+ ws.send(JSON.stringify({ requestId, error: `Failed to get entries: ${error.message}` }));
468
+ }
469
+ break;
243
470
  default:
244
471
  ws.send(JSON.stringify({ requestId, error: `Unknown message type: ${type}` }));
245
472
  }
246
473
  }
474
+ function getDb() {
475
+ return root.inject("database");
476
+ }
477
+ function getDbType() {
478
+ const dbFeature = getDb();
479
+ const dialectName = dbFeature.db.dialect.name;
480
+ if (["mongodb"].includes(dialectName)) return "document";
481
+ if (["redis"].includes(dialectName)) return "keyvalue";
482
+ return "related";
483
+ }
484
+ function getDatabaseInfo() {
485
+ const dbFeature = getDb();
486
+ const db = dbFeature.db;
487
+ return {
488
+ dialect: db.dialectName,
489
+ type: getDbType(),
490
+ tables: Array.from(db.models.keys())
491
+ };
492
+ }
493
+ function getDatabaseTables() {
494
+ const dbFeature = getDb();
495
+ const db = dbFeature.db;
496
+ getDbType();
497
+ const tables = [];
498
+ for (const [name] of db.models) {
499
+ const def = db.definitions.get(name);
500
+ tables.push({ name, columns: def ? Object.fromEntries(Object.entries(def).map(([col, colDef]) => [col, colDef])) : void 0 });
501
+ }
502
+ return tables;
503
+ }
504
+ async function dbSelect(table, page, pageSize, where) {
505
+ const dbFeature = getDb();
506
+ const db = dbFeature.db;
507
+ const dbType = getDbType();
508
+ const model = db.models.get(table);
509
+ if (!model) throw new Error(`Table '${table}' not found`);
510
+ if (dbType === "keyvalue") {
511
+ const kvModel = model;
512
+ const allEntries = await kvModel.entries();
513
+ const total2 = allEntries.length;
514
+ const start = (page - 1) * pageSize;
515
+ const rows2 = allEntries.slice(start, start + pageSize).map(([k, v]) => ({ key: k, value: v }));
516
+ return { rows: rows2, total: total2, page, pageSize };
517
+ }
518
+ let selection = model.select();
519
+ if (where && Object.keys(where).length > 0) {
520
+ selection = selection.where(where);
521
+ }
522
+ let total;
523
+ try {
524
+ const countResult = await db.aggregate(table).count("*", "total").where(where || {});
525
+ total = countResult?.[0]?.total ?? 0;
526
+ } catch {
527
+ const all = await model.select();
528
+ total = all.length;
529
+ }
530
+ const offset = (page - 1) * pageSize;
531
+ let query = model.select();
532
+ if (where && Object.keys(where).length > 0) {
533
+ query = query.where(where);
534
+ }
535
+ const rows = await query.limit(pageSize).offset(offset);
536
+ return { rows, total, page, pageSize };
537
+ }
538
+ async function dbInsert(table, row) {
539
+ const dbFeature = getDb();
540
+ const db = dbFeature.db;
541
+ const dbType = getDbType();
542
+ const model = db.models.get(table);
543
+ if (!model) throw new Error(`Table '${table}' not found`);
544
+ if (dbType === "keyvalue") {
545
+ const kvModel = model;
546
+ if (!row.key) throw new Error("key is required for KV insert");
547
+ await kvModel.set(row.key, row.value);
548
+ return;
549
+ }
550
+ if (dbType === "document") {
551
+ await model.create(row);
552
+ return;
553
+ }
554
+ await model.insert(row);
555
+ }
556
+ async function dbUpdate(table, row, where) {
557
+ const dbFeature = getDb();
558
+ const db = dbFeature.db;
559
+ const dbType = getDbType();
560
+ const model = db.models.get(table);
561
+ if (!model) throw new Error(`Table '${table}' not found`);
562
+ if (dbType === "keyvalue") {
563
+ const kvModel = model;
564
+ if (!where.key) throw new Error("key is required for KV update");
565
+ await kvModel.set(where.key, row.value);
566
+ return 1;
567
+ }
568
+ if (dbType === "document") {
569
+ if (where._id) {
570
+ return await model.updateById(where._id, row);
571
+ }
572
+ }
573
+ return await model.update(row).where(where);
574
+ }
575
+ async function dbDelete(table, where) {
576
+ const dbFeature = getDb();
577
+ const db = dbFeature.db;
578
+ const dbType = getDbType();
579
+ const model = db.models.get(table);
580
+ if (!model) throw new Error(`Table '${table}' not found`);
581
+ if (dbType === "keyvalue") {
582
+ const kvModel = model;
583
+ if (!where.key) throw new Error("key is required for KV delete");
584
+ await kvModel.deleteByKey(where.key);
585
+ return 1;
586
+ }
587
+ if (dbType === "document") {
588
+ if (where._id) {
589
+ return await model.deleteById(where._id);
590
+ }
591
+ }
592
+ return await model.delete(where);
593
+ }
594
+ async function dbDropTable(table) {
595
+ const dbFeature = getDb();
596
+ const db = dbFeature.db;
597
+ const model = db.models.get(table);
598
+ if (!model) throw new Error(`Table '${table}' not found`);
599
+ const sql = db.dialect.formatDropTable(table, true);
600
+ await db.query(sql);
601
+ db.models.delete(table);
602
+ db.definitions.delete(table);
603
+ }
604
+ async function kvGet(table, key) {
605
+ const dbFeature = getDb();
606
+ const model = dbFeature.db.models.get(table);
607
+ if (!model) throw new Error(`Bucket '${table}' not found`);
608
+ return await model.get(key);
609
+ }
610
+ async function kvSet(table, key, value, ttl) {
611
+ const dbFeature = getDb();
612
+ const model = dbFeature.db.models.get(table);
613
+ if (!model) throw new Error(`Bucket '${table}' not found`);
614
+ await model.set(key, value, ttl);
615
+ }
616
+ async function kvDelete(table, key) {
617
+ const dbFeature = getDb();
618
+ const model = dbFeature.db.models.get(table);
619
+ if (!model) throw new Error(`Bucket '${table}' not found`);
620
+ await model.deleteByKey(key);
621
+ }
622
+ async function kvGetEntries(table) {
623
+ const dbFeature = getDb();
624
+ const model = dbFeature.db.models.get(table);
625
+ if (!model) throw new Error(`Bucket '${table}' not found`);
626
+ const entries = await model.entries();
627
+ return entries.map(([k, v]) => ({ key: k, value: v }));
628
+ }
629
+ function buildFileTree(cwd, relativePath, allowed) {
630
+ const tree = [];
631
+ path__default.resolve(cwd, relativePath);
632
+ for (const entry of allowed) {
633
+ const entryRelative = entry;
634
+ if (entryRelative.includes("/")) continue;
635
+ const absPath = path__default.resolve(cwd, entry);
636
+ if (!fs__default.existsSync(absPath)) continue;
637
+ const stat = fs__default.statSync(absPath);
638
+ if (stat.isDirectory()) {
639
+ tree.push({
640
+ name: entryRelative,
641
+ path: entry,
642
+ type: "directory",
643
+ children: buildDirectoryTree(cwd, entry, 3)
644
+ });
645
+ } else if (stat.isFile()) {
646
+ tree.push({ name: entryRelative, path: entry, type: "file" });
647
+ }
648
+ }
649
+ return tree.sort((a, b) => {
650
+ if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
651
+ return a.name.localeCompare(b.name);
652
+ });
653
+ }
654
+ function buildDirectoryTree(cwd, relativePath, maxDepth) {
655
+ if (maxDepth <= 0) return [];
656
+ const absDir = path__default.resolve(cwd, relativePath);
657
+ if (!fs__default.existsSync(absDir) || !fs__default.statSync(absDir).isDirectory()) return [];
658
+ const entries = fs__default.readdirSync(absDir, { withFileTypes: true });
659
+ const result = [];
660
+ for (const entry of entries) {
661
+ if (FILE_MANAGER_BLOCKED.has(entry.name) || entry.name.startsWith(".")) continue;
662
+ const childRelative = relativePath ? `${relativePath}/${entry.name}` : entry.name;
663
+ if (entry.isDirectory()) {
664
+ result.push({
665
+ name: entry.name,
666
+ path: childRelative,
667
+ type: "directory",
668
+ children: buildDirectoryTree(cwd, childRelative, maxDepth - 1)
669
+ });
670
+ } else if (entry.isFile()) {
671
+ result.push({ name: entry.name, path: childRelative, type: "file" });
672
+ }
673
+ }
674
+ return result.sort((a, b) => {
675
+ if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
676
+ return a.name.localeCompare(b.name);
677
+ });
678
+ }
247
679
  function findPluginByConfigKey(rootPlugin, configKey) {
248
680
  for (const child of rootPlugin.children) {
249
681
  if (child.name === configKey || child.name.endsWith(`-${configKey}`) || child.name.includes(configKey)) {
@@ -268,7 +700,7 @@ var cache = /* @__PURE__ */ new Map();
268
700
  var TRANSFORMABLE_EXTS = [".ts", ".tsx", ".jsx"];
269
701
  var RESOLVE_EXTS = [".tsx", ".ts", ".jsx", ".js"];
270
702
  function isTransformable(filePath) {
271
- const ext = path3.extname(filePath).toLowerCase();
703
+ const ext = path.extname(filePath).toLowerCase();
272
704
  return TRANSFORMABLE_EXTS.includes(ext);
273
705
  }
274
706
  var IMPORT_RE = /(?:import|export)\s+.*?\s+from\s+['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
@@ -277,14 +709,14 @@ function isRelative(specifier) {
277
709
  return specifier.startsWith("./") || specifier.startsWith("../");
278
710
  }
279
711
  function hasExtension(specifier) {
280
- const base = path3.basename(specifier);
712
+ const base = path.basename(specifier);
281
713
  return base.includes(".") && !base.startsWith(".");
282
714
  }
283
715
  function resolveRelativeImport(specifier, fromFile) {
284
716
  if (!isRelative(specifier)) return specifier;
285
717
  if (hasExtension(specifier)) return specifier;
286
- const dir = path3.dirname(fromFile);
287
- const target = path3.resolve(dir, specifier);
718
+ const dir = path.dirname(fromFile);
719
+ const target = path.resolve(dir, specifier);
288
720
  for (const ext of RESOLVE_EXTS) {
289
721
  if (fs.existsSync(target + ext)) {
290
722
  return specifier + ext;
@@ -292,7 +724,7 @@ function resolveRelativeImport(specifier, fromFile) {
292
724
  }
293
725
  if (fs.existsSync(target) && fs.statSync(target).isDirectory()) {
294
726
  for (const ext of RESOLVE_EXTS) {
295
- if (fs.existsSync(path3.join(target, `index${ext}`))) {
727
+ if (fs.existsSync(path.join(target, `index${ext}`))) {
296
728
  return specifier + `/index${ext}`;
297
729
  }
298
730
  }
@@ -318,7 +750,7 @@ async function transformFile(filePath) {
318
750
  return cached.code;
319
751
  }
320
752
  const source = fs.readFileSync(filePath, "utf-8");
321
- const ext = path3.extname(filePath).toLowerCase();
753
+ const ext = path.extname(filePath).toLowerCase();
322
754
  const loader = ext === ".tsx" ? "tsx" : ext === ".ts" ? "ts" : ext === ".jsx" ? "jsx" : "js";
323
755
  const result = await transform(source, {
324
756
  loader,
@@ -353,23 +785,23 @@ if (enabled) {
353
785
  });
354
786
  useContext("router", async (router) => {
355
787
  const entryBases = /* @__PURE__ */ new Map();
356
- const genToken = () => Math.random().toString(36).slice(2, 8);
788
+ const genToken = () => crypto.randomBytes(6).toString("hex");
357
789
  const watchedDirs = /* @__PURE__ */ new Set();
358
790
  const watchers = [];
359
- path3.join(import.meta.dirname, "../client");
360
- const distDir = path3.join(import.meta.dirname, "../dist");
791
+ path.join(import.meta.dirname, "../client");
792
+ const distDir = path.join(import.meta.dirname, "../dist");
361
793
  const resolveFile = (name) => {
362
- const distPath = path3.resolve(distDir, name);
794
+ const distPath = path.resolve(distDir, name);
363
795
  if (fs.existsSync(distPath)) return distPath;
364
796
  return null;
365
797
  };
366
798
  const webServer = {
367
799
  entries: {},
368
800
  addEntry(entry) {
369
- const hash = Date.now().toString(16) + Math.random().toString(16).slice(2, 8);
801
+ const hash = crypto.randomBytes(8).toString("hex");
370
802
  const entryFile = typeof entry === "string" ? entry : entry.production;
371
- const dir = path3.dirname(entryFile);
372
- const filename = path3.basename(entryFile);
803
+ const dir = path.dirname(entryFile);
804
+ const filename = path.basename(entryFile);
373
805
  let token;
374
806
  for (const [t, d] of entryBases) {
375
807
  if (d === dir) {
@@ -399,6 +831,32 @@ if (enabled) {
399
831
  ws: router.ws("/server")
400
832
  };
401
833
  logger2.info(`Web \u63A7\u5236\u53F0\u5DF2\u542F\u52A8 (${"\u751F\u4EA7\u6A21\u5F0F, \u9759\u6001\u6587\u4EF6 + \u6309\u9700\u8F6C\u8BD1"})`);
834
+ const loginAssist = root2.inject("loginAssist");
835
+ if (loginAssist) {
836
+ router.get("/api/login-assist/pending", (ctx) => {
837
+ ctx.body = loginAssist.listPending();
838
+ });
839
+ router.post("/api/login-assist/submit", async (ctx) => {
840
+ const body = ctx.request?.body;
841
+ if (!body?.id) {
842
+ ctx.status = 400;
843
+ ctx.body = { error: "missing id" };
844
+ return;
845
+ }
846
+ const ok = loginAssist.submit(body.id, body.value ?? "");
847
+ ctx.body = { ok };
848
+ });
849
+ router.post("/api/login-assist/cancel", async (ctx) => {
850
+ const body = ctx.request?.body;
851
+ if (!body?.id) {
852
+ ctx.status = 400;
853
+ ctx.body = { error: "missing id" };
854
+ return;
855
+ }
856
+ const ok = loginAssist.cancel(body.id, body.reason);
857
+ ctx.body = { ok };
858
+ });
859
+ }
402
860
  router.all("*all", async (ctx, next) => {
403
861
  if (ctx.path.startsWith("/api/") || ctx.path === "/api" || ctx.path.startsWith("/pub/") || ctx.path === "/pub") {
404
862
  return next();
@@ -433,7 +891,7 @@ if (enabled) {
433
891
  return;
434
892
  }
435
893
  }
436
- ctx.type = path3.extname(filename);
894
+ ctx.type = path.extname(filename);
437
895
  ctx.type = mime.getType(filename) || ctx.type;
438
896
  return ctx.body = fs.createReadStream(filename);
439
897
  };
@@ -451,8 +909,8 @@ if (enabled) {
451
909
  ctx.status = 404;
452
910
  return;
453
911
  }
454
- const fullPath = path3.resolve(baseDir, relPath);
455
- const safePfx = baseDir.endsWith(path3.sep) ? baseDir : baseDir + path3.sep;
912
+ const fullPath = path.resolve(baseDir, relPath);
913
+ const safePfx = baseDir.endsWith(path.sep) ? baseDir : baseDir + path.sep;
456
914
  if (!fullPath.startsWith(safePfx) && fullPath !== baseDir) {
457
915
  ctx.status = 403;
458
916
  return;