node-karin 1.15.5 → 1.16.1

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 (89) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/adapter-BqlH3u3X.mjs +218 -0
  3. package/dist/app-DdMQbBEY.mjs +4109 -0
  4. package/dist/cache-CPcPeo6N.mjs +163 -0
  5. package/dist/chunk-NzVPYdc1.mjs +21 -0
  6. package/dist/cli/index.cjs +10900 -1
  7. package/dist/cli/index.d.ts +1 -1
  8. package/dist/cli/index.mjs +10770 -10224
  9. package/dist/file-ZGuqNDd-.mjs +15987 -0
  10. package/dist/file-dGy9of8-.mjs +268 -0
  11. package/dist/fsSync-Cf5MWILk.mjs +65 -0
  12. package/dist/index.d.ts +12235 -12738
  13. package/dist/index.mjs +2054 -25247
  14. package/dist/internal-DupfycKE.mjs +597 -0
  15. package/dist/kv-DZp4UIxg.mjs +192 -0
  16. package/dist/module/art-template.d.ts +2 -13
  17. package/dist/module/art-template.mjs +3 -1
  18. package/dist/module/axios.d.ts +3 -2
  19. package/dist/module/axios.mjs +5 -2
  20. package/dist/module/chalk.d.ts +3 -2
  21. package/dist/module/chalk.mjs +5 -2
  22. package/dist/module/chokidar.d.ts +3 -2
  23. package/dist/module/chokidar.mjs +5 -2
  24. package/dist/module/express.d.ts +2 -1
  25. package/dist/module/express.mjs +3 -1
  26. package/dist/module/lodash.d.ts +2 -1
  27. package/dist/module/lodash.mjs +3 -1
  28. package/dist/module/log4js.d.ts +3 -2
  29. package/dist/module/log4js.mjs +5 -2
  30. package/dist/module/moment.d.ts +2 -1
  31. package/dist/module/moment.mjs +3 -1
  32. package/dist/module/node-schedule.d.ts +3 -2
  33. package/dist/module/node-schedule.mjs +5 -2
  34. package/dist/module/redis.d.ts +3 -2
  35. package/dist/module/redis.mjs +5 -2
  36. package/dist/module/sqlite3.d.ts +3 -2
  37. package/dist/module/sqlite3.mjs +5 -2
  38. package/dist/module/ws.d.ts +3 -2
  39. package/dist/module/ws.mjs +5 -2
  40. package/dist/module/yaml.d.ts +3 -2
  41. package/dist/module/yaml.mjs +5 -2
  42. package/dist/queue-CnKedaZA.mjs +70 -0
  43. package/dist/redis-aLJ7wbJH.mjs +1556 -0
  44. package/dist/render-DPqueDZr.mjs +170 -0
  45. package/dist/root.d.ts +46 -46
  46. package/dist/root.mjs +136 -93
  47. package/dist/router-zPSN9-tY.mjs +124 -0
  48. package/dist/server-DT64D-m-.mjs +38 -0
  49. package/dist/snapka-BTlnZOyI.mjs +450 -0
  50. package/dist/sqlite-Dcj9jlW9.mjs +307 -0
  51. package/dist/start/app.d.ts +1 -1
  52. package/dist/start/app.mjs +14 -7
  53. package/dist/start/index.d.ts +1 -1
  54. package/dist/start/index.mjs +325 -656
  55. package/dist/template-Djk6y0uC.mjs +133 -0
  56. package/dist/terminalManager-Lxa8Sm06.mjs +783 -0
  57. package/dist/uptime-C121X_rq.mjs +210 -0
  58. package/dist/web/{CompressaPRO-GX.woff2.br → CompressaPRO-GX.woff2} +0 -0
  59. package/dist/web/assets/css/style-CBB8wM_W.css +14880 -0
  60. package/dist/web/assets/js/entry-Blf4Trpx.js +258540 -0
  61. package/dist/web/{googleapis.woff2.br → googleapis.woff2} +0 -0
  62. package/dist/web/index.html +2 -15
  63. package/dist/web/karin.png +0 -0
  64. package/dist/web/sha256.min.js +9 -0
  65. package/dist/ws-BLDoC2gV.mjs +80 -0
  66. package/dist/ws-CcoWd3Ar.mjs +106 -0
  67. package/package.json +7 -7
  68. package/dist/global.d.d.ts +0 -68
  69. package/dist/types-hAhbXJDZ.d.ts +0 -109
  70. package/dist/web/assets/css/components-ep7vm38G.css +0 -1
  71. package/dist/web/assets/css/index-Dadvd9mn.css.br +0 -0
  72. package/dist/web/assets/css/vendor-editor-CFbL2ovg.css.br +0 -0
  73. package/dist/web/assets/css/vendor-others-ZgkIHsf0.css +0 -1
  74. package/dist/web/assets/js/components-CU2xw4lY.js.br +0 -0
  75. package/dist/web/assets/js/entry-Dvb7eYLE.js.br +0 -0
  76. package/dist/web/assets/js/hooks-CRfhs4ON.js.br +0 -0
  77. package/dist/web/assets/js/page-404.tsx-DYMd_RI_.js +0 -1
  78. package/dist/web/assets/js/page-dashboard-CG60V_Z-.js.br +0 -0
  79. package/dist/web/assets/js/page-loading.tsx-wY8a9me3.js.br +0 -0
  80. package/dist/web/assets/js/page-login.tsx-B54ZOEZB.js.br +0 -0
  81. package/dist/web/assets/js/utils-C9nWTSuo.js +0 -2
  82. package/dist/web/assets/js/vendor-editor-BmqYP7lh.js.br +0 -0
  83. package/dist/web/assets/js/vendor-heroui-ClBCy2zk.js.br +0 -0
  84. package/dist/web/assets/js/vendor-others-6GiMrjd4.js.br +0 -0
  85. package/dist/web/assets/js/vendor-react-Dc9jdQiK.js.br +0 -0
  86. package/dist/web/assets/js/vendor-ui-utils-D0xkboLL.js.br +0 -0
  87. package/dist/web/assets/js/vendor-visual-saF8KLH_.js.br +0 -0
  88. package/dist/web/karin.png.br +0 -0
  89. package/dist/web/sha256.min.js.br +0 -0
