@zhin.js/console 1.0.7 → 1.0.9

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/app/index.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { register, useContext, useApp } from "@zhin.js/core";
2
- import react from "@vitejs/plugin-react";
3
- import WebSocket, { WebSocketServer } from "ws";
4
- import { createServer, ViteDevServer, searchForWorkspaceRoot } from "vite";
1
+ import { register, useContext,useLogger } from "@zhin.js/core";
2
+ import { WebSocketServer } from "ws";
3
+ import { ViteDevServer } from "vite";
4
+ import mime from "mime";
5
5
  import connect from "koa-connect";
6
6
  import * as fs from "fs";
7
7
  import * as path from "path";
8
- import { fileURLToPath } from "node:url";
9
- import tailwindcss from "@tailwindcss/vite";
8
+ import { createViteDevServer } from "./dev.js";
9
+ import { setupWebSocket,notifyDataUpdate } from "./websocket.js";
10
10
 
11
11
  declare module "@zhin.js/types" {
12
12
  interface GlobalContext {
@@ -20,11 +20,12 @@ export type WebEntry =
20
20
  development: string;
21
21
  };
22
22
  export type WebServer = {
23
- vite: ViteDevServer;
23
+ vite?: ViteDevServer;
24
24
  addEntry(entry: WebEntry): () => void;
25
25
  entries: Record<string, string>;
26
26
  ws: WebSocketServer;
27
27
  };
28
+ const logger=useLogger()
28
29
  const createSyncMsg = (key: string, value: any) => {
29
30
  return {
30
31
  type: "sync",
@@ -53,90 +54,9 @@ const createDeleteMsg = (key: string, value: any) => {
53
54
  };
54
55
  };
55
56
  useContext("router", async (router) => {
56
- const root = path.join(import.meta.dirname, "../client");
57
57
  const base = "/vite/";
58
58
 
59
- const vite = await createServer({
60
- root,
61
- base,
62
- plugins: [react(), tailwindcss()],
63
- server: {
64
- middlewareMode: true,
65
- fs: {
66
- strict: false,
67
- },
68
- },
69
- resolve: {
70
- dedupe: [
71
- "react",
72
- "react-dom",
73
- "clsx",
74
- "tailwind-merge",
75
- "@reduxjs/toolkit",
76
- "react-router",
77
- "react-redux",
78
- "redux-persist",
79
- ],
80
- alias: {
81
- "@zhin.js/client": path.resolve(root, "../../client/client"),
82
- "@": path.resolve(root, "../client/src"),
83
- },
84
- },
85
- optimizeDeps: {
86
- include: ["react", "react-dom"],
87
- },
88
- build: {
89
- rollupOptions: {
90
- input: root + "/index.html",
91
- },
92
- },
93
- });
94
-
95
- // Vite 中间件 - 必须在其他路由之前
96
- router.use((ctx: any, next: any) => {
97
- if (ctx.request.originalUrl.startsWith("/api")) return next();
98
- return connect(vite.middlewares)(ctx, next);
99
- });
100
-
101
- // SPA 回退路由 - 处理所有未匹配的路由
102
- router.all("*all", async (ctx, next) => {
103
- const url = ctx.request.originalUrl.replace(base, "");
104
- const name = ctx.path.slice(1);
105
-
106
- const sendFile = (filename: string) => {
107
- ctx.type = path.extname(filename);
108
- if (filename.endsWith(".ts")) ctx.type = "text/javascript";
109
- return (ctx.body = fs.createReadStream(filename));
110
- };
111
-
112
- // 1. 检查是否是动态入口
113
- if (Object.keys(webServer.entries).includes(name)) {
114
- return sendFile(path.resolve(process.cwd(), webServer.entries[name]));
115
- }
116
-
117
- // 2. 检查是否是静态文件
118
- const filename = path.resolve(root, name);
119
- if (filename.startsWith(root) || filename.includes("node_modules")) {
120
- if (fs.existsSync(filename)) {
121
- const fileState = fs.statSync(filename);
122
- if (fileState.isFile()) {
123
- return sendFile(filename);
124
- }
125
- }
126
- } else {
127
- // 安全检查:路径不在允许范围内
128
- return (ctx.status = 403);
129
- }
130
-
131
- // 3. 所有其他路径(包括 SPA 路由)都返回 index.html
132
- // 这样前端路由可以正确处理
133
- const template = fs.readFileSync(path.resolve(root, "index.html"), "utf8");
134
- ctx.type = "html";
135
- ctx.body = await vite.transformIndexHtml(url, template);
136
- });
137
-
138
59
  const webServer: WebServer = {
139
- vite,
140
60
  entries: {},
141
61
  addEntry(entry) {
142
62
  const hash =
@@ -148,7 +68,7 @@ useContext("router", async (router) => {
148
68
  (process.env.NODE_ENV as "development" | "production") ||
149
69
  "development"
150
70
  ];
151
- this.entries[hash] = `/vite/@fs/${entryFile}`;
71
+ this.entries[hash] = `/vite/@fs/${entryFile}`
152
72
  for (const ws of this.ws.clients || []) {
153
73
  ws.send(JSON.stringify(createAddMsg("entries", this.entries[hash])));
154
74
  }
@@ -163,244 +83,98 @@ useContext("router", async (router) => {
163
83
  },
164
84
  ws: router.ws("/server"),
165
85
  };
166
- // 数据推送函数
167
- const broadcastToAll = (message: any) => {
168
- for (const ws of webServer.ws.clients || []) {
169
- if (ws.readyState === WebSocket.OPEN) {
170
- ws.send(JSON.stringify(message));
171
- }
172
- }
173
- };
174
-
175
- // 推送数据更新通知
176
- const notifyDataUpdate = () => {
177
- broadcastToAll({
178
- type: "data-update",
179
- timestamp: Date.now(),
86
+ const isDev = process.env.NODE_ENV === "development";
87
+ const root = isDev
88
+ ? path.join(import.meta.dirname, "../client")
89
+ : path.join(import.meta.dirname, "../dist");
90
+ logger.info({isDev,root})
91
+ if (isDev) {
92
+ webServer.vite = await await createViteDevServer({
93
+ root,
94
+ base,
95
+ enableTailwind: true,
180
96
  });
181
- };
182
-
183
- // WebSocket 连接处理
184
- webServer.ws.on("connection", (ws: WebSocket) => {
185
- // 发送初始数据
186
- ws.send(
187
- JSON.stringify(
188
- createSyncMsg(
189
- "entries",
190
- Array.from(new Set(Object.values(webServer.entries)))
191
- )
192
- )
193
- );
194
-
195
- // 通知客户端进行数据初始化
196
- ws.send(
197
- JSON.stringify({
198
- type: "init-data",
199
- timestamp: Date.now(),
200
- })
201
- );
97
+ // Vite 中间件 - 必须在其他路由之前
98
+ router.use((ctx, next) => {
99
+ if (ctx.request.originalUrl.startsWith("/api")) return next();
100
+ return connect(webServer.vite!.middlewares)(ctx, next);
101
+ });
102
+ }else{
103
+ router.use((ctx, next) => {
104
+ if (ctx.request.originalUrl.startsWith("/api")) return next();
105
+ if(!ctx.path.startsWith('/vite/@fs/')) return next();
106
+ const filename=ctx.path.replace(`/vite/@fs/`,'')
107
+ if(!fs.existsSync(filename)) return next();
108
+ ctx.type = mime.getType(filename) || path.extname(filename);
109
+ ctx.body = fs.createReadStream(filename);
110
+ });
111
+ }
202
112
 
203
- // 处理 WebSocket 消息
204
- ws.on("message", async (data) => {
113
+ // SPA 回退路由 - 处理所有未匹配的路由
114
+ router.all("*all", async (ctx, next) => {
115
+ const url = ctx.request.originalUrl.replace(base, "");
116
+ const name = isDev ? ctx.path.slice(1) : ctx.path.slice(1);
117
+ const sendFile = (filename: string) => {
118
+ console.log(`发送文件: ${filename}`);
119
+ // 安全检查:确保是常规文件
205
120
  try {
206
- const message = JSON.parse(data.toString());
207
- const { type, pluginName, requestId } = message;
208
-
209
- // 获取应用实例
210
- const app = useApp();
211
-
212
- switch (type) {
213
- case "config:get":
214
- try {
215
- let config;
216
- if (pluginName === "app") {
217
- config = app.getConfig();
218
- } else {
219
- const plugin = app.findPluginByName(pluginName);
220
- if (!plugin) {
221
- throw new Error(`Plugin ${pluginName} not found`);
222
- }
223
- config = plugin.config;
224
- }
225
-
226
- ws.send(
227
- JSON.stringify({
228
- requestId,
229
- data: config,
230
- })
231
- );
232
- } catch (error) {
233
- ws.send(
234
- JSON.stringify({
235
- requestId,
236
- error: (error as Error).message,
237
- })
238
- );
239
- }
240
- break;
241
-
242
- case "config:set":
243
- try {
244
- const { data: newConfig } = message;
245
-
246
- if (pluginName === "app") {
247
- app.config = newConfig;
248
- } else {
249
- const plugin = app.findPluginByName(pluginName);
250
- if (!plugin) {
251
- throw new Error(`Plugin ${pluginName} not found`);
252
- }
253
- plugin.config = newConfig;
254
- }
255
-
256
- // 响应成功
257
- ws.send(
258
- JSON.stringify({
259
- requestId,
260
- data: "success",
261
- })
262
- );
263
-
264
- // 广播配置更新
265
- webServer.ws.clients.forEach((client) => {
266
- if (client.readyState === 1) {
267
- // WebSocket.OPEN
268
- client.send(
269
- JSON.stringify({
270
- type: "config:updated",
271
- pluginName,
272
- data: newConfig,
273
- })
274
- );
275
- }
276
- });
277
- } catch (error) {
278
- ws.send(
279
- JSON.stringify({
280
- requestId,
281
- error: (error as Error).message,
282
- })
283
- );
284
- }
285
- break;
286
-
287
- case "schema:get":
288
- try {
289
- let schema;
290
- if (pluginName === "app") {
291
- schema = app.schema?.toJSON();
292
- } else {
293
- const plugin = app.findPluginByName(pluginName);
294
- if (!plugin) {
295
- throw new Error(`Plugin ${pluginName} not found`);
296
- }
297
- schema = plugin.schema?.toJSON();
298
- }
299
-
300
- ws.send(
301
- JSON.stringify({
302
- requestId,
303
- data: schema,
304
- })
305
- );
306
- } catch (error) {
307
- ws.send(
308
- JSON.stringify({
309
- requestId,
310
- error: (error as Error).message,
311
- })
312
- );
313
- }
314
- break;
315
-
316
- case "config:get-all":
317
- try {
318
- const configs: Record<string, any> = {};
319
-
320
- // 获取 App 配置
321
- configs["app"] = app.getConfig();
322
-
323
- // 获取所有插件配置
324
- for (const plugin of app.dependencyList) {
325
- if (plugin.config && Object.keys(plugin.config).length > 0) {
326
- configs[plugin.name] = plugin.config;
327
- }
328
- }
329
-
330
- ws.send(
331
- JSON.stringify({
332
- requestId,
333
- data: configs,
334
- })
335
- );
336
- } catch (error) {
337
- ws.send(
338
- JSON.stringify({
339
- requestId,
340
- error: (error as Error).message,
341
- })
342
- );
343
- }
344
- break;
345
-
346
- case "schema:get-all":
347
- try {
348
- const schemas: Record<string, any> = {};
349
-
350
- // 获取 App Schema
351
- const appSchema = app.schema?.toJSON();
352
- if (appSchema) {
353
- schemas["app"] = appSchema;
354
- }
355
-
356
- // 获取所有插件 Schema
357
- for (const plugin of app.dependencyList) {
358
- const schema = plugin.schema?.toJSON();
359
- if (schema) {
360
- schemas[plugin.name] = schema;
361
- }
362
- }
121
+ const stat = fs.statSync(filename);
122
+ if (!stat.isFile()) {
123
+ ctx.status = 404;
124
+ return;
125
+ }
126
+ } catch (error) {
127
+ ctx.status = 404;
128
+ return;
129
+ }
363
130
 
364
- ws.send(
365
- JSON.stringify({
366
- requestId,
367
- data: schemas,
368
- })
369
- );
370
- } catch (error) {
371
- ws.send(
372
- JSON.stringify({
373
- requestId,
374
- error: (error as Error).message,
375
- })
376
- );
377
- }
378
- break;
131
+ ctx.type = path.extname(filename);
132
+ ctx.type = mime.getType(filename) || ctx.type;
133
+ return (ctx.body = fs.createReadStream(filename));
134
+ };
135
+
136
+ // 1. 检查是否是动态入口
137
+ if (Object.keys(webServer.entries).includes(name)) {
138
+ return sendFile(path.resolve(process.cwd(), webServer.entries[name]));
139
+ }
379
140
 
380
- // 其他消息类型保持不变,让 console 插件自己处理
141
+ // 2. 检查是否是静态文件
142
+ const filename = path.resolve(root, name);
143
+ if (filename.startsWith(root) || filename.includes("node_modules")) {
144
+ try {
145
+ if (fs.existsSync(filename)) {
146
+ const fileState = fs.statSync(filename);
147
+ // 只处理常规文件,忽略目录、socket、符号链接等
148
+ if (
149
+ fileState.isFile() &&
150
+ !fileState.isSocket() &&
151
+ !fileState.isFIFO()
152
+ ) {
153
+ return sendFile(filename);
154
+ }
381
155
  }
382
156
  } catch (error) {
383
- console.error("WebSocket 消息处理错误:", error);
384
- ws.send(
385
- JSON.stringify({
386
- error: "Invalid message format",
387
- })
388
- );
157
+ // 忽略文件系统错误,继续处理
158
+ console.warn(`文件访问错误: ${filename}`, (error as Error).message);
389
159
  }
390
- });
391
-
392
- ws.on("close", () => {});
160
+ } else {
161
+ // 安全检查:路径不在允许范围内
162
+ return (ctx.status = 403);
163
+ }
393
164
 
394
- ws.on("error", (error) => {
395
- // console.error 已替换为注释
396
- });
165
+ // 3. 所有其他路径(包括 SPA 路由)都返回 index.html
166
+ // 这样前端路由可以正确处理
167
+ const indexFile = path.resolve(root, "index.html");
168
+ if(!isDev) return sendFile(indexFile);
169
+ const template = fs.readFileSync(indexFile, "utf8");
170
+ ctx.type = "html";
171
+ ctx.body = await webServer.vite!.transformIndexHtml(url, template);
397
172
  });
398
-
399
173
  // 定时通知客户端更新数据
400
174
  const dataUpdateInterval = setInterval(() => {
401
- notifyDataUpdate();
175
+ notifyDataUpdate(webServer);
402
176
  }, 5000); // 每5秒通知一次更新
403
-
177
+ setupWebSocket(webServer);
404
178
  // 插件卸载时清理定时器
405
179
  process.on("exit", () => {
406
180
  clearInterval(dataUpdateInterval);
@@ -412,7 +186,7 @@ useContext("router", async (router) => {
412
186
  return webServer;
413
187
  },
414
188
  async dispose(server) {
415
- await server.vite.close();
189
+ await server.vite?.close();
416
190
  server.ws.close();
417
191
  },
418
192
  });
@@ -0,0 +1,109 @@
1
+ import WebSocket from "ws";
2
+ import type { WebServer } from "./index.js";
3
+
4
+ /**
5
+ * 设置 WebSocket 连接处理
6
+ */
7
+ export function setupWebSocket(webServer: WebServer) {
8
+ webServer.ws.on("connection", (ws: WebSocket) => {
9
+ // 发送初始数据同步
10
+ ws.send(
11
+ JSON.stringify({
12
+ type: "sync",
13
+ data: {
14
+ key: "entries",
15
+ value: Object.values(webServer.entries),
16
+ },
17
+ })
18
+ );
19
+
20
+ // 通知客户端进行数据初始化
21
+ ws.send(
22
+ JSON.stringify({
23
+ type: "init-data",
24
+ timestamp: Date.now(),
25
+ })
26
+ );
27
+
28
+ // 处理客户端消息
29
+ ws.on("message", async (data) => {
30
+ try {
31
+ const message = JSON.parse(data.toString());
32
+ await handleWebSocketMessage(ws, message, webServer);
33
+ } catch (error) {
34
+ console.error("WebSocket 消息处理错误:", error);
35
+ ws.send(
36
+ JSON.stringify({
37
+ error: "Invalid message format",
38
+ })
39
+ );
40
+ }
41
+ });
42
+
43
+ ws.on("close", () => {
44
+ // 连接关闭时的清理工作
45
+ });
46
+
47
+ ws.on("error", (error) => {
48
+ console.error("WebSocket 错误:", error);
49
+ });
50
+ });
51
+ }
52
+
53
+ /**
54
+ * 处理 WebSocket 消息
55
+ */
56
+ async function handleWebSocketMessage(
57
+ ws: WebSocket,
58
+ message: any,
59
+ webServer: WebServer
60
+ ) {
61
+ const { type, requestId } = message;
62
+
63
+ switch (type) {
64
+ case "ping":
65
+ // 心跳检测
66
+ ws.send(JSON.stringify({ type: "pong", requestId }));
67
+ break;
68
+
69
+ case "entries:get":
70
+ // 获取所有入口文件
71
+ ws.send(
72
+ JSON.stringify({
73
+ requestId,
74
+ data: Object.values(webServer.entries),
75
+ })
76
+ );
77
+ break;
78
+
79
+ default:
80
+ // 未知消息类型
81
+ ws.send(
82
+ JSON.stringify({
83
+ requestId,
84
+ error: `Unknown message type: ${type}`,
85
+ })
86
+ );
87
+ }
88
+ }
89
+
90
+ /**
91
+ * 广播消息给所有连接的客户端
92
+ */
93
+ export function broadcastToAll(webServer: WebServer, message: any) {
94
+ for (const ws of webServer.ws.clients || []) {
95
+ if (ws.readyState === WebSocket.OPEN) {
96
+ ws.send(JSON.stringify(message));
97
+ }
98
+ }
99
+ }
100
+
101
+ /**
102
+ * 通知数据更新
103
+ */
104
+ export function notifyDataUpdate(webServer: WebServer) {
105
+ broadcastToAll(webServer, {
106
+ type: "data-update",
107
+ timestamp: Date.now(),
108
+ });
109
+ }
@@ -0,0 +1 @@
1
+ Not found: /react-dom@19.2.0/umd/react-dom.production.min.js
@@ -0,0 +1 @@
1
+ Not found: /react@19.2.0/umd/react.production.min.js
@@ -1,12 +1,27 @@
1
1
  /** @type {import('tailwindcss').Config} */
2
+ import path from 'path';
2
3
  const cwd = process.cwd();
4
+
5
+ // 安全的路径构建函数,避免扫描特殊文件
6
+ const safePath = (...parts) => {
7
+ try {
8
+ return path.resolve(...parts);
9
+ } catch (error) {
10
+ console.warn(`Tailwind 路径构建失败: ${parts.join('/')}`, error.message);
11
+ return '';
12
+ }
13
+ };
14
+
3
15
  export default {
4
16
  content: [
5
17
  "./index.html",
6
18
  './src/**/*.{js,ts,jsx,tsx,mdx}',
7
- `${cwd}/**/radix-ui/**/*.{js,ts,jsx,tsx}`,
8
- `${cwd}/**/@radix-ui/themes/**/*.{js,ts,jsx,tsx}`,
9
- ],
19
+ './node_modules/@radix-ui/**/*.{js,ts,jsx,tsx}',
20
+ `${cwd}/node_modules/@zhin.js/*/client/**/*.{js,ts,jsx,tsx}`,
21
+ `${cwd}/node_modules/@zhin.js/*/dist/**/*.{js,ts,jsx,tsx}`,
22
+ `${cwd}/plugins/*/client/**/*.{js,ts,jsx,tsx}`,
23
+ `${cwd}/client/**/*.{js,ts,jsx,tsx}`,
24
+ ].filter(Boolean), // 过滤掉空字符串
10
25
  theme: {
11
26
  extend: {
12
27
  colors: {
package/lib/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../app/bin.ts"],"names":[],"mappings":""}
package/lib/bin.js ADDED
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ import { buildCurrentPlugin, buildConsoleClient } from "./build.js";
3
+ import path from "path";
4
+ import { fileURLToPath } from "url";
5
+ const args = process.argv.slice(2);
6
+ const command = args[0];
7
+ async function main() {
8
+ try {
9
+ switch (command) {
10
+ case "build":
11
+ // 构建当前目录的插件客户端代码
12
+ console.log("🔨 Building plugin client...");
13
+ await buildCurrentPlugin();
14
+ break;
15
+ case "build:console":
16
+ // 构建 console 插件的客户端代码
17
+ console.log("🔨 Building console client...");
18
+ const consoleRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
19
+ await buildConsoleClient({ consoleRoot });
20
+ break;
21
+ default:
22
+ console.log(`
23
+ Zhin.js Client Builder
24
+
25
+ Usage:
26
+ zhin-client build Build current plugin's client code
27
+ zhin-client build:console Build console plugin's client code (SPA mode)
28
+
29
+ Examples:
30
+ # Build a plugin (single file mode)
31
+ cd my-plugin && zhin-client build
32
+
33
+ # Build console (SPA mode)
34
+ zhin-client build:console
35
+ `);
36
+ process.exit(1);
37
+ }
38
+ }
39
+ catch (error) {
40
+ console.error("❌ Build failed:", error);
41
+ process.exit(1);
42
+ }
43
+ }
44
+ main();
45
+ //# sourceMappingURL=bin.js.map
package/lib/bin.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../app/bin.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AACpE,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AAExB,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,QAAQ,OAAO,EAAE,CAAC;YAChB,KAAK,OAAO;gBACV,iBAAiB;gBACjB,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;gBAC5C,MAAM,kBAAkB,EAAE,CAAC;gBAC3B,MAAM;YAER,KAAK,eAAe;gBAClB,sBAAsB;gBACtB,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC;gBAC7C,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAC9B,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAC5C,IAAI,CACL,CAAC;gBACF,MAAM,kBAAkB,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC;gBAC1C,MAAM;YAER;gBACE,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;SAaX,CAAC,CAAC;gBACH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;QACxC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}