@lark-apaas/devtool-kits 1.0.4 → 1.0.5-alpha.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/dist/error.html +94 -9
- package/dist/index.cjs +128 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +128 -10
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/error.html
CHANGED
|
@@ -111,6 +111,12 @@
|
|
|
111
111
|
let errorData = {
|
|
112
112
|
message: `{{.errorData.message}}`,
|
|
113
113
|
};
|
|
114
|
+
|
|
115
|
+
// 探针配置
|
|
116
|
+
const PROBE_INTERVAL = 2000; // 探测间隔 2 秒
|
|
117
|
+
const PROBE_TIMEOUT = 5000; // 单次请求超时 5 秒
|
|
118
|
+
let probeTimer = null;
|
|
119
|
+
|
|
114
120
|
// 初始化页面
|
|
115
121
|
function init() {
|
|
116
122
|
// 通知前端,渲染错误页面已准备就绪
|
|
@@ -125,6 +131,84 @@
|
|
|
125
131
|
data: errorData,
|
|
126
132
|
});
|
|
127
133
|
}
|
|
134
|
+
|
|
135
|
+
// 启动服务恢复探针
|
|
136
|
+
startServiceProbe();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 启动服务恢复探针
|
|
140
|
+
function startServiceProbe() {
|
|
141
|
+
console.log('[Probe] 启动服务恢复探针,间隔:', PROBE_INTERVAL, 'ms');
|
|
142
|
+
|
|
143
|
+
// 立即执行一次探测
|
|
144
|
+
probeService();
|
|
145
|
+
|
|
146
|
+
// 设置定时探测
|
|
147
|
+
probeTimer = setInterval(probeService, PROBE_INTERVAL);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 停止探针
|
|
151
|
+
function stopServiceProbe() {
|
|
152
|
+
if (probeTimer) {
|
|
153
|
+
clearInterval(probeTimer);
|
|
154
|
+
probeTimer = null;
|
|
155
|
+
console.log('[Probe] 服务恢复探针已停止');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// 探测服务是否恢复
|
|
160
|
+
async function probeService() {
|
|
161
|
+
try {
|
|
162
|
+
// 获取当前 URL,去掉 _retry_count 参数
|
|
163
|
+
const currentUrl = getCurrentUrlWithoutRetryCount();
|
|
164
|
+
|
|
165
|
+
console.log('[Probe] 探测服务状态:', currentUrl);
|
|
166
|
+
|
|
167
|
+
// 使用 AbortController 实现超时控制
|
|
168
|
+
const controller = new AbortController();
|
|
169
|
+
const timeoutId = setTimeout(() => controller.abort(), PROBE_TIMEOUT);
|
|
170
|
+
|
|
171
|
+
const response = await fetch(currentUrl, {
|
|
172
|
+
method: 'HEAD', // 使用 HEAD 方法,只检查状态不下载内容
|
|
173
|
+
cache: 'no-cache',
|
|
174
|
+
signal: controller.signal
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
clearTimeout(timeoutId);
|
|
178
|
+
|
|
179
|
+
// 检查响应状态
|
|
180
|
+
if (response.ok && response.status === 200) {
|
|
181
|
+
console.log('[Probe] 服务已恢复,准备刷新页面');
|
|
182
|
+
stopServiceProbe();
|
|
183
|
+
|
|
184
|
+
// 延迟一小段时间后刷新,让日志输出完整
|
|
185
|
+
setTimeout(() => {
|
|
186
|
+
console.log('[Probe] 刷新页面...');
|
|
187
|
+
window.location.href = currentUrl;
|
|
188
|
+
}, 300);
|
|
189
|
+
} else {
|
|
190
|
+
console.log('[Probe] 服务未恢复,状态码:', response.status);
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
// 网络错误、超时或服务未恢复
|
|
194
|
+
if (error.name === 'AbortError') {
|
|
195
|
+
console.log('[Probe] 探测超时');
|
|
196
|
+
} else {
|
|
197
|
+
console.log('[Probe] 探测失败:', error.message);
|
|
198
|
+
}
|
|
199
|
+
// 继续下一次探测
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 获取当前 URL(去掉 _retry_count 参数)
|
|
204
|
+
function getCurrentUrlWithoutRetryCount() {
|
|
205
|
+
try {
|
|
206
|
+
const url = new URL(window.location.href);
|
|
207
|
+
url.searchParams.delete('_retry_count');
|
|
208
|
+
return url.pathname + url.search;
|
|
209
|
+
} catch (e) {
|
|
210
|
+
return window.location.pathname;
|
|
211
|
+
}
|
|
128
212
|
}
|
|
129
213
|
|
|
130
214
|
// 复制错误信息到剪贴板
|
|
@@ -196,19 +280,20 @@
|
|
|
196
280
|
|
|
197
281
|
// 复制到剪贴板
|
|
198
282
|
async function copyToClipboard(text) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
283
|
+
// 优先使用现代的 Clipboard API
|
|
284
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
285
|
+
try {
|
|
202
286
|
await navigator.clipboard.writeText(text);
|
|
203
287
|
return true;
|
|
288
|
+
} catch (error) {
|
|
289
|
+
// 权限被拒绝或其他错误,降级到 execCommand
|
|
290
|
+
console.warn('Clipboard API 失败,降级到 execCommand:', error);
|
|
291
|
+
return fallbackCopyToClipboard(text);
|
|
204
292
|
}
|
|
205
|
-
|
|
206
|
-
// 降级方案:使用传统的 execCommand 方法
|
|
207
|
-
return fallbackCopyToClipboard(text);
|
|
208
|
-
} catch (error) {
|
|
209
|
-
console.error('复制到剪切板失败:', error);
|
|
210
|
-
return false;
|
|
211
293
|
}
|
|
294
|
+
|
|
295
|
+
// 降级方案:使用传统的 execCommand 方法
|
|
296
|
+
return fallbackCopyToClipboard(text);
|
|
212
297
|
}
|
|
213
298
|
|
|
214
299
|
// 降级复制方案(兼容旧浏览器)
|
package/dist/index.cjs
CHANGED
|
@@ -28407,7 +28407,52 @@ var import_node_fs2 = __toESM(require("fs"), 1);
|
|
|
28407
28407
|
var import_node_path2 = __toESM(require("path"), 1);
|
|
28408
28408
|
var import_node_fs3 = require("fs");
|
|
28409
28409
|
var import_node_readline = require("readline");
|
|
28410
|
+
var import_node_net = require("net");
|
|
28410
28411
|
var errorHtmlTemplate = null;
|
|
28412
|
+
function isConnectionError(err) {
|
|
28413
|
+
const code = err.code;
|
|
28414
|
+
const connectionErrorCodes = [
|
|
28415
|
+
"ECONNREFUSED",
|
|
28416
|
+
"ECONNRESET",
|
|
28417
|
+
"ETIMEDOUT",
|
|
28418
|
+
"ENOTFOUND",
|
|
28419
|
+
"ENETUNREACH"
|
|
28420
|
+
];
|
|
28421
|
+
return connectionErrorCodes.includes(code || "");
|
|
28422
|
+
}
|
|
28423
|
+
__name(isConnectionError, "isConnectionError");
|
|
28424
|
+
function checkServiceAvailable(host, port, timeout = 1e3) {
|
|
28425
|
+
return new Promise((resolve) => {
|
|
28426
|
+
const socket = (0, import_node_net.connect)(port, host);
|
|
28427
|
+
const timer = setTimeout(() => {
|
|
28428
|
+
socket.destroy();
|
|
28429
|
+
resolve(false);
|
|
28430
|
+
}, timeout);
|
|
28431
|
+
socket.on("connect", () => {
|
|
28432
|
+
clearTimeout(timer);
|
|
28433
|
+
socket.destroy();
|
|
28434
|
+
resolve(true);
|
|
28435
|
+
});
|
|
28436
|
+
socket.on("error", () => {
|
|
28437
|
+
clearTimeout(timer);
|
|
28438
|
+
socket.destroy();
|
|
28439
|
+
resolve(false);
|
|
28440
|
+
});
|
|
28441
|
+
});
|
|
28442
|
+
}
|
|
28443
|
+
__name(checkServiceAvailable, "checkServiceAvailable");
|
|
28444
|
+
async function waitForServiceRecovery(host, port, timeout, interval) {
|
|
28445
|
+
const startTime = Date.now();
|
|
28446
|
+
while (Date.now() - startTime < timeout) {
|
|
28447
|
+
const isAvailable = await checkServiceAvailable(host, port);
|
|
28448
|
+
if (isAvailable) {
|
|
28449
|
+
return true;
|
|
28450
|
+
}
|
|
28451
|
+
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
28452
|
+
}
|
|
28453
|
+
return false;
|
|
28454
|
+
}
|
|
28455
|
+
__name(waitForServiceRecovery, "waitForServiceRecovery");
|
|
28411
28456
|
function getDirname() {
|
|
28412
28457
|
return __dirname;
|
|
28413
28458
|
}
|
|
@@ -28436,7 +28481,10 @@ async function readRecentErrorLogs(logDir, maxLogs, fileName) {
|
|
|
28436
28481
|
try {
|
|
28437
28482
|
await import_node_fs2.default.promises.access(logFilePath);
|
|
28438
28483
|
} catch {
|
|
28439
|
-
return
|
|
28484
|
+
return {
|
|
28485
|
+
logs: [],
|
|
28486
|
+
hasCompileError: false
|
|
28487
|
+
};
|
|
28440
28488
|
}
|
|
28441
28489
|
const allLines = [];
|
|
28442
28490
|
const stream = (0, import_node_fs3.createReadStream)(logFilePath, {
|
|
@@ -28465,20 +28513,26 @@ async function readRecentErrorLogs(logDir, maxLogs, fileName) {
|
|
|
28465
28513
|
}
|
|
28466
28514
|
}
|
|
28467
28515
|
if (startIndex === -1) {
|
|
28468
|
-
return
|
|
28516
|
+
return {
|
|
28517
|
+
logs: [],
|
|
28518
|
+
hasCompileError: false
|
|
28519
|
+
};
|
|
28469
28520
|
}
|
|
28470
28521
|
let endIndex = allLines.length;
|
|
28522
|
+
let hasCompileError = false;
|
|
28471
28523
|
for (let i = startIndex; i < allLines.length; i++) {
|
|
28472
28524
|
if (/Found \d+ errors?\./.test(allLines[i])) {
|
|
28473
28525
|
endIndex = i + 1;
|
|
28526
|
+
hasCompileError = true;
|
|
28474
28527
|
break;
|
|
28475
28528
|
}
|
|
28476
28529
|
}
|
|
28477
28530
|
const errorSection = allLines.slice(startIndex, endIndex);
|
|
28478
|
-
|
|
28479
|
-
|
|
28480
|
-
|
|
28481
|
-
|
|
28531
|
+
const logs = errorSection.length > maxLogs ? errorSection.slice(-maxLogs) : errorSection;
|
|
28532
|
+
return {
|
|
28533
|
+
logs,
|
|
28534
|
+
hasCompileError
|
|
28535
|
+
};
|
|
28482
28536
|
}
|
|
28483
28537
|
__name(readRecentErrorLogs, "readRecentErrorLogs");
|
|
28484
28538
|
function injectErrorData(template, errorLogs) {
|
|
@@ -28492,8 +28546,8 @@ function injectErrorData(template, errorLogs) {
|
|
|
28492
28546
|
${JSON.stringify(logsText)}`);
|
|
28493
28547
|
}
|
|
28494
28548
|
__name(injectErrorData, "injectErrorData");
|
|
28495
|
-
function handleDevProxyError(err,
|
|
28496
|
-
const { logDir = import_node_path2.default.join(process.cwd(), "logs"), maxErrorLogs = 100, logFileName = "server.log" } = options || {};
|
|
28549
|
+
function handleDevProxyError(err, req, res, options) {
|
|
28550
|
+
const { logDir = import_node_path2.default.join(process.cwd(), "logs"), maxErrorLogs = 100, logFileName = "server.log", retryTimeout = 5e3, retryInterval = 500, target, maxRedirects = 3 } = options || {};
|
|
28497
28551
|
console.error("[Proxy Error]:", err.message);
|
|
28498
28552
|
if (res.headersSent) {
|
|
28499
28553
|
console.error("[Proxy Error]: Headers already sent, cannot send error page");
|
|
@@ -28501,7 +28555,32 @@ function handleDevProxyError(err, _req, res, options) {
|
|
|
28501
28555
|
}
|
|
28502
28556
|
(async () => {
|
|
28503
28557
|
try {
|
|
28504
|
-
const
|
|
28558
|
+
const isConnError = isConnectionError(err);
|
|
28559
|
+
const { logs: errorLogs, hasCompileError } = await readRecentErrorLogs(logDir, maxErrorLogs, logFileName);
|
|
28560
|
+
if (isConnError && !hasCompileError && target) {
|
|
28561
|
+
console.log("[Proxy Error]: Connection error without compile errors, possibly server restarting...");
|
|
28562
|
+
let targetUrl = null;
|
|
28563
|
+
try {
|
|
28564
|
+
targetUrl = new URL(target);
|
|
28565
|
+
} catch (e) {
|
|
28566
|
+
console.error("[Proxy Error]: Invalid target URL:", target);
|
|
28567
|
+
}
|
|
28568
|
+
if (targetUrl) {
|
|
28569
|
+
const host = targetUrl.hostname;
|
|
28570
|
+
const port = targetUrl.port ? parseInt(targetUrl.port) : targetUrl.protocol === "https:" ? 443 : 80;
|
|
28571
|
+
console.log(`[Proxy Error]: Waiting for service recovery at ${host}:${port} (timeout: ${retryTimeout}ms)...`);
|
|
28572
|
+
const recovered = await waitForServiceRecovery(host, port, retryTimeout, retryInterval);
|
|
28573
|
+
if (recovered) {
|
|
28574
|
+
console.log("[Proxy Error]: Service recovered, sending retry response");
|
|
28575
|
+
} else {
|
|
28576
|
+
console.log("[Proxy Error]: Service did not recover within timeout, sending retry response");
|
|
28577
|
+
}
|
|
28578
|
+
}
|
|
28579
|
+
const redirected = sendSimpleRetryResponse(req, res, maxRedirects);
|
|
28580
|
+
if (redirected) return;
|
|
28581
|
+
console.log("[Proxy Error]: Falling back to scenario 2 (detailed error page)");
|
|
28582
|
+
}
|
|
28583
|
+
console.log("[Proxy Error]: Compile error or non-connection error, showing error page");
|
|
28505
28584
|
const template = getErrorHtmlTemplate();
|
|
28506
28585
|
const html = injectErrorData(template, errorLogs);
|
|
28507
28586
|
res.writeHead(502, {
|
|
@@ -28510,7 +28589,7 @@ function handleDevProxyError(err, _req, res, options) {
|
|
|
28510
28589
|
});
|
|
28511
28590
|
res.end(html);
|
|
28512
28591
|
} catch (error) {
|
|
28513
|
-
console.error("[Proxy Error]: Failed to
|
|
28592
|
+
console.error("[Proxy Error]: Failed to handle error:", error);
|
|
28514
28593
|
if (!res.headersSent) {
|
|
28515
28594
|
res.writeHead(502, {
|
|
28516
28595
|
"Content-Type": "text/plain; charset=utf-8"
|
|
@@ -28521,6 +28600,45 @@ function handleDevProxyError(err, _req, res, options) {
|
|
|
28521
28600
|
})();
|
|
28522
28601
|
}
|
|
28523
28602
|
__name(handleDevProxyError, "handleDevProxyError");
|
|
28603
|
+
function getRedirectCount(url) {
|
|
28604
|
+
try {
|
|
28605
|
+
const urlObj = new URL(url, "http://localhost");
|
|
28606
|
+
const retryCount = urlObj.searchParams.get("_retry_count");
|
|
28607
|
+
return retryCount ? parseInt(retryCount, 10) : 0;
|
|
28608
|
+
} catch {
|
|
28609
|
+
return 0;
|
|
28610
|
+
}
|
|
28611
|
+
}
|
|
28612
|
+
__name(getRedirectCount, "getRedirectCount");
|
|
28613
|
+
function addRedirectCount(url, count) {
|
|
28614
|
+
try {
|
|
28615
|
+
const urlObj = new URL(url, "http://localhost");
|
|
28616
|
+
urlObj.searchParams.set("_retry_count", String(count));
|
|
28617
|
+
return urlObj.pathname + urlObj.search;
|
|
28618
|
+
} catch {
|
|
28619
|
+
return url;
|
|
28620
|
+
}
|
|
28621
|
+
}
|
|
28622
|
+
__name(addRedirectCount, "addRedirectCount");
|
|
28623
|
+
function sendSimpleRetryResponse(req, res, maxRedirects) {
|
|
28624
|
+
if (res.headersSent) return true;
|
|
28625
|
+
const originalUrl = req.url || "/";
|
|
28626
|
+
const currentCount = getRedirectCount(originalUrl);
|
|
28627
|
+
if (currentCount >= maxRedirects) {
|
|
28628
|
+
console.log(`[Proxy Error]: Max redirects (${maxRedirects}) reached, will show detailed error page`);
|
|
28629
|
+
return false;
|
|
28630
|
+
}
|
|
28631
|
+
const nextCount = currentCount + 1;
|
|
28632
|
+
const redirectUrl = addRedirectCount(originalUrl, nextCount);
|
|
28633
|
+
console.log(`[Proxy Error]: Redirecting (attempt ${nextCount}/${maxRedirects}) to ${redirectUrl}`);
|
|
28634
|
+
res.writeHead(302, {
|
|
28635
|
+
"Location": redirectUrl,
|
|
28636
|
+
"Cache-Control": "no-cache, no-store, must-revalidate"
|
|
28637
|
+
});
|
|
28638
|
+
res.end();
|
|
28639
|
+
return true;
|
|
28640
|
+
}
|
|
28641
|
+
__name(sendSimpleRetryResponse, "sendSimpleRetryResponse");
|
|
28524
28642
|
|
|
28525
28643
|
// src/middlewares/index.ts
|
|
28526
28644
|
var import_node_path8 = __toESM(require("path"), 1);
|