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.
- package/CHANGELOG.md +31 -0
- package/dist/adapter-BqlH3u3X.mjs +218 -0
- package/dist/app-DdMQbBEY.mjs +4109 -0
- package/dist/cache-CPcPeo6N.mjs +163 -0
- package/dist/chunk-NzVPYdc1.mjs +21 -0
- package/dist/cli/index.cjs +10900 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.mjs +10770 -10224
- package/dist/file-ZGuqNDd-.mjs +15987 -0
- package/dist/file-dGy9of8-.mjs +268 -0
- package/dist/fsSync-Cf5MWILk.mjs +65 -0
- package/dist/index.d.ts +12235 -12738
- package/dist/index.mjs +2054 -25247
- package/dist/internal-DupfycKE.mjs +597 -0
- package/dist/kv-DZp4UIxg.mjs +192 -0
- package/dist/module/art-template.d.ts +2 -13
- package/dist/module/art-template.mjs +3 -1
- package/dist/module/axios.d.ts +3 -2
- package/dist/module/axios.mjs +5 -2
- package/dist/module/chalk.d.ts +3 -2
- package/dist/module/chalk.mjs +5 -2
- package/dist/module/chokidar.d.ts +3 -2
- package/dist/module/chokidar.mjs +5 -2
- package/dist/module/express.d.ts +2 -1
- package/dist/module/express.mjs +3 -1
- package/dist/module/lodash.d.ts +2 -1
- package/dist/module/lodash.mjs +3 -1
- package/dist/module/log4js.d.ts +3 -2
- package/dist/module/log4js.mjs +5 -2
- package/dist/module/moment.d.ts +2 -1
- package/dist/module/moment.mjs +3 -1
- package/dist/module/node-schedule.d.ts +3 -2
- package/dist/module/node-schedule.mjs +5 -2
- package/dist/module/redis.d.ts +3 -2
- package/dist/module/redis.mjs +5 -2
- package/dist/module/sqlite3.d.ts +3 -2
- package/dist/module/sqlite3.mjs +5 -2
- package/dist/module/ws.d.ts +3 -2
- package/dist/module/ws.mjs +5 -2
- package/dist/module/yaml.d.ts +3 -2
- package/dist/module/yaml.mjs +5 -2
- package/dist/queue-CnKedaZA.mjs +70 -0
- package/dist/redis-aLJ7wbJH.mjs +1556 -0
- package/dist/render-DPqueDZr.mjs +170 -0
- package/dist/root.d.ts +46 -46
- package/dist/root.mjs +136 -93
- package/dist/router-zPSN9-tY.mjs +124 -0
- package/dist/server-DT64D-m-.mjs +38 -0
- package/dist/snapka-BTlnZOyI.mjs +450 -0
- package/dist/sqlite-Dcj9jlW9.mjs +307 -0
- package/dist/start/app.d.ts +1 -1
- package/dist/start/app.mjs +14 -7
- package/dist/start/index.d.ts +1 -1
- package/dist/start/index.mjs +325 -656
- package/dist/template-Djk6y0uC.mjs +133 -0
- package/dist/terminalManager-Lxa8Sm06.mjs +783 -0
- package/dist/uptime-C121X_rq.mjs +210 -0
- package/dist/web/{CompressaPRO-GX.woff2.br → CompressaPRO-GX.woff2} +0 -0
- package/dist/web/assets/css/style-CBB8wM_W.css +14880 -0
- package/dist/web/assets/js/entry-Blf4Trpx.js +258540 -0
- package/dist/web/{googleapis.woff2.br → googleapis.woff2} +0 -0
- package/dist/web/index.html +2 -15
- package/dist/web/karin.png +0 -0
- package/dist/web/sha256.min.js +9 -0
- package/dist/ws-BLDoC2gV.mjs +80 -0
- package/dist/ws-CcoWd3Ar.mjs +106 -0
- package/package.json +7 -7
- package/dist/global.d.d.ts +0 -68
- package/dist/types-hAhbXJDZ.d.ts +0 -109
- package/dist/web/assets/css/components-ep7vm38G.css +0 -1
- package/dist/web/assets/css/index-Dadvd9mn.css.br +0 -0
- package/dist/web/assets/css/vendor-editor-CFbL2ovg.css.br +0 -0
- package/dist/web/assets/css/vendor-others-ZgkIHsf0.css +0 -1
- package/dist/web/assets/js/components-CU2xw4lY.js.br +0 -0
- package/dist/web/assets/js/entry-Dvb7eYLE.js.br +0 -0
- package/dist/web/assets/js/hooks-CRfhs4ON.js.br +0 -0
- package/dist/web/assets/js/page-404.tsx-DYMd_RI_.js +0 -1
- package/dist/web/assets/js/page-dashboard-CG60V_Z-.js.br +0 -0
- package/dist/web/assets/js/page-loading.tsx-wY8a9me3.js.br +0 -0
- package/dist/web/assets/js/page-login.tsx-B54ZOEZB.js.br +0 -0
- package/dist/web/assets/js/utils-C9nWTSuo.js +0 -2
- package/dist/web/assets/js/vendor-editor-BmqYP7lh.js.br +0 -0
- package/dist/web/assets/js/vendor-heroui-ClBCy2zk.js.br +0 -0
- package/dist/web/assets/js/vendor-others-6GiMrjd4.js.br +0 -0
- package/dist/web/assets/js/vendor-react-Dc9jdQiK.js.br +0 -0
- package/dist/web/assets/js/vendor-ui-utils-D0xkboLL.js.br +0 -0
- package/dist/web/assets/js/vendor-visual-saF8KLH_.js.br +0 -0
- package/dist/web/karin.png.br +0 -0
- 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 };
|