@zhin.js/console 1.0.39 → 1.0.41
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 +17 -0
- package/client/src/components/PluginConfigForm/index.tsx +20 -5
- package/client/src/components/PluginConfigForm/types.ts +1 -0
- package/client/src/main.tsx +22 -6
- package/client/src/pages/dashboard-config.tsx +421 -0
- package/client/src/pages/dashboard-env.tsx +219 -0
- package/dist/client.js +1 -1
- package/dist/index.js +137 -6
- package/dist/style.css +2 -2
- package/lib/index.js +204 -81
- package/lib/websocket.d.ts +0 -9
- package/lib/websocket.js +174 -56
- package/package.json +7 -6
package/lib/index.js
CHANGED
|
@@ -1,40 +1,45 @@
|
|
|
1
1
|
import { usePlugin } from '@zhin.js/core';
|
|
2
2
|
import mime from 'mime';
|
|
3
|
-
import * as
|
|
4
|
-
import
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
import fs__default from 'fs';
|
|
5
|
+
import * as path3 from 'path';
|
|
6
|
+
import path3__default from 'path';
|
|
5
7
|
import WebSocket from 'ws';
|
|
6
8
|
import { transform } from 'esbuild';
|
|
7
9
|
|
|
8
10
|
// src/index.ts
|
|
9
11
|
var { root, logger } = usePlugin();
|
|
12
|
+
var ENV_WHITELIST = [".env", ".env.development", ".env.production"];
|
|
13
|
+
function resolveConfigKey(pluginName) {
|
|
14
|
+
const schemaService = root.inject("schema");
|
|
15
|
+
return schemaService?.resolveConfigKey(pluginName) ?? pluginName;
|
|
16
|
+
}
|
|
17
|
+
function getPluginKeys() {
|
|
18
|
+
const schemaService = root.inject("schema");
|
|
19
|
+
if (!schemaService) return [];
|
|
20
|
+
const keys = /* @__PURE__ */ new Set();
|
|
21
|
+
for (const [, configKey] of schemaService.getPluginKeyMap()) {
|
|
22
|
+
keys.add(configKey);
|
|
23
|
+
}
|
|
24
|
+
return Array.from(keys);
|
|
25
|
+
}
|
|
26
|
+
function getConfigFilePath() {
|
|
27
|
+
return path3__default.resolve(process.cwd(), "zhin.config.yml");
|
|
28
|
+
}
|
|
10
29
|
function setupWebSocket(webServer) {
|
|
11
30
|
webServer.ws.on("connection", (ws) => {
|
|
12
|
-
ws.send(
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
value: Object.values(webServer.entries)
|
|
18
|
-
}
|
|
19
|
-
})
|
|
20
|
-
);
|
|
21
|
-
ws.send(
|
|
22
|
-
JSON.stringify({
|
|
23
|
-
type: "init-data",
|
|
24
|
-
timestamp: Date.now()
|
|
25
|
-
})
|
|
26
|
-
);
|
|
31
|
+
ws.send(JSON.stringify({
|
|
32
|
+
type: "sync",
|
|
33
|
+
data: { key: "entries", value: Object.values(webServer.entries) }
|
|
34
|
+
}));
|
|
35
|
+
ws.send(JSON.stringify({ type: "init-data", timestamp: Date.now() }));
|
|
27
36
|
ws.on("message", async (data) => {
|
|
28
37
|
try {
|
|
29
38
|
const message = JSON.parse(data.toString());
|
|
30
39
|
await handleWebSocketMessage(ws, message, webServer);
|
|
31
40
|
} catch (error) {
|
|
32
41
|
console.error("WebSocket \u6D88\u606F\u5904\u7406\u9519\u8BEF:", error);
|
|
33
|
-
ws.send(
|
|
34
|
-
JSON.stringify({
|
|
35
|
-
error: "Invalid message format"
|
|
36
|
-
})
|
|
37
|
-
);
|
|
42
|
+
ws.send(JSON.stringify({ error: "Invalid message format" }));
|
|
38
43
|
}
|
|
39
44
|
});
|
|
40
45
|
ws.on("close", () => {
|
|
@@ -51,19 +56,50 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
51
56
|
ws.send(JSON.stringify({ type: "pong", requestId }));
|
|
52
57
|
break;
|
|
53
58
|
case "entries:get":
|
|
54
|
-
ws.send(
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
ws.send(JSON.stringify({ requestId, data: Object.values(webServer.entries) }));
|
|
60
|
+
break;
|
|
61
|
+
// ================================================================
|
|
62
|
+
// 配置文件原始 YAML 读写(用于配置管理页面)
|
|
63
|
+
// ================================================================
|
|
64
|
+
case "config:get-yaml":
|
|
65
|
+
try {
|
|
66
|
+
const filePath = getConfigFilePath();
|
|
67
|
+
const yaml = fs__default.existsSync(filePath) ? fs__default.readFileSync(filePath, "utf-8") : "";
|
|
68
|
+
ws.send(JSON.stringify({ requestId, data: { yaml, pluginKeys: getPluginKeys() } }));
|
|
69
|
+
} catch (error) {
|
|
70
|
+
ws.send(JSON.stringify({ requestId, error: `Failed to read config: ${error.message}` }));
|
|
71
|
+
}
|
|
60
72
|
break;
|
|
73
|
+
case "config:save-yaml":
|
|
74
|
+
try {
|
|
75
|
+
const { yaml } = message;
|
|
76
|
+
if (typeof yaml !== "string") {
|
|
77
|
+
ws.send(JSON.stringify({ requestId, error: "yaml field is required" }));
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
const filePath = getConfigFilePath();
|
|
81
|
+
fs__default.writeFileSync(filePath, yaml, "utf-8");
|
|
82
|
+
const configService2 = root.inject("config");
|
|
83
|
+
const loader = configService2.configs.get("zhin.config.yml");
|
|
84
|
+
if (loader) loader.load();
|
|
85
|
+
ws.send(JSON.stringify({ requestId, data: { success: true, message: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u9700\u91CD\u542F\u751F\u6548" } }));
|
|
86
|
+
} catch (error) {
|
|
87
|
+
ws.send(JSON.stringify({ requestId, error: `Failed to save config: ${error.message}` }));
|
|
88
|
+
}
|
|
89
|
+
break;
|
|
90
|
+
// ================================================================
|
|
91
|
+
// 插件配置(供 PluginConfigForm 使用)
|
|
92
|
+
// ================================================================
|
|
61
93
|
case "config:get":
|
|
62
94
|
try {
|
|
63
95
|
const configService2 = root.inject("config");
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
96
|
+
const rawConfig = configService2.getRaw("zhin.config.yml");
|
|
97
|
+
if (!pluginName) {
|
|
98
|
+
ws.send(JSON.stringify({ requestId, data: rawConfig }));
|
|
99
|
+
} else {
|
|
100
|
+
const configKey = resolveConfigKey(pluginName);
|
|
101
|
+
ws.send(JSON.stringify({ requestId, data: rawConfig[configKey] || {} }));
|
|
102
|
+
}
|
|
67
103
|
} catch (error) {
|
|
68
104
|
ws.send(JSON.stringify({ requestId, error: `Failed to get config: ${error.message}` }));
|
|
69
105
|
}
|
|
@@ -71,8 +107,17 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
71
107
|
case "config:get-all":
|
|
72
108
|
try {
|
|
73
109
|
const configService2 = root.inject("config");
|
|
74
|
-
const
|
|
75
|
-
|
|
110
|
+
const rawConfig = configService2.getRaw("zhin.config.yml");
|
|
111
|
+
const allConfigs = { ...rawConfig };
|
|
112
|
+
const schemaService = root.inject("schema");
|
|
113
|
+
if (schemaService) {
|
|
114
|
+
for (const [pName, configKey] of schemaService.getPluginKeyMap()) {
|
|
115
|
+
if (pName !== configKey) {
|
|
116
|
+
allConfigs[pName] = rawConfig[configKey] || {};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
ws.send(JSON.stringify({ requestId, data: allConfigs }));
|
|
76
121
|
} catch (error) {
|
|
77
122
|
ws.send(JSON.stringify({ requestId, error: `Failed to get all configs: ${error.message}` }));
|
|
78
123
|
}
|
|
@@ -84,28 +129,45 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
84
129
|
ws.send(JSON.stringify({ requestId, error: "Plugin name is required" }));
|
|
85
130
|
break;
|
|
86
131
|
}
|
|
132
|
+
const configKey = resolveConfigKey(pluginName);
|
|
87
133
|
const configService2 = root.inject("config");
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
134
|
+
const loader = configService2.configs.get("zhin.config.yml");
|
|
135
|
+
if (!loader) {
|
|
136
|
+
ws.send(JSON.stringify({ requestId, error: "Config file not loaded" }));
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
loader.patchKey(configKey, data);
|
|
140
|
+
broadcastToAll(webServer, { type: "config:updated", data: { pluginName, config: data } });
|
|
141
|
+
const schemaService = root.inject("schema");
|
|
142
|
+
const reloadable = schemaService?.isReloadable?.(pluginName) ?? false;
|
|
143
|
+
if (reloadable) {
|
|
144
|
+
const target = findPluginByConfigKey(root, pluginName);
|
|
145
|
+
if (target) {
|
|
146
|
+
try {
|
|
147
|
+
await target.reload();
|
|
148
|
+
ws.send(JSON.stringify({ requestId, data: { success: true, reloaded: true } }));
|
|
149
|
+
} catch (reloadErr) {
|
|
150
|
+
logger.warn(`\u91CD\u8F7D\u63D2\u4EF6 ${pluginName} \u5931\u8D25: ${reloadErr.message}`);
|
|
151
|
+
ws.send(JSON.stringify({ requestId, data: { success: true, reloaded: false, message: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u4F46\u91CD\u8F7D\u5931\u8D25" } }));
|
|
152
|
+
}
|
|
153
|
+
} else {
|
|
154
|
+
ws.send(JSON.stringify({ requestId, data: { success: true, reloaded: false, message: "\u914D\u7F6E\u5DF2\u4FDD\u5B58" } }));
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
ws.send(JSON.stringify({ requestId, data: { success: true, reloaded: false, message: "\u914D\u7F6E\u5DF2\u4FDD\u5B58\uFF0C\u9700\u91CD\u542F\u8FDB\u7A0B\u624D\u80FD\u751F\u6548" } }));
|
|
158
|
+
}
|
|
96
159
|
} catch (error) {
|
|
97
160
|
ws.send(JSON.stringify({ requestId, error: `Failed to set config: ${error.message}` }));
|
|
98
161
|
}
|
|
99
162
|
break;
|
|
163
|
+
// ================================================================
|
|
164
|
+
// Schema(供 PluginConfigForm 使用)
|
|
165
|
+
// ================================================================
|
|
100
166
|
case "schema:get":
|
|
101
167
|
try {
|
|
102
168
|
const schemaService = root.inject("schema");
|
|
103
169
|
const schema = pluginName && schemaService ? schemaService.get(pluginName) : null;
|
|
104
|
-
|
|
105
|
-
ws.send(JSON.stringify({ requestId, data: schema.toJSON() }));
|
|
106
|
-
} else {
|
|
107
|
-
ws.send(JSON.stringify({ requestId, data: null }));
|
|
108
|
-
}
|
|
170
|
+
ws.send(JSON.stringify({ requestId, data: schema ? schema.toJSON() : null }));
|
|
109
171
|
} catch (error) {
|
|
110
172
|
ws.send(JSON.stringify({ requestId, error: `Failed to get schema: ${error.message}` }));
|
|
111
173
|
}
|
|
@@ -115,8 +177,15 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
115
177
|
const schemaService = root.inject("schema");
|
|
116
178
|
const schemas = {};
|
|
117
179
|
if (schemaService) {
|
|
118
|
-
for (const
|
|
119
|
-
|
|
180
|
+
for (const record of schemaService.items) {
|
|
181
|
+
if (record.key && record.schema) {
|
|
182
|
+
schemas[record.key] = record.schema.toJSON();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
for (const [pName, configKey] of schemaService.getPluginKeyMap()) {
|
|
186
|
+
if (pName !== configKey && schemas[configKey]) {
|
|
187
|
+
schemas[pName] = schemas[configKey];
|
|
188
|
+
}
|
|
120
189
|
}
|
|
121
190
|
}
|
|
122
191
|
ws.send(JSON.stringify({ requestId, data: schemas }));
|
|
@@ -124,15 +193,67 @@ async function handleWebSocketMessage(ws, message, webServer) {
|
|
|
124
193
|
ws.send(JSON.stringify({ requestId, error: `Failed to get all schemas: ${error.message}` }));
|
|
125
194
|
}
|
|
126
195
|
break;
|
|
196
|
+
// ================================================================
|
|
197
|
+
// 环境变量文件管理
|
|
198
|
+
// ================================================================
|
|
199
|
+
case "env:list":
|
|
200
|
+
try {
|
|
201
|
+
const cwd = process.cwd();
|
|
202
|
+
const files = ENV_WHITELIST.map((name) => ({
|
|
203
|
+
name,
|
|
204
|
+
exists: fs__default.existsSync(path3__default.resolve(cwd, name))
|
|
205
|
+
}));
|
|
206
|
+
ws.send(JSON.stringify({ requestId, data: { files } }));
|
|
207
|
+
} catch (error) {
|
|
208
|
+
ws.send(JSON.stringify({ requestId, error: `Failed to list env files: ${error.message}` }));
|
|
209
|
+
}
|
|
210
|
+
break;
|
|
211
|
+
case "env:get":
|
|
212
|
+
try {
|
|
213
|
+
const { filename } = message;
|
|
214
|
+
if (!filename || !ENV_WHITELIST.includes(filename)) {
|
|
215
|
+
ws.send(JSON.stringify({ requestId, error: `Invalid env file: ${filename}` }));
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
const envPath = path3__default.resolve(process.cwd(), filename);
|
|
219
|
+
const content = fs__default.existsSync(envPath) ? fs__default.readFileSync(envPath, "utf-8") : "";
|
|
220
|
+
ws.send(JSON.stringify({ requestId, data: { content } }));
|
|
221
|
+
} catch (error) {
|
|
222
|
+
ws.send(JSON.stringify({ requestId, error: `Failed to read env file: ${error.message}` }));
|
|
223
|
+
}
|
|
224
|
+
break;
|
|
225
|
+
case "env:save":
|
|
226
|
+
try {
|
|
227
|
+
const { filename, content } = message;
|
|
228
|
+
if (!filename || !ENV_WHITELIST.includes(filename)) {
|
|
229
|
+
ws.send(JSON.stringify({ requestId, error: `Invalid env file: ${filename}` }));
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
if (typeof content !== "string") {
|
|
233
|
+
ws.send(JSON.stringify({ requestId, error: "content field is required" }));
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
const envPath = path3__default.resolve(process.cwd(), filename);
|
|
237
|
+
fs__default.writeFileSync(envPath, content, "utf-8");
|
|
238
|
+
ws.send(JSON.stringify({ requestId, data: { success: true, message: "\u73AF\u5883\u53D8\u91CF\u5DF2\u4FDD\u5B58\uFF0C\u9700\u91CD\u542F\u751F\u6548" } }));
|
|
239
|
+
} catch (error) {
|
|
240
|
+
ws.send(JSON.stringify({ requestId, error: `Failed to save env file: ${error.message}` }));
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
127
243
|
default:
|
|
128
|
-
ws.send(
|
|
129
|
-
JSON.stringify({
|
|
130
|
-
requestId,
|
|
131
|
-
error: `Unknown message type: ${type}`
|
|
132
|
-
})
|
|
133
|
-
);
|
|
244
|
+
ws.send(JSON.stringify({ requestId, error: `Unknown message type: ${type}` }));
|
|
134
245
|
}
|
|
135
246
|
}
|
|
247
|
+
function findPluginByConfigKey(rootPlugin, configKey) {
|
|
248
|
+
for (const child of rootPlugin.children) {
|
|
249
|
+
if (child.name === configKey || child.name.endsWith(`-${configKey}`) || child.name.includes(configKey)) {
|
|
250
|
+
return child;
|
|
251
|
+
}
|
|
252
|
+
const found = findPluginByConfigKey(child, configKey);
|
|
253
|
+
if (found) return found;
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
136
257
|
function broadcastToAll(webServer, message) {
|
|
137
258
|
for (const ws of webServer.ws.clients || []) {
|
|
138
259
|
if (ws.readyState === WebSocket.OPEN) {
|
|
@@ -141,16 +262,13 @@ function broadcastToAll(webServer, message) {
|
|
|
141
262
|
}
|
|
142
263
|
}
|
|
143
264
|
function notifyDataUpdate(webServer) {
|
|
144
|
-
broadcastToAll(webServer, {
|
|
145
|
-
type: "data-update",
|
|
146
|
-
timestamp: Date.now()
|
|
147
|
-
});
|
|
265
|
+
broadcastToAll(webServer, { type: "data-update", timestamp: Date.now() });
|
|
148
266
|
}
|
|
149
267
|
var cache = /* @__PURE__ */ new Map();
|
|
150
268
|
var TRANSFORMABLE_EXTS = [".ts", ".tsx", ".jsx"];
|
|
151
269
|
var RESOLVE_EXTS = [".tsx", ".ts", ".jsx", ".js"];
|
|
152
270
|
function isTransformable(filePath) {
|
|
153
|
-
const ext =
|
|
271
|
+
const ext = path3.extname(filePath).toLowerCase();
|
|
154
272
|
return TRANSFORMABLE_EXTS.includes(ext);
|
|
155
273
|
}
|
|
156
274
|
var IMPORT_RE = /(?:import|export)\s+.*?\s+from\s+['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
@@ -159,22 +277,22 @@ function isRelative(specifier) {
|
|
|
159
277
|
return specifier.startsWith("./") || specifier.startsWith("../");
|
|
160
278
|
}
|
|
161
279
|
function hasExtension(specifier) {
|
|
162
|
-
const base =
|
|
280
|
+
const base = path3.basename(specifier);
|
|
163
281
|
return base.includes(".") && !base.startsWith(".");
|
|
164
282
|
}
|
|
165
283
|
function resolveRelativeImport(specifier, fromFile) {
|
|
166
284
|
if (!isRelative(specifier)) return specifier;
|
|
167
285
|
if (hasExtension(specifier)) return specifier;
|
|
168
|
-
const dir =
|
|
169
|
-
const target =
|
|
286
|
+
const dir = path3.dirname(fromFile);
|
|
287
|
+
const target = path3.resolve(dir, specifier);
|
|
170
288
|
for (const ext of RESOLVE_EXTS) {
|
|
171
|
-
if (
|
|
289
|
+
if (fs.existsSync(target + ext)) {
|
|
172
290
|
return specifier + ext;
|
|
173
291
|
}
|
|
174
292
|
}
|
|
175
|
-
if (
|
|
293
|
+
if (fs.existsSync(target) && fs.statSync(target).isDirectory()) {
|
|
176
294
|
for (const ext of RESOLVE_EXTS) {
|
|
177
|
-
if (
|
|
295
|
+
if (fs.existsSync(path3.join(target, `index${ext}`))) {
|
|
178
296
|
return specifier + `/index${ext}`;
|
|
179
297
|
}
|
|
180
298
|
}
|
|
@@ -194,13 +312,13 @@ function rewriteImports(code, fromFile) {
|
|
|
194
312
|
return code;
|
|
195
313
|
}
|
|
196
314
|
async function transformFile(filePath) {
|
|
197
|
-
const stat =
|
|
315
|
+
const stat = fs.statSync(filePath);
|
|
198
316
|
const cached = cache.get(filePath);
|
|
199
317
|
if (cached && cached.mtime === stat.mtimeMs) {
|
|
200
318
|
return cached.code;
|
|
201
319
|
}
|
|
202
|
-
const source =
|
|
203
|
-
const ext =
|
|
320
|
+
const source = fs.readFileSync(filePath, "utf-8");
|
|
321
|
+
const ext = path3.extname(filePath).toLowerCase();
|
|
204
322
|
const loader = ext === ".tsx" ? "tsx" : ext === ".ts" ? "ts" : ext === ".jsx" ? "jsx" : "js";
|
|
205
323
|
const result = await transform(source, {
|
|
206
324
|
loader,
|
|
@@ -238,11 +356,11 @@ if (enabled) {
|
|
|
238
356
|
const genToken = () => Math.random().toString(36).slice(2, 8);
|
|
239
357
|
const watchedDirs = /* @__PURE__ */ new Set();
|
|
240
358
|
const watchers = [];
|
|
241
|
-
|
|
242
|
-
const distDir =
|
|
359
|
+
path3.join(import.meta.dirname, "../client");
|
|
360
|
+
const distDir = path3.join(import.meta.dirname, "../dist");
|
|
243
361
|
const resolveFile = (name) => {
|
|
244
|
-
const distPath =
|
|
245
|
-
if (
|
|
362
|
+
const distPath = path3.resolve(distDir, name);
|
|
363
|
+
if (fs.existsSync(distPath)) return distPath;
|
|
246
364
|
return null;
|
|
247
365
|
};
|
|
248
366
|
const webServer = {
|
|
@@ -250,8 +368,8 @@ if (enabled) {
|
|
|
250
368
|
addEntry(entry) {
|
|
251
369
|
const hash = Date.now().toString(16) + Math.random().toString(16).slice(2, 8);
|
|
252
370
|
const entryFile = typeof entry === "string" ? entry : entry.production;
|
|
253
|
-
const dir =
|
|
254
|
-
const filename =
|
|
371
|
+
const dir = path3.dirname(entryFile);
|
|
372
|
+
const filename = path3.basename(entryFile);
|
|
255
373
|
let token;
|
|
256
374
|
for (const [t, d] of entryBases) {
|
|
257
375
|
if (d === dir) {
|
|
@@ -285,10 +403,15 @@ if (enabled) {
|
|
|
285
403
|
if (ctx.path.startsWith("/api/") || ctx.path === "/api" || ctx.path.startsWith("/pub/") || ctx.path === "/pub") {
|
|
286
404
|
return next();
|
|
287
405
|
}
|
|
406
|
+
const whiteList = router.whiteList || [];
|
|
407
|
+
const isRegistered = whiteList.some(
|
|
408
|
+
(p) => typeof p === "string" && !p.includes("*") && !p.startsWith("/vite") && ctx.path.startsWith(p)
|
|
409
|
+
);
|
|
410
|
+
if (isRegistered) return next();
|
|
288
411
|
const name = ctx.path.slice(1);
|
|
289
412
|
const sendFile = async (filename) => {
|
|
290
413
|
try {
|
|
291
|
-
const stat =
|
|
414
|
+
const stat = fs.statSync(filename);
|
|
292
415
|
if (!stat.isFile()) {
|
|
293
416
|
ctx.status = 404;
|
|
294
417
|
return;
|
|
@@ -310,9 +433,9 @@ if (enabled) {
|
|
|
310
433
|
return;
|
|
311
434
|
}
|
|
312
435
|
}
|
|
313
|
-
ctx.type =
|
|
436
|
+
ctx.type = path3.extname(filename);
|
|
314
437
|
ctx.type = mime.getType(filename) || ctx.type;
|
|
315
|
-
return ctx.body =
|
|
438
|
+
return ctx.body = fs.createReadStream(filename);
|
|
316
439
|
};
|
|
317
440
|
if (ctx.path.startsWith("/vite/@ext/")) {
|
|
318
441
|
const rest = ctx.path.replace("/vite/@ext/", "");
|
|
@@ -328,13 +451,13 @@ if (enabled) {
|
|
|
328
451
|
ctx.status = 404;
|
|
329
452
|
return;
|
|
330
453
|
}
|
|
331
|
-
const fullPath =
|
|
332
|
-
const safePfx = baseDir.endsWith(
|
|
454
|
+
const fullPath = path3.resolve(baseDir, relPath);
|
|
455
|
+
const safePfx = baseDir.endsWith(path3.sep) ? baseDir : baseDir + path3.sep;
|
|
333
456
|
if (!fullPath.startsWith(safePfx) && fullPath !== baseDir) {
|
|
334
457
|
ctx.status = 403;
|
|
335
458
|
return;
|
|
336
459
|
}
|
|
337
|
-
if (
|
|
460
|
+
if (fs.existsSync(fullPath)) {
|
|
338
461
|
return sendFile(fullPath);
|
|
339
462
|
}
|
|
340
463
|
ctx.status = 404;
|
|
@@ -343,7 +466,7 @@ if (enabled) {
|
|
|
343
466
|
const resolved = resolveFile(name);
|
|
344
467
|
if (resolved) {
|
|
345
468
|
try {
|
|
346
|
-
const fileState =
|
|
469
|
+
const fileState = fs.statSync(resolved);
|
|
347
470
|
if (fileState.isFile() && !fileState.isSocket() && !fileState.isFIFO()) {
|
|
348
471
|
return sendFile(resolved);
|
|
349
472
|
}
|
package/lib/websocket.d.ts
CHANGED
|
@@ -2,17 +2,8 @@ import { WebServer } from './index.js';
|
|
|
2
2
|
import '@zhin.js/http';
|
|
3
3
|
import 'ws';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* 设置 WebSocket 连接处理
|
|
7
|
-
*/
|
|
8
5
|
declare function setupWebSocket(webServer: WebServer): void;
|
|
9
|
-
/**
|
|
10
|
-
* 广播消息给所有连接的客户端
|
|
11
|
-
*/
|
|
12
6
|
declare function broadcastToAll(webServer: WebServer, message: any): void;
|
|
13
|
-
/**
|
|
14
|
-
* 通知数据更新
|
|
15
|
-
*/
|
|
16
7
|
declare function notifyDataUpdate(webServer: WebServer): void;
|
|
17
8
|
|
|
18
9
|
export { broadcastToAll, notifyDataUpdate, setupWebSocket };
|