@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 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
- try {
200
- // 优先使用现代的 Clipboard API
201
- if (navigator.clipboard && window.isSecureContext) {
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
- if (errorSection.length > maxLogs) {
28479
- return errorSection.slice(-maxLogs);
28480
- }
28481
- return errorSection;
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, _req, res, options) {
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 errorLogs = await readRecentErrorLogs(logDir, maxErrorLogs, logFileName);
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 send error page:", error);
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);