@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/CHANGELOG.md +16 -0
- package/client/index.html +1 -0
- package/client/src/main.tsx +51 -24
- package/client/src/pages/{dashboard-bots.tsx → bots.tsx} +1 -1
- package/client/src/pages/{dashboard-config.tsx → config.tsx} +1 -1
- package/client/src/pages/{dashboard-home.tsx → dashboard.tsx} +1 -1
- package/client/src/pages/database.tsx +708 -0
- package/client/src/pages/{dashboard-env.tsx → env.tsx} +1 -1
- package/client/src/pages/files.tsx +470 -0
- package/client/src/pages/login-assist.tsx +225 -0
- package/client/src/pages/{dashboard-logs.tsx → logs.tsx} +1 -1
- package/client/src/pages/{dashboard-plugin-detail.tsx → plugin-detail.tsx} +1 -1
- package/client/src/pages/{dashboard-plugins.tsx → plugins.tsx} +1 -1
- package/dist/chunk-efA98nb6.js +13 -0
- package/dist/client.js +1 -1
- package/dist/cva.js +44 -44
- package/dist/index.html +1 -0
- package/dist/index.js +110 -124
- package/dist/lucide-react.js +29540 -19582
- package/dist/radix-ui.js +10036 -11108
- package/dist/react-dom-client.js +7601 -9988
- package/dist/react-dom.js +141 -171
- package/dist/react-jsx-dev-runtime.js +9 -42
- package/dist/react-jsx-runtime.js +22 -59
- package/dist/react-router.js +7658 -10644
- package/dist/react.js +293 -455
- package/dist/style.css +5 -2
- package/lib/index.js +480 -22
- package/lib/websocket.js +431 -0
- package/package.json +6 -6
- package/dist/_commonjsHelpers-C6fGbg64.js +0 -6
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
|
|
6
|
-
import
|
|
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
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
287
|
-
const target =
|
|
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(
|
|
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 =
|
|
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 = () =>
|
|
788
|
+
const genToken = () => crypto.randomBytes(6).toString("hex");
|
|
357
789
|
const watchedDirs = /* @__PURE__ */ new Set();
|
|
358
790
|
const watchers = [];
|
|
359
|
-
|
|
360
|
-
const distDir =
|
|
791
|
+
path.join(import.meta.dirname, "../client");
|
|
792
|
+
const distDir = path.join(import.meta.dirname, "../dist");
|
|
361
793
|
const resolveFile = (name) => {
|
|
362
|
-
const distPath =
|
|
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 =
|
|
801
|
+
const hash = crypto.randomBytes(8).toString("hex");
|
|
370
802
|
const entryFile = typeof entry === "string" ? entry : entry.production;
|
|
371
|
-
const dir =
|
|
372
|
-
const filename =
|
|
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 =
|
|
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 =
|
|
455
|
-
const safePfx = baseDir.endsWith(
|
|
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;
|