@zhin.js/console 1.0.21 → 1.0.22

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.
Files changed (56) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +4 -4
  3. package/client/components.json +17 -0
  4. package/client/index.html +1 -1
  5. package/client/src/components/PluginConfigForm/BasicFieldRenderers.tsx +89 -180
  6. package/client/src/components/PluginConfigForm/CollectionFieldRenderers.tsx +97 -200
  7. package/client/src/components/PluginConfigForm/CompositeFieldRenderers.tsx +31 -70
  8. package/client/src/components/PluginConfigForm/FieldRenderer.tsx +27 -77
  9. package/client/src/components/PluginConfigForm/NestedFieldRenderer.tsx +33 -53
  10. package/client/src/components/PluginConfigForm/index.tsx +71 -173
  11. package/client/src/components/ui/accordion.tsx +54 -0
  12. package/client/src/components/ui/alert.tsx +62 -0
  13. package/client/src/components/ui/avatar.tsx +41 -0
  14. package/client/src/components/ui/badge.tsx +32 -0
  15. package/client/src/components/ui/button.tsx +50 -0
  16. package/client/src/components/ui/card.tsx +50 -0
  17. package/client/src/components/ui/checkbox.tsx +25 -0
  18. package/client/src/components/ui/dialog.tsx +87 -0
  19. package/client/src/components/ui/dropdown-menu.tsx +97 -0
  20. package/client/src/components/ui/input.tsx +21 -0
  21. package/client/src/components/ui/scroll-area.tsx +43 -0
  22. package/client/src/components/ui/select.tsx +127 -0
  23. package/client/src/components/ui/separator.tsx +23 -0
  24. package/client/src/components/ui/skeleton.tsx +12 -0
  25. package/client/src/components/ui/switch.tsx +26 -0
  26. package/client/src/components/ui/tabs.tsx +52 -0
  27. package/client/src/components/ui/textarea.tsx +20 -0
  28. package/client/src/components/ui/tooltip.tsx +27 -0
  29. package/client/src/layouts/dashboard.tsx +91 -221
  30. package/client/src/main.tsx +38 -42
  31. package/client/src/pages/dashboard-bots.tsx +91 -137
  32. package/client/src/pages/dashboard-home.tsx +133 -204
  33. package/client/src/pages/dashboard-logs.tsx +125 -196
  34. package/client/src/pages/dashboard-plugin-detail.tsx +261 -329
  35. package/client/src/pages/dashboard-plugins.tsx +108 -105
  36. package/client/src/style.css +156 -865
  37. package/client/src/theme/index.ts +60 -35
  38. package/client/tailwind.config.js +78 -69
  39. package/dist/client.js +1 -1
  40. package/dist/cva.js +47 -0
  41. package/dist/index.html +1 -1
  42. package/dist/index.js +6 -6
  43. package/dist/react-router.js +7121 -5585
  44. package/dist/react.js +192 -149
  45. package/dist/style.css +2 -2
  46. package/lib/bin.js +2 -2
  47. package/lib/build.js +2 -2
  48. package/lib/index.d.ts +0 -3
  49. package/lib/index.js +160 -205
  50. package/lib/transform.d.ts +26 -0
  51. package/lib/transform.js +78 -0
  52. package/lib/websocket.d.ts +0 -1
  53. package/package.json +8 -7
  54. package/dist/radix-ui-themes.js +0 -9305
  55. package/lib/dev.d.ts +0 -18
  56. package/lib/dev.js +0 -87
package/lib/bin.js CHANGED
@@ -46,7 +46,7 @@ async function build2(root, config = {}) {
46
46
  "react/jsx-runtime",
47
47
  "react/jsx-dev-runtime",
48
48
  "radix-ui",
49
- "@radix-ui/themes",
49
+ "class-variance-authority",
50
50
  "lucide-react",
51
51
  "@zhin.js/client"
52
52
  ],
