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