@lark-apaas/devtool-kits 1.0.4 → 1.0.5-alpha.2
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 +85 -10
- package/dist/index.cjs +99 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +99 -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,73 @@
|
|
|
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
|
|
163
|
+
const currentUrl = window.location.pathname + window.location.search;
|
|
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
|
+
}
|
|
128
201
|
}
|
|
129
202
|
|
|
130
203
|
// 复制错误信息到剪贴板
|
|
@@ -166,6 +239,7 @@
|
|
|
166
239
|
|
|
167
240
|
// 告诉妙搭修复
|
|
168
241
|
function handleRepair() {
|
|
242
|
+
console.log('[Render Error Repair] 告诉妙搭修复错误:', errorData);
|
|
169
243
|
sendPostMessage({
|
|
170
244
|
type: 'RenderErrorRepair',
|
|
171
245
|
data: errorData,
|
|
@@ -196,19 +270,20 @@
|
|
|
196
270
|
|
|
197
271
|
// 复制到剪贴板
|
|
198
272
|
async function copyToClipboard(text) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
273
|
+
// 优先使用现代的 Clipboard API
|
|
274
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
275
|
+
try {
|
|
202
276
|
await navigator.clipboard.writeText(text);
|
|
203
277
|
return true;
|
|
278
|
+
} catch (error) {
|
|
279
|
+
// 权限被拒绝或其他错误,降级到 execCommand
|
|
280
|
+
console.warn('Clipboard API 失败,降级到 execCommand:', error);
|
|
281
|
+
return fallbackCopyToClipboard(text);
|
|
204
282
|
}
|
|
205
|
-
|
|
206
|
-
// 降级方案:使用传统的 execCommand 方法
|
|
207
|
-
return fallbackCopyToClipboard(text);
|
|
208
|
-
} catch (error) {
|
|
209
|
-
console.error('复制到剪切板失败:', error);
|
|
210
|
-
return false;
|
|
211
283
|
}
|
|
284
|
+
|
|
285
|
+
// 降级方案:使用传统的 execCommand 方法
|
|
286
|
+
return fallbackCopyToClipboard(text);
|
|
212
287
|
}
|
|
213
288
|
|
|
214
289
|
// 降级复制方案(兼容旧浏览器)
|
|
@@ -261,7 +336,7 @@
|
|
|
261
336
|
class="error-image"
|
|
262
337
|
/>
|
|
263
338
|
<p class="title">哎呀,写错代码了</p>
|
|
264
|
-
<p class="description"
|
|
339
|
+
<p class="description">可复制错误信息,或告诉妙搭进行修复。</p>
|
|
265
340
|
<div class="button-group">
|
|
266
341
|
<button class="button button-copy" id="copyBtn" onclick="handleCopy()">
|
|
267
342
|
复制错误信息
|
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 } = 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,31 @@ 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 redirect");
|
|
28575
|
+
sendSimpleRedirect(req, res);
|
|
28576
|
+
return;
|
|
28577
|
+
}
|
|
28578
|
+
console.log("[Proxy Error]: Service did not recover within timeout, showing error page");
|
|
28579
|
+
}
|
|
28580
|
+
console.log("[Proxy Error]: Falling back to scenario 2 (detailed error page with probe)");
|
|
28581
|
+
}
|
|
28582
|
+
console.log("[Proxy Error]: Compile error or non-connection error, showing error page");
|
|
28505
28583
|
const template = getErrorHtmlTemplate();
|
|
28506
28584
|
const html = injectErrorData(template, errorLogs);
|
|
28507
28585
|
res.writeHead(502, {
|
|
@@ -28510,7 +28588,7 @@ function handleDevProxyError(err, _req, res, options) {
|
|
|
28510
28588
|
});
|
|
28511
28589
|
res.end(html);
|
|
28512
28590
|
} catch (error) {
|
|
28513
|
-
console.error("[Proxy Error]: Failed to
|
|
28591
|
+
console.error("[Proxy Error]: Failed to handle error:", error);
|
|
28514
28592
|
if (!res.headersSent) {
|
|
28515
28593
|
res.writeHead(502, {
|
|
28516
28594
|
"Content-Type": "text/plain; charset=utf-8"
|
|
@@ -28521,6 +28599,17 @@ function handleDevProxyError(err, _req, res, options) {
|
|
|
28521
28599
|
})();
|
|
28522
28600
|
}
|
|
28523
28601
|
__name(handleDevProxyError, "handleDevProxyError");
|
|
28602
|
+
function sendSimpleRedirect(req, res) {
|
|
28603
|
+
if (res.headersSent) return;
|
|
28604
|
+
const originalUrl = req.url || "/";
|
|
28605
|
+
console.log("[Proxy Error]: Sending 302 redirect to", originalUrl);
|
|
28606
|
+
res.writeHead(302, {
|
|
28607
|
+
"Location": originalUrl,
|
|
28608
|
+
"Cache-Control": "no-cache, no-store, must-revalidate"
|
|
28609
|
+
});
|
|
28610
|
+
res.end();
|
|
28611
|
+
}
|
|
28612
|
+
__name(sendSimpleRedirect, "sendSimpleRedirect");
|
|
28524
28613
|
|
|
28525
28614
|
// src/middlewares/index.ts
|
|
28526
28615
|
var import_node_path8 = __toESM(require("path"), 1);
|