@@ -0,0 +1,783 @@
1
+ import { t as __exportAll } from "./chunk-NzVPYdc1.mjs";
2
+ import { karinPathLogs } from "./root.mjs";
3
+ import { br as createUnauthorizedResponse, lr as HTTPStatusCode, sr as verifyJwt, tr as authKey, ur as createAccessTokenExpiredResponse, vr as createServerErrorResponse } from "./file-ZGuqNDd-.mjs";
4
+ import { r as listeners, v as WS_CONNECTION_TERMINAL } from "./internal-DupfycKE.mjs";
5
+ import chalk from "chalk";
6
+ import path from "path";
7
+ import fs from "node:fs";
8
+ import { URL } from "node:url";
9
+ import axios from "axios";
10
+ import { randomUUID } from "node:crypto";
11
+ import { WebSocket } from "ws";
12
+ import os from "node:os";
13
+ import log4js from "log4js";
14
+ import fs$1 from "fs";
15
+
16
+ //#region src/service/logger/index.ts
17
+ /**
18
+ * 创建日志记录器
19
+ * @param options - 配置项
20
+ */
21
+ const initLogger = () => {
22
+ const level = process.env.LOG_LEVEL || "info";
23
+ const daysToKeep = Number(process.env.LOG_DAYS_TO_KEEP) || 30;
24
+ const config = {
25
+ appenders: {
26
+ console: {
27
+ type: "console",
28
+ layout: {
29
+ type: "pattern",
30
+ pattern: `%[[Karin][%d{hh:mm:ss.SSS}][%4.4p]%] ${process.env.RUNTIME === "tsx" ? "[%f{3}:%l] " : ""}%m`
31
+ }
32
+ },
33
+ overall: {
34
+ /** 输出到文件 */
35
+ type: "dateFile",
36
+ /** 日志文件名 */
37
+ filename: "@karinjs/logs/logger",
38
+ /** 日期后缀 */
39
+ pattern: "yyyy-MM-dd.log",
40
+ /** 日期后缀 */
41
+ keepFileExt: true,
42
+ /** 日志文件名中包含日期模式 */
43
+ alwaysIncludePattern: true,
44
+ /** 日志文件保留天数 */
45
+ numBackups: daysToKeep || 14,
46
+ /** 日期后缀分隔符 */
47
+ fileNameSep: ".",
48
+ /** 压缩 */
49
+ /** 日志输出格式 */
50
+ layout: {
51
+ type: "pattern",
52
+ pattern: "[%d{hh:mm:ss.SSS}][%4.4p] %m"
53
+ }
54
+ },
55
+ errorFile: {
56
+ /** 输出到文件 */
57
+ type: "dateFile",
58
+ /** 日志文件名 */
59
+ filename: "@karinjs/logs/error/logger",
60
+ /** 日期后缀 */
61
+ pattern: "yyyy-MM-dd.log",
62
+ /** 日期后缀 */
63
+ alwaysIncludePattern: true,
64
+ /** 日志文件保留天数 */
65
+ numBackups: daysToKeep || 14,
66
+ /** 压缩 */
67
+ /** 保留文件扩展名 */
68
+ keepFileExt: true,
69
+ /** 日期后缀分隔符 */
70
+ fileNameSep: ".",
71
+ /** 日志输出格式 */
72
+ layout: {
73
+ type: "pattern",
74
+ pattern: "[%d{hh:mm:ss.SSS}][%4.4p] %m"
75
+ }
76
+ },
77
+ errors: {
78
+ /** 错误日志过滤器 */
79
+ type: "logLevelFilter",
80
+ /** 目标appender */
81
+ appender: "errorFile",
82
+ /** 只记录错误级别及以上的日志 */
83
+ level: "error"
84
+ }
85
+ },
86
+ categories: { default: {
87
+ appenders: [
88
+ "console",
89
+ "overall",
90
+ "errors"
91
+ ],
92
+ level,
93
+ enableCallStack: process.env.RUNTIME === "tsx"
94
+ } },
95
+ levels: { handler: {
96
+ value: 15e3,
97
+ colour: "cyan"
98
+ } }
99
+ };
100
+ /** 碎片化: 将日志分片,达到指定大小后自动切割 日志较多的情况下不建议与整体化同时开启 */
101
+ return log4js.configure(config);
102
+ };
103
+ /**
104
+ * 为logger添加自定义颜色
105
+ * @param logger - 日志记录器
106
+ */
107
+ const addColor = (Logger, color) => {
108
+ const logger = Logger;
109
+ logger.chalk = chalk;
110
+ logger.red = chalk.red;
111
+ logger.green = chalk.green;
112
+ logger.yellow = chalk.yellow;
113
+ logger.blue = chalk.blue;
114
+ logger.magenta = chalk.magenta;
115
+ logger.cyan = chalk.cyan;
116
+ logger.white = chalk.white;
117
+ logger.gray = chalk.gray;
118
+ logger.violet = chalk.hex("#868ECC");
119
+ logger.fnc = chalk.hex(color || "#FFFF00");
120
+ logger.bot = (level, id, ...args) => {
121
+ switch (level) {
122
+ case "trace": return logger.trace(logger.violet(`[Bot:${id}]`), ...args);
123
+ case "debug": return logger.debug(logger.violet(`[Bot:${id}]`), ...args);
124
+ case "mark": return logger.mark(logger.violet(`[Bot:${id}]`), ...args);
125
+ case "info": return logger.info(logger.violet(`[Bot:${id}]`), ...args);
126
+ case "warn": return logger.warn(logger.violet(`[Bot:${id}]`), ...args);
127
+ case "error": return logger.error(logger.violet(`[Bot:${id}]`), ...args);
128
+ case "fatal": return logger.fatal(logger.violet(`[Bot:${id}]`), ...args);
129
+ default: return logger.info(logger.violet(`[Bot:${id}]`), ...args);
130
+ }
131
+ };
132
+ logger.setContextLayouts("pattern", {
133
+ type: "pattern",
134
+ pattern: "[%d{hh:mm:ss.SSS}][%4.4p] %m"
135
+ });
136
+ return logger;
137
+ };
138
+ /**
139
+ * 创建日志记录器
140
+ * @param dir - 日志文件夹
141
+ * @param options - 配置项
142
+ * @returns 日志记录器
143
+ */
144
+ const createLogger = () => {
145
+ const dir = karinPathLogs;
146
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
147
+ initLogger();
148
+ const logger = addColor(log4js.getLogger("default"));
149
+ global.logger = logger;
150
+ return logger;
151
+ };
152
+ /**
153
+ * @public
154
+ * @description 日志管理器
155
+ */
156
+ const logger$1 = createLogger();
157
+
158
+ //#endregion
159
+ //#region src/utils/request/race.ts
160
+ /**
161
+ * 竞速请求 返回最先成功响应的数据
162
+ * @param urls - 请求地址数组
163
+ * @param config - 请求配置 默认 { timeout: 10000, method: 'HEAD', successCodes: [200] }
164
+ * @returns 返回最先成功响应的数据
165
+ * @example
166
+ * const urls = ['https://api.github.com', 'https://api.gitee.com']
167
+ * const data = await raceRequest(urls)
168
+ * console.log(data)
169
+ *
170
+ * @example
171
+ * const urls = ['https://api.github.com/post', 'https://api.gitee.com/post']
172
+ * const data = await raceRequest(urls, {
173
+ * method: 'post',
174
+ * data: { foo: 'bar' },
175
+ * timeout: 10000,
176
+ * successCodes: [200, 201]
177
+ * })
178
+ * console.log(data)
179
+ */
180
+ const raceRequest = async (urls, config = {
181
+ method: "HEAD",
182
+ timeout: 2e3,
183
+ successCodes: [200]
184
+ }) => {
185
+ const successCodes = Array.isArray(config.successCodes) && config.successCodes.length > 0 ? config.successCodes : [200];
186
+ const requests = urls.map((url) => new Promise((resolve, reject) => {
187
+ axios.request({
188
+ ...config,
189
+ url,
190
+ timeout: config.timeout || 2e3
191
+ }).then((response) => {
192
+ if (successCodes.includes(response.status)) resolve(response);
193
+ else reject(/* @__PURE__ */ new Error(`响应状态码 ${response.status} 不在 successCodes 范围内`));
194
+ }).catch(reject);
195
+ }));
196
+ try {
197
+ return await Promise.any(requests);
198
+ } catch {
199
+ return null;
200
+ }
201
+ };
202
+ /**
203
+ * 测试网络请求
204
+ * @template D - 请求数据类型
205
+ * @template T - 是否返回详细信息
206
+ * @template R - 是否为竞速模式
207
+ * @param urls - 请求地址数组
208
+ * @param config - 扩展的请求配置,包含成功状态码列表和是否返回详细信息选项
209
+ * @returns 根据配置返回不同格式的结果
210
+ * @example
211
+ * const urls = ['https://api.github.com', 'https://api.gitee.com']
212
+ * const data = await pingRequest(urls)
213
+ * console.log(data)
214
+ * // -> ['https://api.github.com']
215
+ *
216
+ * @example
217
+ * const urls = ['https://api.github.com', 'https://api.gitee.com']
218
+ * const data = await pingRequest(urls, { detailed: true })
219
+ * console.log(data)
220
+ * // -> [{ url: 'https://api.github.com', success: true, duration: 100, error: null }]
221
+ *
222
+ * @example
223
+ * const urls = ['https://api.github.com', 'https://api.gitee.com']
224
+ * const data = await pingRequest(urls, { isRace: true })
225
+ * console.log(data)
226
+ * // -> 'https://api.github.com' 启用竞速模式,返回第一个成功的请求结果
227
+ */
228
+ const pingRequest = async (urls, config = {
229
+ timeout: 2e3,
230
+ successCodes: [200],
231
+ detailed: false,
232
+ isRace: false
233
+ }) => {
234
+ config.timeout = typeof config.timeout === "number" ? config.timeout : 2e3;
235
+ config.successCodes = Array.isArray(config.successCodes) && config.successCodes.length > 0 ? config.successCodes : [200];
236
+ config.detailed = typeof config.detailed === "boolean" ? config.detailed : false;
237
+ config.isRace = typeof config.isRace === "boolean" ? config.isRace : false;
238
+ /**
239
+ * 执行单个请求并返回请求结果
240
+ * @param url - 请求地址
241
+ * @returns 返回请求结果
242
+ */
243
+ const sendRequest = async (url) => {
244
+ const startTime = Date.now();
245
+ try {
246
+ const response = await axios.request({
247
+ ...config,
248
+ url
249
+ });
250
+ const duration = Date.now() - startTime;
251
+ if (!config.successCodes.includes(response.status)) throw new Error(`请求失败: ${response.status}`, { cause: response });
252
+ return {
253
+ url,
254
+ success: true,
255
+ duration,
256
+ error: null
257
+ };
258
+ } catch (error) {
259
+ return {
260
+ url,
261
+ success: false,
262
+ duration: Date.now() - startTime,
263
+ error
264
+ };
265
+ }
266
+ };
267
+ /** 竞速模式:第一个成功的请求立即返回 */
268
+ if (config.isRace) return await raceMode(urls, sendRequest, config.detailed);
269
+ return await standardMode(urls, sendRequest, config.detailed);
270
+ };
271
+ /**
272
+ * 竞速模式实现 - 第一个成功的请求立即返回
273
+ * @param urls - 请求地址数组
274
+ * @param sendRequest - 发送请求的函数
275
+ * @param detailed - 是否返回详细信息
276
+ * @returns 返回单个请求结果,成功返回第一个成功结果,全部失败返回null
277
+ */
278
+ const raceMode = async (urls, sendRequest, detailed) => {
279
+ return new Promise((resolve) => {
280
+ let pendingCount = urls.length;
281
+ urls.forEach(async (url) => {
282
+ const res = await sendRequest(url);
283
+ if (res.success) {
284
+ if (detailed) resolve(res);
285
+ else resolve(url);
286
+ return;
287
+ }
288
+ /** 记录完成的请求数 */
289
+ pendingCount--;
290
+ /** 如果所有请求都失败了 */
291
+ if (pendingCount === 0) resolve(null);
292
+ });
293
+ });
294
+ };
295
+ /**
296
+ * 标准模式实现 - 等待所有请求完成后返回
297
+ * @param urls - 请求地址数组
298
+ * @param sendRequest - 发送请求的函数
299
+ * @param detailed - 是否返回详细信息
300
+ * @returns 返回所有请求结果数组
301
+ */
302
+ const standardMode = async (urls, sendRequest, detailed) => {
303
+ /** 按照响应时间排序 */
304
+ const sortedResults = [...await Promise.all(urls.map(sendRequest))].sort((a, b) => a.duration - b.duration);
305
+ if (detailed) return sortedResults;
306
+ return sortedResults.filter((item) => item.success).map((item) => item.url);
307
+ };
308
+ /**
309
+ * 返回最快的npm registry
310
+ * @description 阿里云兜底
311
+ * @returns 返回最快的npm registry
312
+ */
313
+ const getFastRegistry = async () => {
314
+ const urls = [
315
+ "https://registry.npmmirror.com",
316
+ "https://registry.npmjs.com",
317
+ "https://mirrors.cloud.tencent.com/npm"
318
+ ];
319
+ try {
320
+ return (await raceRequest(urls))?.config.url || urls[0];
321
+ } catch (error) {
322
+ console.error("获取最快的npm registry失败:", error);
323
+ return urls[0];
324
+ }
325
+ };
326
+ /**
327
+ * 获取指定仓库的package.json
328
+ * @param owner - 仓库所属用户名
329
+ * @param repo - 仓库名
330
+ * @returns 返回指定仓库的package.json
331
+ */
332
+ const getPackageJson = async (owner, repo) => {
333
+ try {
334
+ return (await raceRequest([
335
+ `https://jsd.cdn.zzko.cn/gh/${owner}/${repo}/package.json`,
336
+ `https://jsd.onmicrosoft.cn/gh/${owner}/${repo}/package.json`,
337
+ `https://raw.github.com/${owner}/${repo}/HEAD/package.json`,
338
+ `https://gitproxy.click/https://raw.githubusercontent.com/${owner}/${repo}/HEAD/package.json`,
339
+ `https://gh.qninq.cn/https://raw.githubusercontent.com/${owner}/${repo}/HEAD/package.json`,
340
+ `https://github.starrlzy.cn/https://raw.githubusercontent.com/${owner}/${repo}/HEAD/package.json`,
341
+ `https://gh-proxy.ygxz.in/https://raw.githubusercontent.com/${owner}/${repo}/HEAD/package.json`
342
+ ]))?.data || { version: "0.0.0" };
343
+ } catch (error) {
344
+ console.error("获取package.json失败:", error);
345
+ return { version: "0.0.0" };
346
+ }
347
+ };
348
+
349
+ //#endregion
350
+ //#region src/utils/request/github.ts
351
+ /**
352
+ * 构建符合 https://github.akams.cn/ 标准的github加速源
353
+ * @param owner - 仓库所属用户名
354
+ * @param repo - 仓库名
355
+ * @returns 返回符合标准的github加速源
356
+ */
357
+ const buildGithub = (proxy) => {
358
+ return {
359
+ proxy,
360
+ isClone: true,
361
+ isRaw: true,
362
+ raw: function(url) {
363
+ return `${this.proxy}/${url}`;
364
+ },
365
+ clone: function(url) {
366
+ return `${this.proxy}/${url}`;
367
+ }
368
+ };
369
+ };
370
+ /**
371
+ * 解析github地址
372
+ * @param url - github地址
373
+ * @returns 返回解析后的地址
374
+ */
375
+ const parseGithubUrl = (url) => {
376
+ const urlObj = new URL(url);
377
+ return {
378
+ owner: urlObj.pathname.split("/")[1],
379
+ repo: urlObj.pathname.split("/")[2],
380
+ path: urlObj.pathname.split("/").slice(3).join("/")
381
+ };
382
+ };
383
+ /**
384
+ * Gihub加速 获取当前最快的源
385
+ * @param urls - 请求地址数组
386
+ * @param owner - 仓库所属用户名
387
+ * @param repo - 仓库名
388
+ * @returns 返回最快的源
389
+ */
390
+ const getFastGithub = async (type) => {
391
+ const list = [
392
+ {
393
+ proxy: "https://github.com",
394
+ isClone: true,
395
+ isRaw: true,
396
+ raw: function(url) {
397
+ return url;
398
+ },
399
+ clone: function(url) {
400
+ return url;
401
+ }
402
+ },
403
+ {
404
+ proxy: "https://jsd.cdn.zzko.cn/gh",
405
+ isClone: false,
406
+ isRaw: true,
407
+ raw: function(url) {
408
+ const { owner, repo, path } = parseGithubUrl(url);
409
+ return `${this.proxy}/${owner}/${repo}/${path}`;
410
+ },
411
+ clone: function(url) {
412
+ const { owner, repo } = parseGithubUrl(url);
413
+ return `${this.proxy}/${owner}/${repo}`;
414
+ }
415
+ },
416
+ {
417
+ proxy: "https://jsd.onmicrosoft.cn/gh",
418
+ isClone: false,
419
+ isRaw: true,
420
+ raw: function(url) {
421
+ const { owner, repo, path } = parseGithubUrl(url);
422
+ return `${this.proxy}/${owner}/${repo}/${path}`;
423
+ },
424
+ clone: function(url) {
425
+ const { owner, repo } = parseGithubUrl(url);
426
+ return `${this.proxy}/${owner}/${repo}`;
427
+ }
428
+ },
429
+ buildGithub("https://gitproxy.click"),
430
+ buildGithub("https://gh.qninq.cn"),
431
+ buildGithub("https://github.starrlzy.cn"),
432
+ buildGithub("https://gh-proxy.ygxz.in")
433
+ ];
434
+ /** 最终ping的配置列表 */
435
+ const urls = [];
436
+ list.forEach((item) => {
437
+ if (type === "raw" && item.isRaw) urls.push(item);
438
+ else if (type === "clone" && item.isClone) urls.push(item);
439
+ });
440
+ const rawUrl = "https://raw.githubusercontent.com/github/docs/refs/heads/main/README.md";
441
+ const gitCloneUrl = "https://github.com/github/docs.git";
442
+ /** 最终需要ping的url */
443
+ const pingUrls = [];
444
+ /** 测试url和github配置map */
445
+ const map = {};
446
+ urls.forEach((item) => {
447
+ const url = type === "raw" ? item.raw(rawUrl) : item.clone(gitCloneUrl);
448
+ pingUrls.push(url);
449
+ map[url] = item;
450
+ });
451
+ const result = await pingRequest(pingUrls, {
452
+ detailed: true,
453
+ isRace: true
454
+ });
455
+ if (!result) return list[0];
456
+ return map[result.url];
457
+ };
458
+
459
+ //#endregion
460
+ //#region src/utils/ini/index.ts
461
+ /**
462
+ * 创建并返回INI解析器对象
463
+ * @returns INI解析器对象
464
+ */
465
+ const createINIParser = () => {
466
+ /**
467
+ * 解析INI格式的字符串内容
468
+ * @param content - INI格式的文件内容
469
+ * @returns 解析后的键值对对象
470
+ */
471
+ const parseINIContent = (content) => {
472
+ const result = {};
473
+ const lines = content.split(/\r?\n/);
474
+ for (const line of lines) {
475
+ const trimmedLine = line.trim();
476
+ if (!trimmedLine || trimmedLine.startsWith("#") || trimmedLine.startsWith(";")) continue;
477
+ const separatorIndex = trimmedLine.indexOf("=");
478
+ if (separatorIndex !== -1) {
479
+ const key = trimmedLine.slice(0, separatorIndex).trim();
480
+ const value = trimmedLine.slice(separatorIndex + 1).trim();
481
+ if (key) result[key] = value;
482
+ }
483
+ }
484
+ return result;
485
+ };
486
+ /**
487
+ * 将键值对对象转换为INI格式的字符串
488
+ * @param data - 键值对数据
489
+ * @returns INI格式的字符串
490
+ */
491
+ const stringifyINI = (data) => {
492
+ return Object.entries(data).map(([key, value]) => `${key}=${value}`).join("\n");
493
+ };
494
+ return {
495
+ /**
496
+ * 从指定路径读取并解析INI文件
497
+ * @param filePath - 文件路径
498
+ * @returns 解析后的键值对对象
499
+ */
500
+ read: (filePath) => {
501
+ try {
502
+ const resolvedPath = path.resolve(filePath);
503
+ if (!fs$1.existsSync(resolvedPath)) return {};
504
+ return parseINIContent(fs$1.readFileSync(resolvedPath, "utf-8"));
505
+ } catch (error) {
506
+ console.error(`读取INI文件失败: ${error}`);
507
+ return {};
508
+ }
509
+ },
510
+ /**
511
+ * 将键值对对象保存到指定路径
512
+ * @param data - 要保存的键值对数据
513
+ * @param filePath - 保存的文件路径
514
+ * @returns 是否保存成功
515
+ */
516
+ write: (data, filePath) => {
517
+ try {
518
+ const resolvedPath = path.resolve(filePath);
519
+ const content = stringifyINI(data);
520
+ const dirname = path.dirname(resolvedPath);
521
+ if (!fs$1.existsSync(dirname)) fs$1.mkdirSync(dirname, { recursive: true });
522
+ fs$1.writeFileSync(resolvedPath, content, "utf-8");
523
+ return true;
524
+ } catch (error) {
525
+ console.error(`保存INI文件失败: ${error}`);
526
+ return false;
527
+ }
528
+ }
529
+ };
530
+ };
531
+ /**
532
+ * INI解析器
533
+ */
534
+ const ini = createINIParser();
535
+
536
+ //#endregion
537
+ //#region src/server/common/common.ts
538
+ /**
539
+ * 统一的token验证函数
540
+ * @param token 令牌
541
+ * @param userId 用户id
542
+ * @param res 响应
543
+ * @returns 是否验证成功
544
+ */
545
+ const verifyToken = async (token, userId, res) => {
546
+ if (!token) {
547
+ createUnauthorizedResponse(res, "鉴权失败: 缺少authorization");
548
+ return false;
549
+ }
550
+ if (!userId) {
551
+ /** 尝试明文密码验证 */
552
+ if (authKey() === token) return true;
553
+ createUnauthorizedResponse(res, "鉴权失败: token错误");
554
+ return false;
555
+ }
556
+ /** 验证jwt */
557
+ const verifyStatus = verifyJwt(token, userId);
558
+ /** JWT验证成功 */
559
+ if (verifyStatus.status === 200) return true;
560
+ /** JWT验证失败后尝试明文密码 */
561
+ if (authKey() === token) return true;
562
+ /** 处理各种错误情况 */
563
+ switch (verifyStatus.status) {
564
+ case 401:
565
+ createUnauthorizedResponse(res, verifyStatus.data);
566
+ return false;
567
+ case 419:
568
+ createAccessTokenExpiredResponse(res);
569
+ return false;
570
+ case 500:
571
+ createServerErrorResponse(res, verifyStatus.data);
572
+ return false;
573
+ default:
574
+ createServerErrorResponse(res, "服务器内部错误");
575
+ return false;
576
+ }
577
+ };
578
+ /**
579
+ * http鉴权
580
+ * @public
581
+ */
582
+ const auth = {
583
+ /**
584
+ * get请求鉴权
585
+ * @description 支持请求头中携带`Authorization`字段
586
+ * @description 支持请求参数中携带`token`字段
587
+ * @description 支持明文密码
588
+ */
589
+ getAuth: async (req, res) => {
590
+ const token = req?.headers?.authorization?.replace("Bearer ", "") || req?.query?.token;
591
+ const userId = req?.headers?.["x-user-id"];
592
+ return verifyToken(token, userId, res);
593
+ },
594
+ /**
595
+ * post请求鉴权
596
+ * @description 仅支持请求头中携带`Authorization`字段
597
+ * @description 除了支持jwt之外,还支持明文密码
598
+ */
599
+ postAuth: async (req, res) => {
600
+ const token = req?.headers?.authorization?.replace("Bearer ", "");
601
+ const userId = req?.headers?.["x-user-id"];
602
+ return verifyToken(token, userId, res);
603
+ },
604
+ /**
605
+ * 虚拟终端鉴权
606
+ * @param token 令牌
607
+ * @param userId 用户id
608
+ * @returns 是否验证成功
609
+ */
610
+ terminalAuth: (token, userId) => {
611
+ if (!token || !userId) return false;
612
+ if (verifyJwt(token, userId).status === 200) return true;
613
+ return false;
614
+ }
615
+ };
616
+
617
+ //#endregion
618
+ //#region src/server/pty/terminalManager.ts
619
+ var terminalManager_exports = /* @__PURE__ */ __exportAll({
620
+ closeTerminal: () => closeTerminal,
621
+ createTerminal: () => createTerminal,
622
+ getTerminalList: () => getTerminalList,
623
+ initialize: () => initialize,
624
+ isPtyInstalled: () => isPtyInstalled
625
+ });
626
+ /**
627
+ * 终端实例集合
628
+ */
629
+ const terminals = /* @__PURE__ */ new Map();
630
+ /**
631
+ * PTY 模块
632
+ */
633
+ let pty = null;
634
+ /**
635
+ * 检查 PTY 插件是否已安装
636
+ */
637
+ const isPtyInstalled = async () => {
638
+ try {
639
+ pty = await import("@karinjs/node-pty");
640
+ return true;
641
+ } catch (error) {
642
+ return false;
643
+ }
644
+ };
645
+ /**
646
+ * 初始化终端管理器
647
+ */
648
+ const initialize = async () => {
649
+ if (!await isPtyInstalled()) {
650
+ logger.debug("[terminal] PTY 模块未安装,终端功能不可用");
651
+ return;
652
+ }
653
+ listeners.on(WS_CONNECTION_TERMINAL, (socket, request, call) => {
654
+ try {
655
+ call();
656
+ const url = new URL(request.url || "", "ws://localhost");
657
+ const terminalId = url.searchParams.get("id");
658
+ const userId = url.searchParams.get("user_id");
659
+ const token = url.searchParams.get("token");
660
+ if (!token || !terminalId || !userId) {
661
+ socket.close();
662
+ logger.info(`[terminal] 非法请求: ${request.url}`);
663
+ return;
664
+ }
665
+ if (!auth.terminalAuth(token, userId)) {
666
+ socket.close();
667
+ logger.info(`[terminal] 鉴权失败: ${request.url}`);
668
+ return;
669
+ }
670
+ const instance = terminals.get(terminalId);
671
+ if (!instance) {
672
+ socket.close();
673
+ logger.info(`[terminal] 终端不存在: ${request.url}`);
674
+ return;
675
+ }
676
+ instance.sockets.add(socket);
677
+ instance.lastAccess = Date.now();
678
+ /** 发送当前终端内容给新连接 */
679
+ if (socket.readyState === WebSocket.OPEN) socket.send(JSON.stringify({
680
+ type: "output",
681
+ data: instance.buffer
682
+ }));
683
+ socket.on("message", (data) => {
684
+ if (instance && !instance.isClosing) {
685
+ const result = JSON.parse(data.toString());
686
+ if (result.type === "input") instance.pty.write(result.data);
687
+ if (result.type === "resize") instance.pty.resize(result.cols, result.rows);
688
+ }
689
+ });
690
+ socket.on("close", () => {
691
+ logger.info(`[terminal] 终端连接关闭: ${terminalId}`);
692
+ instance.sockets.delete(socket);
693
+ if (instance.sockets.size === 0 && !instance.isClosing) {
694
+ instance.isClosing = true;
695
+ if (os.platform() === "win32") process.kill(instance.pty.pid);
696
+ else instance.pty.kill();
697
+ }
698
+ });
699
+ } catch (error) {
700
+ logger.error(`[terminal] 初始化失败: ${request.url}`);
701
+ logger.error(error);
702
+ socket.close();
703
+ }
704
+ });
705
+ };
706
+ /**
707
+ * 创建终端
708
+ * @param shell 终端类型
709
+ * @param cols 列数
710
+ * @param rows 行数
711
+ * @returns 终端实例
712
+ */
713
+ const createTerminal = async (name = "xterm-256color", shell, cols = 80, rows = 30) => {
714
+ if (!pty) throw new Error("终端管理器未初始化或插件未安装");
715
+ const id = randomUUID();
716
+ const term = pty.spawn(shell, [], {
717
+ name: "xterm-256color",
718
+ cols,
719
+ rows,
720
+ cwd: process.cwd(),
721
+ env: {
722
+ ...process.env,
723
+ LANG: os.platform() === "win32" ? "chcp 65001" : "zh_CN.UTF-8",
724
+ TERM: "xterm-256color"
725
+ }
726
+ });
727
+ /** 终端实例 */
728
+ const instance = {
729
+ pty: term,
730
+ lastAccess: Date.now(),
731
+ sockets: /* @__PURE__ */ new Set(),
732
+ isClosing: false,
733
+ buffer: "",
734
+ name
735
+ };
736
+ term.onData((data) => {
737
+ instance.buffer += data;
738
+ instance.sockets.forEach((ws) => {
739
+ if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify({
740
+ type: "output",
741
+ data
742
+ }));
743
+ });
744
+ });
745
+ term.onExit(() => {
746
+ closeTerminal(id);
747
+ });
748
+ terminals.set(id, instance);
749
+ return {
750
+ id,
751
+ instance
752
+ };
753
+ };
754
+ /**
755
+ * 关闭终端
756
+ * @param id 终端ID
757
+ */
758
+ const closeTerminal = (id) => {
759
+ const instance = terminals.get(id);
760
+ if (instance) {
761
+ if (!instance.isClosing) {
762
+ instance.isClosing = true;
763
+ if (os.platform() === "win32") process.kill(instance.pty.pid);
764
+ else instance.pty.kill();
765
+ }
766
+ instance.sockets.forEach((ws) => ws.close());
767
+ terminals.delete(id);
768
+ }
769
+ };
770
+ /**
771
+ * 获取终端列表
772
+ * @returns 终端列表
773
+ */
774
+ const getTerminalList = () => {
775
+ return Array.from(terminals.keys()).map((id) => ({
776
+ id,
777
+ lastAccess: terminals.get(id).lastAccess,
778
+ name: terminals.get(id).name
779
+ }));
780
+ };
781
+
782
+ //#endregion
783
+ export { terminalManager_exports as a, ini as c, parseGithubUrl as d, getFastRegistry as f, logger$1 as g, raceRequest as h, initialize as i, buildGithub as l, pingRequest as m, createTerminal as n, auth as o, getPackageJson as p, getTerminalList as r, createINIParser as s, closeTerminal as t, getFastGithub as u };