@lark-apaas/devtool-kits 1.2.10-alpha.0 → 1.2.11-alpha.0
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/index.cjs +534 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +549 -31
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1850,8 +1850,8 @@ function hasSpecialPatterns(pattern) {
|
|
|
1850
1850
|
return /[{*]/.test(pattern);
|
|
1851
1851
|
}
|
|
1852
1852
|
__name(hasSpecialPatterns, "hasSpecialPatterns");
|
|
1853
|
-
function normalizePathForMatching(
|
|
1854
|
-
return
|
|
1853
|
+
function normalizePathForMatching(path8) {
|
|
1854
|
+
return path8.replace(/\/+/g, "/").replace(/\/+$/, "");
|
|
1855
1855
|
}
|
|
1856
1856
|
__name(normalizePathForMatching, "normalizePathForMatching");
|
|
1857
1857
|
|
|
@@ -2204,20 +2204,20 @@ async function readServerLogs(logDir, options = {}) {
|
|
|
2204
2204
|
}
|
|
2205
2205
|
__name(readServerLogs, "readServerLogs");
|
|
2206
2206
|
async function readLogsBySource(logDir, source) {
|
|
2207
|
-
const { join:
|
|
2207
|
+
const { join: join7 } = await import("path");
|
|
2208
2208
|
let filePath;
|
|
2209
2209
|
let parser;
|
|
2210
2210
|
if (source === "server") {
|
|
2211
|
-
filePath =
|
|
2211
|
+
filePath = join7(logDir, "server.log");
|
|
2212
2212
|
parser = /* @__PURE__ */ __name((line) => parsePinoLog(line, "server"), "parser");
|
|
2213
2213
|
} else if (source === "trace") {
|
|
2214
|
-
filePath =
|
|
2214
|
+
filePath = join7(logDir, "trace.log");
|
|
2215
2215
|
parser = /* @__PURE__ */ __name((line) => parsePinoLog(line, "trace"), "parser");
|
|
2216
2216
|
} else if (source === "server-std") {
|
|
2217
|
-
filePath =
|
|
2217
|
+
filePath = join7(logDir, "server.std.log");
|
|
2218
2218
|
parser = /* @__PURE__ */ __name((line) => parseStdLog(line, "server-std"), "parser");
|
|
2219
2219
|
} else if (source === "client-std") {
|
|
2220
|
-
filePath =
|
|
2220
|
+
filePath = join7(logDir, "client.std.log");
|
|
2221
2221
|
parser = /* @__PURE__ */ __name((line) => parseStdLog(line, "client-std"), "parser");
|
|
2222
2222
|
} else {
|
|
2223
2223
|
console.warn(`[readLogsBySource] Unknown source: ${source}`);
|
|
@@ -2385,7 +2385,7 @@ function generateUUID() {
|
|
|
2385
2385
|
});
|
|
2386
2386
|
}
|
|
2387
2387
|
__name(generateUUID, "generateUUID");
|
|
2388
|
-
async function readTriggerList(filePath, trigger,
|
|
2388
|
+
async function readTriggerList(filePath, trigger, path8, limit, triggerID) {
|
|
2389
2389
|
if (!await fileExists(filePath)) {
|
|
2390
2390
|
return void 0;
|
|
2391
2391
|
}
|
|
@@ -2411,7 +2411,7 @@ async function readTriggerList(filePath, trigger, path7, limit, triggerID) {
|
|
|
2411
2411
|
if (alreadyAdded) {
|
|
2412
2412
|
return false;
|
|
2413
2413
|
}
|
|
2414
|
-
const isAutomationTrigger = builder.path?.endsWith(
|
|
2414
|
+
const isAutomationTrigger = builder.path?.endsWith(path8);
|
|
2415
2415
|
if (!isAutomationTrigger) {
|
|
2416
2416
|
return false;
|
|
2417
2417
|
}
|
|
@@ -2494,7 +2494,7 @@ async function readTriggerList(filePath, trigger, path7, limit, triggerID) {
|
|
|
2494
2494
|
};
|
|
2495
2495
|
}
|
|
2496
2496
|
__name(readTriggerList, "readTriggerList");
|
|
2497
|
-
async function readTriggerDetail(filePath,
|
|
2497
|
+
async function readTriggerDetail(filePath, path8, instanceID) {
|
|
2498
2498
|
const exists = await fileExists(filePath);
|
|
2499
2499
|
if (!exists) {
|
|
2500
2500
|
return void 0;
|
|
@@ -2510,7 +2510,7 @@ async function readTriggerDetail(filePath, path7, instanceID) {
|
|
|
2510
2510
|
for await (const line of rl) {
|
|
2511
2511
|
const entry = parseLogLine2(line);
|
|
2512
2512
|
if (!entry) continue;
|
|
2513
|
-
const isAutomationTrigger = entry.path?.endsWith(
|
|
2513
|
+
const isAutomationTrigger = entry.path?.endsWith(path8);
|
|
2514
2514
|
const hasInstanceID = entry.instance_id === instanceID && entry.trigger;
|
|
2515
2515
|
if (!isAutomationTrigger || !hasInstanceID) continue;
|
|
2516
2516
|
matches.push(entry);
|
|
@@ -2652,16 +2652,16 @@ function createGetTriggerListHandler(logDir) {
|
|
|
2652
2652
|
});
|
|
2653
2653
|
}
|
|
2654
2654
|
const triggerID = typeof req.query.triggerID === "string" ? req.query.triggerID.trim() : void 0;
|
|
2655
|
-
const
|
|
2655
|
+
const path8 = typeof req.query.path === "string" ? req.query.path.trim() : "/__innerapi__/automation/invoke";
|
|
2656
2656
|
const limit = parseLimit(req.query.limit, 10, 200);
|
|
2657
2657
|
try {
|
|
2658
|
-
const result = await readTriggerList(traceLogPath, trigger,
|
|
2658
|
+
const result = await readTriggerList(traceLogPath, trigger, path8, limit, triggerID);
|
|
2659
2659
|
if (!result) {
|
|
2660
2660
|
return handleNotFound(res, traceLogPath);
|
|
2661
2661
|
}
|
|
2662
2662
|
res.json({
|
|
2663
2663
|
file: getRelativePath(traceLogPath),
|
|
2664
|
-
path:
|
|
2664
|
+
path: path8,
|
|
2665
2665
|
...result
|
|
2666
2666
|
});
|
|
2667
2667
|
} catch (error) {
|
|
@@ -2679,9 +2679,9 @@ function createGetTriggerDetailHandler(logDir) {
|
|
|
2679
2679
|
message: "instanceID is required"
|
|
2680
2680
|
});
|
|
2681
2681
|
}
|
|
2682
|
-
const
|
|
2682
|
+
const path8 = typeof req.query.path === "string" ? req.query.path.trim() : "/__innerapi__/automation/invoke";
|
|
2683
2683
|
try {
|
|
2684
|
-
const result = await readTriggerDetail(traceLogPath,
|
|
2684
|
+
const result = await readTriggerDetail(traceLogPath, path8, instanceID);
|
|
2685
2685
|
if (!result) {
|
|
2686
2686
|
return handleNotFound(res, traceLogPath);
|
|
2687
2687
|
}
|
|
@@ -2763,6 +2763,521 @@ function createHealthCheckHandler(options = {}) {
|
|
|
2763
2763
|
}
|
|
2764
2764
|
__name(createHealthCheckHandler, "createHealthCheckHandler");
|
|
2765
2765
|
|
|
2766
|
+
// src/middlewares/dev-logs/sse/log-watcher.ts
|
|
2767
|
+
var fs9 = __toESM(require("fs"), 1);
|
|
2768
|
+
var path6 = __toESM(require("path"), 1);
|
|
2769
|
+
function mapPinoLevelToServerLogLevel2(pinoLevel) {
|
|
2770
|
+
if (typeof pinoLevel === "string") {
|
|
2771
|
+
const lower = pinoLevel.toLowerCase();
|
|
2772
|
+
if (lower === "fatal") return "fatal";
|
|
2773
|
+
if (lower === "error") return "error";
|
|
2774
|
+
if (lower === "warn" || lower === "warning") return "warn";
|
|
2775
|
+
if (lower === "info" || lower === "log") return "log";
|
|
2776
|
+
if (lower === "debug") return "debug";
|
|
2777
|
+
if (lower === "trace" || lower === "verbose") return "verbose";
|
|
2778
|
+
return "log";
|
|
2779
|
+
}
|
|
2780
|
+
if (pinoLevel >= 60) return "fatal";
|
|
2781
|
+
if (pinoLevel >= 50) return "error";
|
|
2782
|
+
if (pinoLevel >= 40) return "warn";
|
|
2783
|
+
if (pinoLevel >= 30) return "log";
|
|
2784
|
+
if (pinoLevel >= 20) return "debug";
|
|
2785
|
+
return "verbose";
|
|
2786
|
+
}
|
|
2787
|
+
__name(mapPinoLevelToServerLogLevel2, "mapPinoLevelToServerLogLevel");
|
|
2788
|
+
function extractLogLevel2(text) {
|
|
2789
|
+
const lower = text.toLowerCase();
|
|
2790
|
+
if (lower.includes("fatal") || lower.includes("critical")) return "fatal";
|
|
2791
|
+
if (lower.includes("error") || lower.includes("<e>") || lower.includes("\u2716")) return "error";
|
|
2792
|
+
if (lower.includes("warn") || lower.includes("warning") || lower.includes("<w>") || lower.includes("\u26A0")) return "warn";
|
|
2793
|
+
if (lower.includes("debug") || lower.includes("<d>")) return "debug";
|
|
2794
|
+
if (lower.includes("verbose") || lower.includes("trace")) return "verbose";
|
|
2795
|
+
return "log";
|
|
2796
|
+
}
|
|
2797
|
+
__name(extractLogLevel2, "extractLogLevel");
|
|
2798
|
+
function generateUUID2() {
|
|
2799
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
2800
|
+
const r = Math.random() * 16 | 0;
|
|
2801
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
2802
|
+
return v.toString(16);
|
|
2803
|
+
});
|
|
2804
|
+
}
|
|
2805
|
+
__name(generateUUID2, "generateUUID");
|
|
2806
|
+
function parsePinoLog2(line, source) {
|
|
2807
|
+
try {
|
|
2808
|
+
const pinoLog = JSON.parse(line);
|
|
2809
|
+
const id = generateUUID2();
|
|
2810
|
+
return {
|
|
2811
|
+
id,
|
|
2812
|
+
level: mapPinoLevelToServerLogLevel2(pinoLog.level),
|
|
2813
|
+
timestamp: new Date(pinoLog.time).getTime(),
|
|
2814
|
+
message: pinoLog.message || pinoLog.msg || "",
|
|
2815
|
+
context: pinoLog.context || null,
|
|
2816
|
+
traceId: pinoLog.trace_id || null,
|
|
2817
|
+
userId: pinoLog.user_id || null,
|
|
2818
|
+
appId: pinoLog.app_id || null,
|
|
2819
|
+
tenantId: pinoLog.tenant_id || null,
|
|
2820
|
+
stack: pinoLog.stack || null,
|
|
2821
|
+
meta: {
|
|
2822
|
+
pid: pinoLog.pid,
|
|
2823
|
+
hostname: pinoLog.hostname,
|
|
2824
|
+
path: pinoLog.path,
|
|
2825
|
+
method: pinoLog.method,
|
|
2826
|
+
statusCode: pinoLog.status_code,
|
|
2827
|
+
durationMs: pinoLog.duration_ms,
|
|
2828
|
+
ip: pinoLog.ip,
|
|
2829
|
+
requestBody: pinoLog.request_body,
|
|
2830
|
+
responseBody: pinoLog.response_body
|
|
2831
|
+
},
|
|
2832
|
+
tags: [
|
|
2833
|
+
source
|
|
2834
|
+
]
|
|
2835
|
+
};
|
|
2836
|
+
} catch {
|
|
2837
|
+
return null;
|
|
2838
|
+
}
|
|
2839
|
+
}
|
|
2840
|
+
__name(parsePinoLog2, "parsePinoLog");
|
|
2841
|
+
function parseStdLog2(line, source) {
|
|
2842
|
+
const id = generateUUID2();
|
|
2843
|
+
const match = line.match(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] \[(server|client)\] (.*)$/);
|
|
2844
|
+
if (!match) {
|
|
2845
|
+
return {
|
|
2846
|
+
id,
|
|
2847
|
+
level: extractLogLevel2(line),
|
|
2848
|
+
timestamp: Date.now(),
|
|
2849
|
+
message: line,
|
|
2850
|
+
context: null,
|
|
2851
|
+
traceId: null,
|
|
2852
|
+
userId: null,
|
|
2853
|
+
appId: null,
|
|
2854
|
+
tenantId: null,
|
|
2855
|
+
stack: null,
|
|
2856
|
+
meta: null,
|
|
2857
|
+
tags: [
|
|
2858
|
+
source
|
|
2859
|
+
]
|
|
2860
|
+
};
|
|
2861
|
+
}
|
|
2862
|
+
const [, timeStr, , content] = match;
|
|
2863
|
+
let timestamp;
|
|
2864
|
+
try {
|
|
2865
|
+
const isoStr = timeStr.replace(" ", "T");
|
|
2866
|
+
timestamp = new Date(isoStr).getTime();
|
|
2867
|
+
if (isNaN(timestamp)) {
|
|
2868
|
+
timestamp = Date.now();
|
|
2869
|
+
}
|
|
2870
|
+
} catch {
|
|
2871
|
+
timestamp = Date.now();
|
|
2872
|
+
}
|
|
2873
|
+
return {
|
|
2874
|
+
id,
|
|
2875
|
+
level: extractLogLevel2(content),
|
|
2876
|
+
timestamp,
|
|
2877
|
+
message: content,
|
|
2878
|
+
context: null,
|
|
2879
|
+
traceId: null,
|
|
2880
|
+
userId: null,
|
|
2881
|
+
appId: null,
|
|
2882
|
+
tenantId: null,
|
|
2883
|
+
stack: null,
|
|
2884
|
+
meta: null,
|
|
2885
|
+
tags: [
|
|
2886
|
+
source
|
|
2887
|
+
]
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
__name(parseStdLog2, "parseStdLog");
|
|
2891
|
+
var LogWatcher = class {
|
|
2892
|
+
static {
|
|
2893
|
+
__name(this, "LogWatcher");
|
|
2894
|
+
}
|
|
2895
|
+
logDir = "";
|
|
2896
|
+
watchers = /* @__PURE__ */ new Map();
|
|
2897
|
+
filePositions = /* @__PURE__ */ new Map();
|
|
2898
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
2899
|
+
isRunning = false;
|
|
2900
|
+
debug = false;
|
|
2901
|
+
logFiles = [
|
|
2902
|
+
{
|
|
2903
|
+
fileName: "server.log",
|
|
2904
|
+
source: "server",
|
|
2905
|
+
parser: parsePinoLog2
|
|
2906
|
+
},
|
|
2907
|
+
{
|
|
2908
|
+
fileName: "trace.log",
|
|
2909
|
+
source: "trace",
|
|
2910
|
+
parser: parsePinoLog2
|
|
2911
|
+
},
|
|
2912
|
+
{
|
|
2913
|
+
fileName: "server.std.log",
|
|
2914
|
+
source: "server-std",
|
|
2915
|
+
parser: parseStdLog2
|
|
2916
|
+
},
|
|
2917
|
+
{
|
|
2918
|
+
fileName: "client.std.log",
|
|
2919
|
+
source: "client-std",
|
|
2920
|
+
parser: parseStdLog2
|
|
2921
|
+
}
|
|
2922
|
+
];
|
|
2923
|
+
constructor(options = {}) {
|
|
2924
|
+
this.debug = options.debug ?? false;
|
|
2925
|
+
}
|
|
2926
|
+
log(...args) {
|
|
2927
|
+
if (this.debug) {
|
|
2928
|
+
console.log("[LogWatcher]", ...args);
|
|
2929
|
+
}
|
|
2930
|
+
}
|
|
2931
|
+
/**
|
|
2932
|
+
* Start watching log files
|
|
2933
|
+
*/
|
|
2934
|
+
start(logDir) {
|
|
2935
|
+
if (this.isRunning) {
|
|
2936
|
+
this.log("Already running, ignoring start call");
|
|
2937
|
+
return;
|
|
2938
|
+
}
|
|
2939
|
+
this.logDir = logDir;
|
|
2940
|
+
this.isRunning = true;
|
|
2941
|
+
this.log(`Starting to watch log files in: ${logDir}`);
|
|
2942
|
+
for (const config of this.logFiles) {
|
|
2943
|
+
this.watchFile(config);
|
|
2944
|
+
}
|
|
2945
|
+
}
|
|
2946
|
+
/**
|
|
2947
|
+
* Stop watching all files
|
|
2948
|
+
*/
|
|
2949
|
+
stop() {
|
|
2950
|
+
if (!this.isRunning) {
|
|
2951
|
+
return;
|
|
2952
|
+
}
|
|
2953
|
+
this.log("Stopping file watchers");
|
|
2954
|
+
this.isRunning = false;
|
|
2955
|
+
Array.from(this.watchers.entries()).forEach(([fileName, watcher]) => {
|
|
2956
|
+
watcher.close();
|
|
2957
|
+
this.log(`Closed watcher for: ${fileName}`);
|
|
2958
|
+
});
|
|
2959
|
+
this.watchers.clear();
|
|
2960
|
+
this.filePositions.clear();
|
|
2961
|
+
}
|
|
2962
|
+
/**
|
|
2963
|
+
* Subscribe to new log events
|
|
2964
|
+
*/
|
|
2965
|
+
onLog(callback) {
|
|
2966
|
+
this.subscribers.add(callback);
|
|
2967
|
+
this.log(`Subscriber added, total: ${this.subscribers.size}`);
|
|
2968
|
+
return () => {
|
|
2969
|
+
this.subscribers.delete(callback);
|
|
2970
|
+
this.log(`Subscriber removed, total: ${this.subscribers.size}`);
|
|
2971
|
+
};
|
|
2972
|
+
}
|
|
2973
|
+
/**
|
|
2974
|
+
* Get subscriber count
|
|
2975
|
+
*/
|
|
2976
|
+
getSubscriberCount() {
|
|
2977
|
+
return this.subscribers.size;
|
|
2978
|
+
}
|
|
2979
|
+
/**
|
|
2980
|
+
* Watch a single log file
|
|
2981
|
+
*/
|
|
2982
|
+
watchFile(config) {
|
|
2983
|
+
const filePath = path6.join(this.logDir, config.fileName);
|
|
2984
|
+
if (!fs9.existsSync(filePath)) {
|
|
2985
|
+
this.log(`File not found, skipping: ${config.fileName}`);
|
|
2986
|
+
return;
|
|
2987
|
+
}
|
|
2988
|
+
try {
|
|
2989
|
+
const stats = fs9.statSync(filePath);
|
|
2990
|
+
this.filePositions.set(config.fileName, stats.size);
|
|
2991
|
+
this.log(`Initialized position for ${config.fileName}: ${stats.size} bytes`);
|
|
2992
|
+
} catch (error) {
|
|
2993
|
+
this.log(`Failed to get initial position for ${config.fileName}:`, error);
|
|
2994
|
+
this.filePositions.set(config.fileName, 0);
|
|
2995
|
+
}
|
|
2996
|
+
try {
|
|
2997
|
+
const watcher = fs9.watch(filePath, (eventType) => {
|
|
2998
|
+
if (eventType === "change") {
|
|
2999
|
+
this.handleFileChange(config);
|
|
3000
|
+
}
|
|
3001
|
+
});
|
|
3002
|
+
watcher.on("error", (error) => {
|
|
3003
|
+
this.log(`Watcher error for ${config.fileName}:`, error);
|
|
3004
|
+
this.restartWatcher(config);
|
|
3005
|
+
});
|
|
3006
|
+
this.watchers.set(config.fileName, watcher);
|
|
3007
|
+
this.log(`Started watching: ${config.fileName}`);
|
|
3008
|
+
} catch (error) {
|
|
3009
|
+
this.log(`Failed to start watcher for ${config.fileName}:`, error);
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
/**
|
|
3013
|
+
* Restart a file watcher after error
|
|
3014
|
+
*/
|
|
3015
|
+
restartWatcher(config) {
|
|
3016
|
+
const existingWatcher = this.watchers.get(config.fileName);
|
|
3017
|
+
if (existingWatcher) {
|
|
3018
|
+
existingWatcher.close();
|
|
3019
|
+
this.watchers.delete(config.fileName);
|
|
3020
|
+
}
|
|
3021
|
+
setTimeout(() => {
|
|
3022
|
+
if (this.isRunning) {
|
|
3023
|
+
this.log(`Restarting watcher for: ${config.fileName}`);
|
|
3024
|
+
this.watchFile(config);
|
|
3025
|
+
}
|
|
3026
|
+
}, 1e3);
|
|
3027
|
+
}
|
|
3028
|
+
/**
|
|
3029
|
+
* Handle file change event - read new content
|
|
3030
|
+
*/
|
|
3031
|
+
handleFileChange(config) {
|
|
3032
|
+
const filePath = path6.join(this.logDir, config.fileName);
|
|
3033
|
+
const lastPosition = this.filePositions.get(config.fileName) || 0;
|
|
3034
|
+
try {
|
|
3035
|
+
const stats = fs9.statSync(filePath);
|
|
3036
|
+
const currentSize = stats.size;
|
|
3037
|
+
if (currentSize < lastPosition) {
|
|
3038
|
+
this.log(`File ${config.fileName} was truncated, resetting position`);
|
|
3039
|
+
this.filePositions.set(config.fileName, 0);
|
|
3040
|
+
this.handleFileChange(config);
|
|
3041
|
+
return;
|
|
3042
|
+
}
|
|
3043
|
+
if (currentSize === lastPosition) {
|
|
3044
|
+
return;
|
|
3045
|
+
}
|
|
3046
|
+
const readSize = currentSize - lastPosition;
|
|
3047
|
+
const buffer = Buffer.alloc(readSize);
|
|
3048
|
+
const fd = fs9.openSync(filePath, "r");
|
|
3049
|
+
try {
|
|
3050
|
+
fs9.readSync(fd, buffer, 0, readSize, lastPosition);
|
|
3051
|
+
} finally {
|
|
3052
|
+
fs9.closeSync(fd);
|
|
3053
|
+
}
|
|
3054
|
+
this.filePositions.set(config.fileName, currentSize);
|
|
3055
|
+
const content = buffer.toString("utf8");
|
|
3056
|
+
const lines = content.split("\n");
|
|
3057
|
+
for (const line of lines) {
|
|
3058
|
+
if (!line.trim()) continue;
|
|
3059
|
+
try {
|
|
3060
|
+
const log = config.parser(line, config.source);
|
|
3061
|
+
if (log) {
|
|
3062
|
+
this.notifySubscribers(log);
|
|
3063
|
+
}
|
|
3064
|
+
} catch {
|
|
3065
|
+
}
|
|
3066
|
+
}
|
|
3067
|
+
} catch (error) {
|
|
3068
|
+
this.log(`Error reading file ${config.fileName}:`, error);
|
|
3069
|
+
}
|
|
3070
|
+
}
|
|
3071
|
+
/**
|
|
3072
|
+
* Notify all subscribers of new log
|
|
3073
|
+
*/
|
|
3074
|
+
notifySubscribers(log) {
|
|
3075
|
+
Array.from(this.subscribers).forEach((subscriber) => {
|
|
3076
|
+
try {
|
|
3077
|
+
subscriber(log);
|
|
3078
|
+
} catch (error) {
|
|
3079
|
+
this.log("Subscriber error:", error);
|
|
3080
|
+
}
|
|
3081
|
+
});
|
|
3082
|
+
}
|
|
3083
|
+
};
|
|
3084
|
+
|
|
3085
|
+
// src/middlewares/dev-logs/sse/client-manager.ts
|
|
3086
|
+
var ClientManager = class {
|
|
3087
|
+
static {
|
|
3088
|
+
__name(this, "ClientManager");
|
|
3089
|
+
}
|
|
3090
|
+
clients = /* @__PURE__ */ new Map();
|
|
3091
|
+
debug = false;
|
|
3092
|
+
constructor(options = {}) {
|
|
3093
|
+
this.debug = options.debug ?? false;
|
|
3094
|
+
}
|
|
3095
|
+
log(...args) {
|
|
3096
|
+
if (this.debug) {
|
|
3097
|
+
console.log("[ClientManager]", ...args);
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
/**
|
|
3101
|
+
* Generate a unique client ID
|
|
3102
|
+
*/
|
|
3103
|
+
generateClientId() {
|
|
3104
|
+
return `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
3105
|
+
}
|
|
3106
|
+
/**
|
|
3107
|
+
* Add a new client connection
|
|
3108
|
+
*/
|
|
3109
|
+
addClient(id, res) {
|
|
3110
|
+
this.clients.set(id, {
|
|
3111
|
+
id,
|
|
3112
|
+
res,
|
|
3113
|
+
connectedAt: Date.now()
|
|
3114
|
+
});
|
|
3115
|
+
this.log(`Client connected: ${id}, total clients: ${this.clients.size}`);
|
|
3116
|
+
}
|
|
3117
|
+
/**
|
|
3118
|
+
* Remove a client connection
|
|
3119
|
+
*/
|
|
3120
|
+
removeClient(id) {
|
|
3121
|
+
if (this.clients.has(id)) {
|
|
3122
|
+
this.clients.delete(id);
|
|
3123
|
+
this.log(`Client disconnected: ${id}, total clients: ${this.clients.size}`);
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
/**
|
|
3127
|
+
* Get current client count
|
|
3128
|
+
*/
|
|
3129
|
+
getClientCount() {
|
|
3130
|
+
return this.clients.size;
|
|
3131
|
+
}
|
|
3132
|
+
/**
|
|
3133
|
+
* Check if a client exists
|
|
3134
|
+
*/
|
|
3135
|
+
hasClient(id) {
|
|
3136
|
+
return this.clients.has(id);
|
|
3137
|
+
}
|
|
3138
|
+
/**
|
|
3139
|
+
* Send SSE event to a specific client
|
|
3140
|
+
*/
|
|
3141
|
+
sendToClient(clientId, event, data) {
|
|
3142
|
+
const client = this.clients.get(clientId);
|
|
3143
|
+
if (!client) {
|
|
3144
|
+
return false;
|
|
3145
|
+
}
|
|
3146
|
+
try {
|
|
3147
|
+
const message = this.formatSSEMessage(event, data);
|
|
3148
|
+
client.res.write(message);
|
|
3149
|
+
return true;
|
|
3150
|
+
} catch (error) {
|
|
3151
|
+
this.log(`Failed to send to client ${clientId}:`, error);
|
|
3152
|
+
this.removeClient(clientId);
|
|
3153
|
+
return false;
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
/**
|
|
3157
|
+
* Broadcast SSE event to all clients
|
|
3158
|
+
*/
|
|
3159
|
+
broadcast(event, data) {
|
|
3160
|
+
const message = this.formatSSEMessage(event, data);
|
|
3161
|
+
const failedClients = [];
|
|
3162
|
+
Array.from(this.clients.entries()).forEach(([id, client]) => {
|
|
3163
|
+
try {
|
|
3164
|
+
client.res.write(message);
|
|
3165
|
+
} catch (error) {
|
|
3166
|
+
this.log(`Broadcast failed for client ${id}:`, error);
|
|
3167
|
+
failedClients.push(id);
|
|
3168
|
+
}
|
|
3169
|
+
});
|
|
3170
|
+
failedClients.forEach((id) => {
|
|
3171
|
+
this.removeClient(id);
|
|
3172
|
+
});
|
|
3173
|
+
}
|
|
3174
|
+
/**
|
|
3175
|
+
* Format SSE message
|
|
3176
|
+
* SSE format: event: <event>\ndata: <json>\n\n
|
|
3177
|
+
*/
|
|
3178
|
+
formatSSEMessage(event, data) {
|
|
3179
|
+
const jsonData = JSON.stringify(data);
|
|
3180
|
+
return `event: ${event}
|
|
3181
|
+
data: ${jsonData}
|
|
3182
|
+
|
|
3183
|
+
`;
|
|
3184
|
+
}
|
|
3185
|
+
/**
|
|
3186
|
+
* Get all client IDs
|
|
3187
|
+
*/
|
|
3188
|
+
getClientIds() {
|
|
3189
|
+
return Array.from(this.clients.keys());
|
|
3190
|
+
}
|
|
3191
|
+
/**
|
|
3192
|
+
* Get client info
|
|
3193
|
+
*/
|
|
3194
|
+
getClientInfo(id) {
|
|
3195
|
+
const client = this.clients.get(id);
|
|
3196
|
+
if (!client) {
|
|
3197
|
+
return null;
|
|
3198
|
+
}
|
|
3199
|
+
return {
|
|
3200
|
+
id: client.id,
|
|
3201
|
+
connectedAt: client.connectedAt
|
|
3202
|
+
};
|
|
3203
|
+
}
|
|
3204
|
+
/**
|
|
3205
|
+
* Close all client connections
|
|
3206
|
+
*/
|
|
3207
|
+
closeAll() {
|
|
3208
|
+
this.log(`Closing all ${this.clients.size} clients`);
|
|
3209
|
+
Array.from(this.clients.entries()).forEach(([id, client]) => {
|
|
3210
|
+
try {
|
|
3211
|
+
client.res.end();
|
|
3212
|
+
} catch (error) {
|
|
3213
|
+
this.log(`Error closing client ${id}:`, error);
|
|
3214
|
+
}
|
|
3215
|
+
});
|
|
3216
|
+
this.clients.clear();
|
|
3217
|
+
}
|
|
3218
|
+
};
|
|
3219
|
+
|
|
3220
|
+
// src/middlewares/dev-logs/sse/sse.controller.ts
|
|
3221
|
+
function createSSEHandler(logDir, options = {}) {
|
|
3222
|
+
const { debug = false, heartbeatInterval = 3e4 } = options;
|
|
3223
|
+
const logWatcher = new LogWatcher({
|
|
3224
|
+
debug
|
|
3225
|
+
});
|
|
3226
|
+
const clientManager = new ClientManager({
|
|
3227
|
+
debug
|
|
3228
|
+
});
|
|
3229
|
+
const log = /* @__PURE__ */ __name((...args) => {
|
|
3230
|
+
if (debug) {
|
|
3231
|
+
console.log("[SSEHandler]", ...args);
|
|
3232
|
+
}
|
|
3233
|
+
}, "log");
|
|
3234
|
+
return (req, res) => {
|
|
3235
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
3236
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
3237
|
+
res.setHeader("Connection", "keep-alive");
|
|
3238
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
3239
|
+
res.setTimeout(0);
|
|
3240
|
+
const clientId = clientManager.generateClientId();
|
|
3241
|
+
clientManager.addClient(clientId, res);
|
|
3242
|
+
log(`New SSE connection: ${clientId}`);
|
|
3243
|
+
if (clientManager.getClientCount() === 1) {
|
|
3244
|
+
log("First client connected, starting log watcher");
|
|
3245
|
+
logWatcher.start(logDir);
|
|
3246
|
+
}
|
|
3247
|
+
const unsubscribe = logWatcher.onLog((logEntry) => {
|
|
3248
|
+
clientManager.sendToClient(clientId, "log", logEntry);
|
|
3249
|
+
});
|
|
3250
|
+
clientManager.sendToClient(clientId, "connected", {
|
|
3251
|
+
clientId,
|
|
3252
|
+
timestamp: Date.now()
|
|
3253
|
+
});
|
|
3254
|
+
const heartbeat = setInterval(() => {
|
|
3255
|
+
const success = clientManager.sendToClient(clientId, "heartbeat", {
|
|
3256
|
+
timestamp: Date.now()
|
|
3257
|
+
});
|
|
3258
|
+
if (!success) {
|
|
3259
|
+
clearInterval(heartbeat);
|
|
3260
|
+
}
|
|
3261
|
+
}, heartbeatInterval);
|
|
3262
|
+
const cleanup = /* @__PURE__ */ __name(() => {
|
|
3263
|
+
log(`Client disconnected: ${clientId}`);
|
|
3264
|
+
clearInterval(heartbeat);
|
|
3265
|
+
unsubscribe();
|
|
3266
|
+
clientManager.removeClient(clientId);
|
|
3267
|
+
if (clientManager.getClientCount() === 0) {
|
|
3268
|
+
log("No more clients, stopping log watcher");
|
|
3269
|
+
logWatcher.stop();
|
|
3270
|
+
}
|
|
3271
|
+
}, "cleanup");
|
|
3272
|
+
req.on("close", cleanup);
|
|
3273
|
+
req.on("error", (error) => {
|
|
3274
|
+
log(`Client error ${clientId}:`, error);
|
|
3275
|
+
cleanup();
|
|
3276
|
+
});
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
__name(createSSEHandler, "createSSEHandler");
|
|
3280
|
+
|
|
2766
3281
|
// src/middlewares/dev-logs/router.ts
|
|
2767
3282
|
function createDevLogRouter(options = {}) {
|
|
2768
3283
|
const logDir = resolveLogDir(options.logDir);
|
|
@@ -2771,6 +3286,9 @@ function createDevLogRouter(options = {}) {
|
|
|
2771
3286
|
router.get("/trace/recent", createGetRecentTracesHandler(logDir));
|
|
2772
3287
|
router.get("/files/:fileName", createGetLogFileHandler(logDir));
|
|
2773
3288
|
router.get("/server-logs", createGetServerLogsHandler(logDir));
|
|
3289
|
+
router.get("/server-logs/stream", createSSEHandler(logDir, {
|
|
3290
|
+
debug: true
|
|
3291
|
+
}));
|
|
2774
3292
|
router.get("/trace/trigger/list", createGetTriggerListHandler(logDir));
|
|
2775
3293
|
router.get("/trace/trigger/:instanceID", createGetTriggerDetailHandler(logDir));
|
|
2776
3294
|
router.get("/health", createHealthCheckHandler());
|