@@ -57,7 +57,7 @@ async function build2(root, config = {}) {
57
57
  "react": root + "/react.js",
58
58
  "react-dom": root + "/react-dom.js",
59
59
  "radix-ui": root + "/radix-ui.js",
60
- "@radix-ui/themes": root + "/radix-ui-themes.js"
60
+ "class-variance-authority": root + "/cva.js"
61
61
  }
62
62
  },
63
63
  output: {
package/lib/build.js CHANGED
@@ -46,7 +46,7 @@ async function build2(root, config = {}) {
46
46
  "react/jsx-runtime",
47
47
  "react/jsx-dev-runtime",
48
48
  "radix-ui",
49
- "@radix-ui/themes",
49
+ "class-variance-authority",
50
50
  "lucide-react",
51
51
  "@zhin.js/client"
52
52
  ],
@@ -57,7 +57,7 @@ async function build2(root, config = {}) {
57
57
  "react": root + "/react.js",
58
58
  "react-dom": root + "/react-dom.js",
59
59
  "radix-ui": root + "/radix-ui.js",
60
- "@radix-ui/themes": root + "/radix-ui-themes.js"
60
+ "class-variance-authority": root + "/cva.js"
61
61
  }
62
62
  },
63
63
  output: {
package/lib/index.d.ts CHANGED
@@ -1,11 +1,9 @@
1
1
  import * as _zhin_js_http from '@zhin.js/http';
2
2
  import { WebSocketServer } from 'ws';
3
- import { ViteDevServer } from 'vite';
4
3
 
5
4
  interface ConsoleConfig {
6
5
  /** 是否启用控制台插件,默认 true */
7
6
  enabled?: boolean;
8
- /** 是否延迟加载 Vite(开发模式),默认 true */
9
7
  /** 端口号(继承自 http 配置) */
10
8
  port?: number;
11
9
  }
@@ -14,7 +12,6 @@ type WebEntry = string | {
14
12
  development: string;
15
13
  };
16
14
  interface WebServer {
17
- vite?: ViteDevServer;
18
15
  addEntry(entry: WebEntry): () => void;
19
16
  entries: Record<string, string>;
20
17
  ws: WebSocketServer;
package/lib/index.js CHANGED
@@ -1,111 +1,11 @@
1
- import * as path2 from 'path';
2
- import path2__default from 'path';
3
- import * as fs2 from 'fs';
4
- import fs2__default from 'fs';
5
1
  import { usePlugin } from '@zhin.js/core';
6
2
  import mime from 'mime';
3
+ import * as fs2 from 'fs';
4
+ import * as path2 from 'path';
7
5
  import WebSocket from 'ws';
6
+ import { transform } from 'esbuild';
8
7
 
9
- var __defProp = Object.defineProperty;
10
- var __getOwnPropNames = Object.getOwnPropertyNames;
11
- var __esm = (fn, res) => function __init() {
12
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
- };
14
- var __export = (target, all) => {
15
- for (var name in all)
16
- __defProp(target, name, { get: all[name], enumerable: true });
17
- };
18
-
19
- // src/dev.ts
20
- var dev_exports = {};
21
- __export(dev_exports, {
22
- createViteDevServer: () => createViteDevServer
23
- });
24
- async function createViteDevServer(options) {
25
- const { root: root3, base = "/vite/", enableTailwind = true } = options;
26
- try {
27
- const [
28
- { createServer, searchForWorkspaceRoot },
29
- { default: react },
30
- { default: tailwindcss }
31
- ] = await Promise.all([
32
- import('vite'),
33
- import('@vitejs/plugin-react'),
34
- import('@tailwindcss/vite')
35
- ]);
36
- const plugins = [react()];
37
- if (enableTailwind) {
38
- plugins.push(tailwindcss());
39
- }
40
- const clientPath = path2__default.resolve(process.cwd(), "node_modules/@zhin.js/client/client");
41
- if (!fs2__default.existsSync(clientPath)) {
42
- throw new Error("@zhin.js/client not found");
43
- }
44
- return await createServer({
45
- root: root3,
46
- base,
47
- plugins: [react(), tailwindcss()],
48
- server: {
49
- middlewareMode: true,
50
- allowedHosts: true,
51
- fs: {
52
- strict: false,
53
- // 添加文件访问过滤,避免访问特殊文件
54
- allow: [
55
- // 允许访问的目录
56
- root3,
57
- searchForWorkspaceRoot(root3),
58
- path2__default.resolve(process.cwd(), "node_modules"),
59
- path2__default.resolve(process.cwd(), "client"),
60
- path2__default.resolve(process.cwd(), "src")
61
- ],
62
- // 拒绝访问某些文件模式
63
- deny: [
64
- "**/.git/**",
65
- "**/node_modules/.cache/**",
66
- "**/*.socket",
67
- "**/*.pipe",
68
- "**/Dockerfile*",
69
- "**/.env*"
70
- ]
71
- }
72
- },
73
- resolve: {
74
- dedupe: [
75
- "react",
76
- "react-dom",
77
- "clsx",
78
- "tailwind-merge",
79
- "@reduxjs/toolkit",
80
- "react-router",
81
- "react-redux",
82
- "redux-persist"
83
- ],
84
- alias: {
85
- "@zhin.js/client": path2__default.resolve(process.cwd(), "node_modules/@zhin.js/client/client"),
86
- "@": path2__default.resolve(root3, "../client/src")
87
- }
88
- },
89
- optimizeDeps: {
90
- include: ["react", "react-dom"]
91
- },
92
- build: {
93
- rollupOptions: {
94
- input: root3 + "/index.html"
95
- }
96
- }
97
- });
98
- } catch (error) {
99
- throw new Error(
100
- `Failed to create Vite dev server. Make sure all development dependencies are installed: vite, @vitejs/plugin-react, @tailwindcss/vite. Run: pnpm install --include=optional
101
- Original error: ${error.message}`
102
- );
103
- }
104
- }
105
- var init_dev = __esm({
106
- "src/dev.ts"() {
107
- }
108
- });
8
+ // src/index.ts
109
9
  var { root, logger } = usePlugin();
110
10
  function setupWebSocket(webServer) {
111
11
  webServer.ws.on("connection", (ws) => {
@@ -246,20 +146,76 @@ function notifyDataUpdate(webServer) {
246
146
  timestamp: Date.now()
247
147
  });
248
148
  }
249
-
250
- // src/index.ts
251
- async function loadDevDependencies() {
252
- try {
253
- const devModule = await Promise.resolve().then(() => (init_dev(), dev_exports));
254
- const koaConnectModule = await import('koa-connect');
255
- return {
256
- createViteDevServer: devModule.createViteDevServer,
257
- connect: koaConnectModule.default
258
- };
259
- } catch {
260
- return null;
149
+ var cache = /* @__PURE__ */ new Map();
150
+ var TRANSFORMABLE_EXTS = [".ts", ".tsx", ".jsx"];
151
+ var RESOLVE_EXTS = [".tsx", ".ts", ".jsx", ".js"];
152
+ function isTransformable(filePath) {
153
+ const ext = path2.extname(filePath).toLowerCase();
154
+ return TRANSFORMABLE_EXTS.includes(ext);
155
+ }
156
+ var IMPORT_RE = /(?:import|export)\s+.*?\s+from\s+['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
157
+ var CSS_IMPORT_RE = /import\s+['"][^'"]+\.css['"]\s*;?\n?/g;
158
+ function isRelative(specifier) {
159
+ return specifier.startsWith("./") || specifier.startsWith("../");
160
+ }
161
+ function hasExtension(specifier) {
162
+ const base = path2.basename(specifier);
163
+ return base.includes(".") && !base.startsWith(".");
164
+ }
165
+ function resolveRelativeImport(specifier, fromFile) {
166
+ if (!isRelative(specifier)) return specifier;
167
+ if (hasExtension(specifier)) return specifier;
168
+ const dir = path2.dirname(fromFile);
169
+ const target = path2.resolve(dir, specifier);
170
+ for (const ext of RESOLVE_EXTS) {
171
+ if (fs2.existsSync(target + ext)) {
172
+ return specifier + ext;
173
+ }
174
+ }
175
+ if (fs2.existsSync(target) && fs2.statSync(target).isDirectory()) {
176
+ for (const ext of RESOLVE_EXTS) {
177
+ if (fs2.existsSync(path2.join(target, `index${ext}`))) {
178
+ return specifier + `/index${ext}`;
179
+ }
180
+ }
261
181
  }
182
+ return specifier;
262
183
  }
184
+ function rewriteImports(code, fromFile) {
185
+ code = code.replace(CSS_IMPORT_RE, "");
186
+ code = code.replace(IMPORT_RE, (match, fromSpecifier, dynamicSpecifier) => {
187
+ const specifier = fromSpecifier || dynamicSpecifier;
188
+ if (!specifier || !isRelative(specifier)) return match;
189
+ if (hasExtension(specifier)) return match;
190
+ const resolved = resolveRelativeImport(specifier, fromFile);
191
+ if (resolved === specifier) return match;
192
+ return match.replace(specifier, resolved);
193
+ });
194
+ return code;
195
+ }
196
+ async function transformFile(filePath) {
197
+ const stat = fs2.statSync(filePath);
198
+ const cached = cache.get(filePath);
199
+ if (cached && cached.mtime === stat.mtimeMs) {
200
+ return cached.code;
201
+ }
202
+ const source = fs2.readFileSync(filePath, "utf-8");
203
+ const ext = path2.extname(filePath).toLowerCase();
204
+ const loader = ext === ".tsx" ? "tsx" : ext === ".ts" ? "ts" : ext === ".jsx" ? "jsx" : "js";
205
+ const result = await transform(source, {
206
+ loader,
207
+ jsx: "automatic",
208
+ jsxImportSource: "react",
209
+ format: "esm",
210
+ sourcemap: "inline",
211
+ target: "es2022"
212
+ });
213
+ const code = rewriteImports(result.code, filePath);
214
+ cache.set(filePath, { mtime: stat.mtimeMs, code });
215
+ return code;
216
+ }
217
+
218
+ // src/index.ts
263
219
  var { provide, root: root2, useContext, logger: logger2, inject, onDispose } = usePlugin();
264
220
  var configService = inject("config");
265
221
  var appConfig = configService?.get("zhin.config.yml") || {};
@@ -278,18 +234,36 @@ if (enabled) {
278
234
  data: { key, value }
279
235
  });
280
236
  useContext("router", async (router) => {
281
- const base = "/vite/";
282
- const isDev = process.env.NODE_ENV === "development";
283
- const rootDir = isDev ? path2.join(import.meta.dirname, "../client") : path2.join(import.meta.dirname, "../dist");
284
- let viteStarting = false;
285
- let viteStarted = false;
286
- let devDeps = null;
237
+ const entryBases = /* @__PURE__ */ new Map();
238
+ const genToken = () => Math.random().toString(36).slice(2, 8);
239
+ const watchedDirs = /* @__PURE__ */ new Set();
240
+ const watchers = [];
241
+ path2.join(import.meta.dirname, "../client");
242
+ const distDir = path2.join(import.meta.dirname, "../dist");
243
+ const resolveFile = (name) => {
244
+ const distPath = path2.resolve(distDir, name);
245
+ if (fs2.existsSync(distPath)) return distPath;
246
+ return null;
247
+ };
287
248
  const webServer = {
288
249
  entries: {},
289
250
  addEntry(entry) {
290
251
  const hash = Date.now().toString(16) + Math.random().toString(16).slice(2, 8);
291
- const entryFile = typeof entry === "string" ? entry : entry[process.env.NODE_ENV || "development"];
292
- this.entries[hash] = `/vite/@fs/${entryFile}`;
252
+ const entryFile = typeof entry === "string" ? entry : entry.production;
253
+ const dir = path2.dirname(entryFile);
254
+ const filename = path2.basename(entryFile);
255
+ let token;
256
+ for (const [t, d] of entryBases) {
257
+ if (d === dir) {
258
+ token = t;
259
+ break;
260
+ }
261
+ }
262
+ if (!token) {
263
+ token = genToken();
264
+ entryBases.set(token, dir);
265
+ }
266
+ this.entries[hash] = `/vite/@ext/${token}/${filename}`;
293
267
  if (this.ws) {
294
268
  for (const ws of this.ws.clients || []) {
295
269
  ws.send(JSON.stringify(createAddMsg("entries", this.entries[hash])));
@@ -306,64 +280,15 @@ if (enabled) {
306
280
  },
307
281
  ws: router.ws("/server")
308
282
  };
309
- const ensureViteStarted = async () => {
310
- if (viteStarted || viteStarting || !isDev) return;
311
- viteStarting = true;
312
- try {
313
- logger2.info("\u{1F504} \u68C0\u6D4B\u5230\u63A7\u5236\u53F0\u8BBF\u95EE\uFF0C\u6B63\u5728\u542F\u52A8 Vite \u5F00\u53D1\u670D\u52A1\u5668...");
314
- devDeps = await loadDevDependencies();
315
- if (devDeps) {
316
- webServer.vite = await devDeps.createViteDevServer({
317
- root: rootDir,
318
- base,
319
- enableTailwind: true
320
- });
321
- viteStarted = true;
322
- logger2.info("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
323
- logger2.info("\u2551 Web \u63A7\u5236\u53F0\u5DF2\u542F\u52A8 (\u6309\u9700\u52A0\u8F7D) \u2551");
324
- logger2.info("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563");
325
- logger2.info("\u2551 \u5730\u5740: http://localhost:8086/ \u2551");
326
- logger2.info("\u2551 \u6A21\u5F0F: \u5F00\u53D1\u6A21\u5F0F (Vite HMR) \u2551");
327
- logger2.info("\u2551 \u5185\u5B58: \u5DF2\u52A0\u8F7D (~23MB) \u2551");
328
- logger2.info("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
329
- }
330
- } catch (error) {
331
- logger2.error("Vite \u542F\u52A8\u5931\u8D25:", error);
332
- } finally {
333
- viteStarting = false;
334
- }
335
- };
336
- if (isDev) {
337
- await ensureViteStarted();
338
- router.use(async (ctx, next) => {
339
- if (ctx.request.originalUrl.startsWith("/api")) return next();
340
- if (webServer.vite && devDeps) {
341
- return devDeps.connect(webServer.vite.middlewares)(ctx, next);
342
- }
343
- return next();
344
- });
345
- } else {
346
- router.use((ctx, next) => {
347
- if (ctx.request.originalUrl.startsWith("/api")) return next();
348
- if (!ctx.path.startsWith("/vite/@fs/")) return next();
349
- const filename = ctx.path.replace(`/vite/@fs/`, "");
350
- if (!fs2.existsSync(filename)) return next();
351
- ctx.type = mime.getType(filename) || path2.extname(filename);
352
- ctx.body = fs2.createReadStream(filename);
353
- });
354
- logger2.info("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557");
355
- logger2.info("\u2551 Web \u63A7\u5236\u53F0\u5DF2\u542F\u52A8 \u2551");
356
- logger2.info("\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563");
357
- logger2.info("\u2551 \u5730\u5740: http://localhost:8086/ \u2551");
358
- logger2.info("\u2551 \u6A21\u5F0F: \u751F\u4EA7\u6A21\u5F0F (\u9759\u6001\u6587\u4EF6) \u2551");
359
- logger2.info("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D");
360
- }
283
+ logger2.info(`Web \u63A7\u5236\u53F0\u5DF2\u542F\u52A8 (${"\u751F\u4EA7\u6A21\u5F0F, \u9759\u6001\u6587\u4EF6 + \u6309\u9700\u8F6C\u8BD1"})`);
361
284
  router.all("*all", async (ctx, next) => {
362
- const url = ctx.request.originalUrl.replace(base, "");
285
+ if (ctx.path.startsWith("/api/") || ctx.path === "/api") {
286
+ return next();
287
+ }
363
288
  const name = ctx.path.slice(1);
364
- const sendFile = (filename2) => {
289
+ const sendFile = async (filename) => {
365
290
  try {
366
- const stat = fs2.statSync(filename2);
291
+ const stat = fs2.statSync(filename);
367
292
  if (!stat.isFile()) {
368
293
  ctx.status = 404;
369
294
  return;
@@ -372,33 +297,63 @@ if (enabled) {
372
297
  ctx.status = 404;
373
298
  return;
374
299
  }
375
- ctx.type = path2.extname(filename2);
376
- ctx.type = mime.getType(filename2) || ctx.type;
377
- return ctx.body = fs2.createReadStream(filename2);
300
+ if (isTransformable(filename)) {
301
+ try {
302
+ const code = await transformFile(filename);
303
+ ctx.type = "application/javascript";
304
+ ctx.body = code;
305
+ return;
306
+ } catch (e) {
307
+ logger2.warn(`\u8F6C\u8BD1\u5931\u8D25: ${filename}`, e.message);
308
+ ctx.status = 500;
309
+ ctx.body = `/* transform error: ${e.message} */`;
310
+ return;
311
+ }
312
+ }
313
+ ctx.type = path2.extname(filename);
314
+ ctx.type = mime.getType(filename) || ctx.type;
315
+ return ctx.body = fs2.createReadStream(filename);
378
316
  };
379
- if (Object.keys(webServer.entries).includes(name)) {
380
- return sendFile(path2.resolve(process.cwd(), webServer.entries[name]));
317
+ if (ctx.path.startsWith("/vite/@ext/")) {
318
+ const rest = ctx.path.replace("/vite/@ext/", "");
319
+ const slashIdx = rest.indexOf("/");
320
+ if (slashIdx === -1) {
321
+ ctx.status = 404;
322
+ return;
323
+ }
324
+ const token = rest.slice(0, slashIdx);
325
+ const relPath = rest.slice(slashIdx + 1);
326
+ const baseDir = entryBases.get(token);
327
+ if (!baseDir) {
328
+ ctx.status = 404;
329
+ return;
330
+ }
331
+ const fullPath = path2.resolve(baseDir, relPath);
332
+ const safePfx = baseDir.endsWith(path2.sep) ? baseDir : baseDir + path2.sep;
333
+ if (!fullPath.startsWith(safePfx) && fullPath !== baseDir) {
334
+ ctx.status = 403;
335
+ return;
336
+ }
337
+ if (fs2.existsSync(fullPath)) {
338
+ return sendFile(fullPath);
339
+ }
340
+ ctx.status = 404;
341
+ return;
381
342
  }
382
- const filename = path2.resolve(rootDir, name);
383
- if (filename.startsWith(rootDir) || filename.includes("node_modules")) {
343
+ const resolved = resolveFile(name);
344
+ if (resolved) {
384
345
  try {
385
- if (fs2.existsSync(filename)) {
386
- const fileState = fs2.statSync(filename);
387
- if (fileState.isFile() && !fileState.isSocket() && !fileState.isFIFO()) {
388
- return sendFile(filename);
389
- }
346
+ const fileState = fs2.statSync(resolved);
347
+ if (fileState.isFile() && !fileState.isSocket() && !fileState.isFIFO()) {
348
+ return sendFile(resolved);
390
349
  }
391
350
  } catch (error) {
392
- logger2.warn(`\u6587\u4EF6\u8BBF\u95EE\u9519\u8BEF: ${filename}`, error.message);
351
+ logger2.warn(`\u6587\u4EF6\u8BBF\u95EE\u9519\u8BEF: ${resolved}`, error.message);
393
352
  }
394
- } else {
395
- return ctx.status = 403;
396
353
  }
397
- const indexFile = path2.resolve(rootDir, "index.html");
398
- if (!isDev) return sendFile(indexFile);
399
- const template = fs2.readFileSync(indexFile, "utf8");
400
- ctx.type = "html";
401
- ctx.body = await webServer.vite?.transformIndexHtml(url, template) || template;
354
+ const indexFile = resolveFile("index.html");
355
+ if (indexFile) return sendFile(indexFile);
356
+ ctx.status = 404;
402
357
  });
403
358
  webServer.ws;
404
359
  const dataUpdateInterval = setInterval(() => {
@@ -407,18 +362,18 @@ if (enabled) {
407
362
  setupWebSocket(webServer);
408
363
  onDispose(() => {
409
364
  clearInterval(dataUpdateInterval);
365
+ for (const w of watchers) w.close();
366
+ watchers.length = 0;
367
+ watchedDirs.clear();
410
368
  });
411
369
  provide({
412
370
  name: "web",
413
371
  description: "web\u670D\u52A1",
414
372
  value: webServer,
415
373
  dispose(server) {
416
- return Promise.all([
417
- server.vite?.close(),
418
- new Promise((resolve2) => {
419
- server.ws.close(() => resolve2());
420
- })
421
- ]);
374
+ return new Promise((resolve3) => {
375
+ server.ws.close(() => resolve3());
376
+ });
422
377
  }
423
378
  });
424
379
  });
@@ -0,0 +1,26 @@
1
+ /**
2
+ * esbuild 按需转译模块
3
+ *
4
+ * 在生产模式下,对请求的 .ts/.tsx/.jsx 文件进行实时转译:
5
+ * 1. 读取源文件
6
+ * 2. esbuild.transform() 转为 ESM JS(React 17+ automatic JSX)
7
+ * 3. 重写相对 import 路径(补全扩展名,移除 CSS import)
8
+ * 4. 以 mtime 为校验的内存缓存
9
+ */
10
+ /**
11
+ * 判断文件是否需要转译
12
+ */
13
+ declare function isTransformable(filePath: string): boolean;
14
+ /**
15
+ * 转译单个 TS/TSX/JSX 文件为 ESM JavaScript
16
+ *
17
+ * @param filePath 文件绝对路径
18
+ * @returns 转译后的 JS 代码
19
+ */
20
+ declare function transformFile(filePath: string): Promise<string>;
21
+ /**
22
+ * 清空转译缓存
23
+ */
24
+ declare function clearTransformCache(): void;
25
+
26
+ export { clearTransformCache, isTransformable, transformFile };
@@ -0,0 +1,78 @@
1
+ import { transform } from 'esbuild';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+
5
+ // src/transform.ts
6
+ var cache = /* @__PURE__ */ new Map();
7
+ var TRANSFORMABLE_EXTS = [".ts", ".tsx", ".jsx"];
8
+ var RESOLVE_EXTS = [".tsx", ".ts", ".jsx", ".js"];
9
+ function isTransformable(filePath) {
10
+ const ext = path.extname(filePath).toLowerCase();
11
+ return TRANSFORMABLE_EXTS.includes(ext);
12
+ }
13
+ var IMPORT_RE = /(?:import|export)\s+.*?\s+from\s+['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
14
+ var CSS_IMPORT_RE = /import\s+['"][^'"]+\.css['"]\s*;?\n?/g;
15
+ function isRelative(specifier) {
16
+ return specifier.startsWith("./") || specifier.startsWith("../");
17
+ }
18
+ function hasExtension(specifier) {
19
+ const base = path.basename(specifier);
20
+ return base.includes(".") && !base.startsWith(".");
21
+ }
22
+ function resolveRelativeImport(specifier, fromFile) {
23
+ if (!isRelative(specifier)) return specifier;
24
+ if (hasExtension(specifier)) return specifier;
25
+ const dir = path.dirname(fromFile);
26
+ const target = path.resolve(dir, specifier);
27
+ for (const ext of RESOLVE_EXTS) {
28
+ if (fs.existsSync(target + ext)) {
29
+ return specifier + ext;
30
+ }
31
+ }
32
+ if (fs.existsSync(target) && fs.statSync(target).isDirectory()) {
33
+ for (const ext of RESOLVE_EXTS) {
34
+ if (fs.existsSync(path.join(target, `index${ext}`))) {
35
+ return specifier + `/index${ext}`;
36
+ }
37
+ }
38
+ }
39
+ return specifier;
40
+ }
41
+ function rewriteImports(code, fromFile) {
42
+ code = code.replace(CSS_IMPORT_RE, "");
43
+ code = code.replace(IMPORT_RE, (match, fromSpecifier, dynamicSpecifier) => {
44
+ const specifier = fromSpecifier || dynamicSpecifier;
45
+ if (!specifier || !isRelative(specifier)) return match;
46
+ if (hasExtension(specifier)) return match;
47
+ const resolved = resolveRelativeImport(specifier, fromFile);
48
+ if (resolved === specifier) return match;
49
+ return match.replace(specifier, resolved);
50
+ });
51
+ return code;
52
+ }
53
+ async function transformFile(filePath) {
54
+ const stat = fs.statSync(filePath);
55
+ const cached = cache.get(filePath);
56
+ if (cached && cached.mtime === stat.mtimeMs) {
57
+ return cached.code;
58
+ }
59
+ const source = fs.readFileSync(filePath, "utf-8");
60
+ const ext = path.extname(filePath).toLowerCase();
61
+ const loader = ext === ".tsx" ? "tsx" : ext === ".ts" ? "ts" : ext === ".jsx" ? "jsx" : "js";
62
+ const result = await transform(source, {
63
+ loader,
64
+ jsx: "automatic",
65
+ jsxImportSource: "react",
66
+ format: "esm",
67
+ sourcemap: "inline",
68
+ target: "es2022"
69
+ });
70
+ const code = rewriteImports(result.code, filePath);
71
+ cache.set(filePath, { mtime: stat.mtimeMs, code });
72
+ return code;
73
+ }
74
+ function clearTransformCache() {
75
+ cache.clear();
76
+ }
77
+
78
+ export { clearTransformCache, isTransformable, transformFile };
@@ -1,7 +1,6 @@
1
1
  import { WebServer } from './index.js';
2
2
  import '@zhin.js/http';
3
3
  import 'ws';
4
- import 'vite';
5
4
 
6
5
  /**
7
6
  * 设置 WebSocket 连接处理
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhin.js/console",
3
- "version": "1.0.21",
3
+ "version": "1.0.22",
4
4
  "description": "Web console service for Zhin.js with real-time monitoring",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -44,15 +44,16 @@
44
44
  "./node.tsconfig.json": "./node.tsconfig.json"
45
45
  },
46
46
  "dependencies": {
47
+ "esbuild": ">=0.21.0",
47
48
  "mime": "^4.1.0",
48
49
  "ws": "^8.18.3"
49
50
  },
50
51
  "devDependencies": {
51
- "@radix-ui/themes": "^3.2.1",
52
52
  "@reduxjs/toolkit": "^2.9.0",
53
53
  "@tailwindcss/postcss": "^4.1.11",
54
54
  "@tailwindcss/vite": "4.1.14",
55
55
  "@vitejs/plugin-react": "^4.3.4",
56
+ "class-variance-authority": "^0.7.1",
56
57
  "clsx": "^2.1.1",
57
58
  "koa-connect": "^2.1.0",
58
59
  "lucide-react": "^0.469.0",
@@ -66,14 +67,14 @@
66
67
  "tailwindcss": "latest",
67
68
  "tsup": "^8.5.1",
68
69
  "vite": "^7.0.6",
69
- "zhin.js": "1.0.25"
70
+ "zhin.js": "1.0.26"
70
71
  },
71
72
  "peerDependencies": {
72
73
  "@types/ws": "^8.18.1",
73
- "@zhin.js/client": "^1.0.8",
74
- "@zhin.js/core": "^1.0.25",
75
- "@zhin.js/http": "^1.0.16",
76
- "zhin.js": "1.0.25"
74
+ "@zhin.js/client": "^1.0.9",
75
+ "@zhin.js/core": "^1.0.26",
76
+ "@zhin.js/http": "^1.0.17",
77
+ "zhin.js": "1.0.26"
77
78
  },
78
79
  "files": [
79
80
  "lib",