@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/README.md +86 -21
- package/app/bin.ts +52 -0
- package/app/build.ts +211 -0
- package/app/dev.ts +84 -0
- package/app/index.ts +89 -315
- package/app/websocket.ts +109 -0
- package/client/public/vendor/react-dom.production.min.js +1 -0
- package/client/public/vendor/react.production.min.js +1 -0
- package/client/tailwind.config.js +18 -3
- package/lib/bin.d.ts +3 -0
- package/lib/bin.d.ts.map +1 -0
- package/lib/bin.js +45 -0
- package/lib/bin.js.map +1 -0
- package/lib/build.d.ts +33 -0
- package/lib/build.d.ts.map +1 -0
- package/lib/build.js +168 -0
- package/lib/build.js.map +1 -0
- package/lib/dev.d.ts +16 -0
- package/lib/dev.d.ts.map +1 -0
- package/lib/dev.js +71 -0
- package/lib/dev.js.map +1 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +89 -260
- package/lib/index.js.map +1 -1
- package/lib/websocket.d.ts +14 -0
- package/lib/websocket.d.ts.map +1 -0
- package/lib/websocket.js +85 -0
- package/lib/websocket.js.map +1 -0
- package/package.json +9 -3
package/app/index.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { register, useContext,
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
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 {
|
|
9
|
-
import
|
|
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
|
|
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
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
204
|
-
|
|
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
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
JSON.stringify({
|
|
386
|
-
error: "Invalid message format",
|
|
387
|
-
})
|
|
388
|
-
);
|
|
157
|
+
// 忽略文件系统错误,继续处理
|
|
158
|
+
console.warn(`文件访问错误: ${filename}`, (error as Error).message);
|
|
389
159
|
}
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
|
|
160
|
+
} else {
|
|
161
|
+
// 安全检查:路径不在允许范围内
|
|
162
|
+
return (ctx.status = 403);
|
|
163
|
+
}
|
|
393
164
|
|
|
394
|
-
|
|
395
|
-
|
|
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
|
|
189
|
+
await server.vite?.close();
|
|
416
190
|
server.ws.close();
|
|
417
191
|
},
|
|
418
192
|
});
|
package/app/websocket.ts
ADDED
|
@@ -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
|
-
|
|
8
|
-
`${cwd}
|
|
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
package/lib/bin.d.ts.map
ADDED
|
@@ -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"}
|