@lark-apaas/devtool-kits 1.2.11-alpha.1 → 1.2.11-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/index.cjs +527 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +542 -26
- 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
|
|
|
@@ -2398,7 +2398,7 @@ __name(readLogsBySource, "readLogsBySource");
|
|
|
2398
2398
|
// src/middlewares/dev-logs/services/trigger.service.ts
|
|
2399
2399
|
var import_node_fs10 = require("fs");
|
|
2400
2400
|
var import_node_readline3 = require("readline");
|
|
2401
|
-
async function readTriggerList(filePath, trigger,
|
|
2401
|
+
async function readTriggerList(filePath, trigger, path8, limit, triggerID) {
|
|
2402
2402
|
if (!await fileExists(filePath)) {
|
|
2403
2403
|
return void 0;
|
|
2404
2404
|
}
|
|
@@ -2424,7 +2424,7 @@ async function readTriggerList(filePath, trigger, path7, limit, triggerID) {
|
|
|
2424
2424
|
if (alreadyAdded) {
|
|
2425
2425
|
return false;
|
|
2426
2426
|
}
|
|
2427
|
-
const isAutomationTrigger = builder.path?.endsWith(
|
|
2427
|
+
const isAutomationTrigger = builder.path?.endsWith(path8);
|
|
2428
2428
|
if (!isAutomationTrigger) {
|
|
2429
2429
|
return false;
|
|
2430
2430
|
}
|
|
@@ -2507,7 +2507,7 @@ async function readTriggerList(filePath, trigger, path7, limit, triggerID) {
|
|
|
2507
2507
|
};
|
|
2508
2508
|
}
|
|
2509
2509
|
__name(readTriggerList, "readTriggerList");
|
|
2510
|
-
async function readTriggerDetail(filePath,
|
|
2510
|
+
async function readTriggerDetail(filePath, path8, instanceID) {
|
|
2511
2511
|
const exists = await fileExists(filePath);
|
|
2512
2512
|
if (!exists) {
|
|
2513
2513
|
return void 0;
|
|
@@ -2523,7 +2523,7 @@ async function readTriggerDetail(filePath, path7, instanceID) {
|
|
|
2523
2523
|
for await (const line of rl) {
|
|
2524
2524
|
const entry = parseLogLine2(line);
|
|
2525
2525
|
if (!entry) continue;
|
|
2526
|
-
const isAutomationTrigger = entry.path?.endsWith(
|
|
2526
|
+
const isAutomationTrigger = entry.path?.endsWith(path8);
|
|
2527
2527
|
const hasInstanceID = entry.instance_id === instanceID && entry.trigger;
|
|
2528
2528
|
if (!isAutomationTrigger || !hasInstanceID) continue;
|
|
2529
2529
|
matches.push(entry);
|
|
@@ -2777,16 +2777,16 @@ function createGetTriggerListHandler(logDir) {
|
|
|
2777
2777
|
});
|
|
2778
2778
|
}
|
|
2779
2779
|
const triggerID = typeof req.query.triggerID === "string" ? req.query.triggerID.trim() : void 0;
|
|
2780
|
-
const
|
|
2780
|
+
const path8 = typeof req.query.path === "string" ? req.query.path.trim() : "/__innerapi__/automation/invoke";
|
|
2781
2781
|
const limit = parseLimit(req.query.limit, 10, 200);
|
|
2782
2782
|
try {
|
|
2783
|
-
const result = await readTriggerList(traceLogPath, trigger,
|
|
2783
|
+
const result = await readTriggerList(traceLogPath, trigger, path8, limit, triggerID);
|
|
2784
2784
|
if (!result) {
|
|
2785
2785
|
return handleNotFound(res, traceLogPath);
|
|
2786
2786
|
}
|
|
2787
2787
|
res.json({
|
|
2788
2788
|
file: getRelativePath(traceLogPath),
|
|
2789
|
-
path:
|
|
2789
|
+
path: path8,
|
|
2790
2790
|
...result
|
|
2791
2791
|
});
|
|
2792
2792
|
} catch (error) {
|
|
@@ -2804,9 +2804,9 @@ function createGetTriggerDetailHandler(logDir) {
|
|
|
2804
2804
|
message: "instanceID is required"
|
|
2805
2805
|
});
|
|
2806
2806
|
}
|
|
2807
|
-
const
|
|
2807
|
+
const path8 = typeof req.query.path === "string" ? req.query.path.trim() : "/__innerapi__/automation/invoke";
|
|
2808
2808
|
try {
|
|
2809
|
-
const result = await readTriggerDetail(traceLogPath,
|
|
2809
|
+
const result = await readTriggerDetail(traceLogPath, path8, instanceID);
|
|
2810
2810
|
if (!result) {
|
|
2811
2811
|
return handleNotFound(res, traceLogPath);
|
|
2812
2812
|
}
|
|
@@ -2913,6 +2913,521 @@ function createHealthCheckHandler(options = {}) {
|
|
|
2913
2913
|
}
|
|
2914
2914
|
__name(createHealthCheckHandler, "createHealthCheckHandler");
|
|
2915
2915
|
|
|
2916
|
+
// src/middlewares/dev-logs/sse/log-watcher.ts
|
|
2917
|
+
var fs9 = __toESM(require("fs"), 1);
|
|
2918
|
+
var path6 = __toESM(require("path"), 1);
|
|
2919
|
+
function mapPinoLevelToServerLogLevel2(pinoLevel) {
|
|
2920
|
+
if (typeof pinoLevel === "string") {
|
|
2921
|
+
const lower = pinoLevel.toLowerCase();
|
|
2922
|
+
if (lower === "fatal") return "fatal";
|
|
2923
|
+
if (lower === "error") return "error";
|
|
2924
|
+
if (lower === "warn" || lower === "warning") return "warn";
|
|
2925
|
+
if (lower === "info" || lower === "log") return "log";
|
|
2926
|
+
if (lower === "debug") return "debug";
|
|
2927
|
+
if (lower === "trace" || lower === "verbose") return "verbose";
|
|
2928
|
+
return "log";
|
|
2929
|
+
}
|
|
2930
|
+
if (pinoLevel >= 60) return "fatal";
|
|
2931
|
+
if (pinoLevel >= 50) return "error";
|
|
2932
|
+
if (pinoLevel >= 40) return "warn";
|
|
2933
|
+
if (pinoLevel >= 30) return "log";
|
|
2934
|
+
if (pinoLevel >= 20) return "debug";
|
|
2935
|
+
return "verbose";
|
|
2936
|
+
}
|
|
2937
|
+
__name(mapPinoLevelToServerLogLevel2, "mapPinoLevelToServerLogLevel");
|
|
2938
|
+
function extractLogLevel2(text) {
|
|
2939
|
+
const lower = text.toLowerCase();
|
|
2940
|
+
if (lower.includes("fatal") || lower.includes("critical")) return "fatal";
|
|
2941
|
+
if (lower.includes("error") || lower.includes("<e>") || lower.includes("\u2716")) return "error";
|
|
2942
|
+
if (lower.includes("warn") || lower.includes("warning") || lower.includes("<w>") || lower.includes("\u26A0")) return "warn";
|
|
2943
|
+
if (lower.includes("debug") || lower.includes("<d>")) return "debug";
|
|
2944
|
+
if (lower.includes("verbose") || lower.includes("trace")) return "verbose";
|
|
2945
|
+
return "log";
|
|
2946
|
+
}
|
|
2947
|
+
__name(extractLogLevel2, "extractLogLevel");
|
|
2948
|
+
function generateUUID2() {
|
|
2949
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
|
2950
|
+
const r = Math.random() * 16 | 0;
|
|
2951
|
+
const v = c === "x" ? r : r & 3 | 8;
|
|
2952
|
+
return v.toString(16);
|
|
2953
|
+
});
|
|
2954
|
+
}
|
|
2955
|
+
__name(generateUUID2, "generateUUID");
|
|
2956
|
+
function parsePinoLog2(line, source) {
|
|
2957
|
+
try {
|
|
2958
|
+
const pinoLog = JSON.parse(line);
|
|
2959
|
+
const id = generateUUID2();
|
|
2960
|
+
return {
|
|
2961
|
+
id,
|
|
2962
|
+
level: mapPinoLevelToServerLogLevel2(pinoLog.level),
|
|
2963
|
+
timestamp: new Date(pinoLog.time).getTime(),
|
|
2964
|
+
message: pinoLog.message || pinoLog.msg || "",
|
|
2965
|
+
context: pinoLog.context || null,
|
|
2966
|
+
traceId: pinoLog.trace_id || null,
|
|
2967
|
+
userId: pinoLog.user_id || null,
|
|
2968
|
+
appId: pinoLog.app_id || null,
|
|
2969
|
+
tenantId: pinoLog.tenant_id || null,
|
|
2970
|
+
stack: pinoLog.stack || null,
|
|
2971
|
+
meta: {
|
|
2972
|
+
pid: pinoLog.pid,
|
|
2973
|
+
hostname: pinoLog.hostname,
|
|
2974
|
+
path: pinoLog.path,
|
|
2975
|
+
method: pinoLog.method,
|
|
2976
|
+
statusCode: pinoLog.status_code,
|
|
2977
|
+
durationMs: pinoLog.duration_ms,
|
|
2978
|
+
ip: pinoLog.ip,
|
|
2979
|
+
requestBody: pinoLog.request_body,
|
|
2980
|
+
responseBody: pinoLog.response_body
|
|
2981
|
+
},
|
|
2982
|
+
tags: [
|
|
2983
|
+
source
|
|
2984
|
+
]
|
|
2985
|
+
};
|
|
2986
|
+
} catch {
|
|
2987
|
+
return null;
|
|
2988
|
+
}
|
|
2989
|
+
}
|
|
2990
|
+
__name(parsePinoLog2, "parsePinoLog");
|
|
2991
|
+
function parseStdLog2(line, source) {
|
|
2992
|
+
const id = generateUUID2();
|
|
2993
|
+
const match = line.match(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] \[(server|client)\] (.*)$/);
|
|
2994
|
+
if (!match) {
|
|
2995
|
+
return {
|
|
2996
|
+
id,
|
|
2997
|
+
level: extractLogLevel2(line),
|
|
2998
|
+
timestamp: Date.now(),
|
|
2999
|
+
message: line,
|
|
3000
|
+
context: null,
|
|
3001
|
+
traceId: null,
|
|
3002
|
+
userId: null,
|
|
3003
|
+
appId: null,
|
|
3004
|
+
tenantId: null,
|
|
3005
|
+
stack: null,
|
|
3006
|
+
meta: null,
|
|
3007
|
+
tags: [
|
|
3008
|
+
source
|
|
3009
|
+
]
|
|
3010
|
+
};
|
|
3011
|
+
}
|
|
3012
|
+
const [, timeStr, , content] = match;
|
|
3013
|
+
let timestamp;
|
|
3014
|
+
try {
|
|
3015
|
+
const isoStr = timeStr.replace(" ", "T");
|
|
3016
|
+
timestamp = new Date(isoStr).getTime();
|
|
3017
|
+
if (isNaN(timestamp)) {
|
|
3018
|
+
timestamp = Date.now();
|
|
3019
|
+
}
|
|
3020
|
+
} catch {
|
|
3021
|
+
timestamp = Date.now();
|
|
3022
|
+
}
|
|
3023
|
+
return {
|
|
3024
|
+
id,
|
|
3025
|
+
level: extractLogLevel2(content),
|
|
3026
|
+
timestamp,
|
|
3027
|
+
message: content,
|
|
3028
|
+
context: null,
|
|
3029
|
+
traceId: null,
|
|
3030
|
+
userId: null,
|
|
3031
|
+
appId: null,
|
|
3032
|
+
tenantId: null,
|
|
3033
|
+
stack: null,
|
|
3034
|
+
meta: null,
|
|
3035
|
+
tags: [
|
|
3036
|
+
source
|
|
3037
|
+
]
|
|
3038
|
+
};
|
|
3039
|
+
}
|
|
3040
|
+
__name(parseStdLog2, "parseStdLog");
|
|
3041
|
+
var LogWatcher = class {
|
|
3042
|
+
static {
|
|
3043
|
+
__name(this, "LogWatcher");
|
|
3044
|
+
}
|
|
3045
|
+
logDir = "";
|
|
3046
|
+
watchers = /* @__PURE__ */ new Map();
|
|
3047
|
+
filePositions = /* @__PURE__ */ new Map();
|
|
3048
|
+
subscribers = /* @__PURE__ */ new Set();
|
|
3049
|
+
isRunning = false;
|
|
3050
|
+
debug = false;
|
|
3051
|
+
logFiles = [
|
|
3052
|
+
{
|
|
3053
|
+
fileName: "server.log",
|
|
3054
|
+
source: "server",
|
|
3055
|
+
parser: parsePinoLog2
|
|
3056
|
+
},
|
|
3057
|
+
{
|
|
3058
|
+
fileName: "trace.log",
|
|
3059
|
+
source: "trace",
|
|
3060
|
+
parser: parsePinoLog2
|
|
3061
|
+
},
|
|
3062
|
+
{
|
|
3063
|
+
fileName: "server.std.log",
|
|
3064
|
+
source: "server-std",
|
|
3065
|
+
parser: parseStdLog2
|
|
3066
|
+
},
|
|
3067
|
+
{
|
|
3068
|
+
fileName: "client.std.log",
|
|
3069
|
+
source: "client-std",
|
|
3070
|
+
parser: parseStdLog2
|
|
3071
|
+
}
|
|
3072
|
+
];
|
|
3073
|
+
constructor(options = {}) {
|
|
3074
|
+
this.debug = options.debug ?? false;
|
|
3075
|
+
}
|
|
3076
|
+
log(...args) {
|
|
3077
|
+
if (this.debug) {
|
|
3078
|
+
console.log("[LogWatcher]", ...args);
|
|
3079
|
+
}
|
|
3080
|
+
}
|
|
3081
|
+
/**
|
|
3082
|
+
* Start watching log files
|
|
3083
|
+
*/
|
|
3084
|
+
start(logDir) {
|
|
3085
|
+
if (this.isRunning) {
|
|
3086
|
+
this.log("Already running, ignoring start call");
|
|
3087
|
+
return;
|
|
3088
|
+
}
|
|
3089
|
+
this.logDir = logDir;
|
|
3090
|
+
this.isRunning = true;
|
|
3091
|
+
this.log(`Starting to watch log files in: ${logDir}`);
|
|
3092
|
+
for (const config of this.logFiles) {
|
|
3093
|
+
this.watchFile(config);
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
/**
|
|
3097
|
+
* Stop watching all files
|
|
3098
|
+
*/
|
|
3099
|
+
stop() {
|
|
3100
|
+
if (!this.isRunning) {
|
|
3101
|
+
return;
|
|
3102
|
+
}
|
|
3103
|
+
this.log("Stopping file watchers");
|
|
3104
|
+
this.isRunning = false;
|
|
3105
|
+
Array.from(this.watchers.entries()).forEach(([fileName, watcher]) => {
|
|
3106
|
+
watcher.close();
|
|
3107
|
+
this.log(`Closed watcher for: ${fileName}`);
|
|
3108
|
+
});
|
|
3109
|
+
this.watchers.clear();
|
|
3110
|
+
this.filePositions.clear();
|
|
3111
|
+
}
|
|
3112
|
+
/**
|
|
3113
|
+
* Subscribe to new log events
|
|
3114
|
+
*/
|
|
3115
|
+
onLog(callback) {
|
|
3116
|
+
this.subscribers.add(callback);
|
|
3117
|
+
this.log(`Subscriber added, total: ${this.subscribers.size}`);
|
|
3118
|
+
return () => {
|
|
3119
|
+
this.subscribers.delete(callback);
|
|
3120
|
+
this.log(`Subscriber removed, total: ${this.subscribers.size}`);
|
|
3121
|
+
};
|
|
3122
|
+
}
|
|
3123
|
+
/**
|
|
3124
|
+
* Get subscriber count
|
|
3125
|
+
*/
|
|
3126
|
+
getSubscriberCount() {
|
|
3127
|
+
return this.subscribers.size;
|
|
3128
|
+
}
|
|
3129
|
+
/**
|
|
3130
|
+
* Watch a single log file
|
|
3131
|
+
*/
|
|
3132
|
+
watchFile(config) {
|
|
3133
|
+
const filePath = path6.join(this.logDir, config.fileName);
|
|
3134
|
+
if (!fs9.existsSync(filePath)) {
|
|
3135
|
+
this.log(`File not found, skipping: ${config.fileName}`);
|
|
3136
|
+
return;
|
|
3137
|
+
}
|
|
3138
|
+
try {
|
|
3139
|
+
const stats = fs9.statSync(filePath);
|
|
3140
|
+
this.filePositions.set(config.fileName, stats.size);
|
|
3141
|
+
this.log(`Initialized position for ${config.fileName}: ${stats.size} bytes`);
|
|
3142
|
+
} catch (error) {
|
|
3143
|
+
this.log(`Failed to get initial position for ${config.fileName}:`, error);
|
|
3144
|
+
this.filePositions.set(config.fileName, 0);
|
|
3145
|
+
}
|
|
3146
|
+
try {
|
|
3147
|
+
const watcher = fs9.watch(filePath, (eventType) => {
|
|
3148
|
+
if (eventType === "change") {
|
|
3149
|
+
this.handleFileChange(config);
|
|
3150
|
+
}
|
|
3151
|
+
});
|
|
3152
|
+
watcher.on("error", (error) => {
|
|
3153
|
+
this.log(`Watcher error for ${config.fileName}:`, error);
|
|
3154
|
+
this.restartWatcher(config);
|
|
3155
|
+
});
|
|
3156
|
+
this.watchers.set(config.fileName, watcher);
|
|
3157
|
+
this.log(`Started watching: ${config.fileName}`);
|
|
3158
|
+
} catch (error) {
|
|
3159
|
+
this.log(`Failed to start watcher for ${config.fileName}:`, error);
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3162
|
+
/**
|
|
3163
|
+
* Restart a file watcher after error
|
|
3164
|
+
*/
|
|
3165
|
+
restartWatcher(config) {
|
|
3166
|
+
const existingWatcher = this.watchers.get(config.fileName);
|
|
3167
|
+
if (existingWatcher) {
|
|
3168
|
+
existingWatcher.close();
|
|
3169
|
+
this.watchers.delete(config.fileName);
|
|
3170
|
+
}
|
|
3171
|
+
setTimeout(() => {
|
|
3172
|
+
if (this.isRunning) {
|
|
3173
|
+
this.log(`Restarting watcher for: ${config.fileName}`);
|
|
3174
|
+
this.watchFile(config);
|
|
3175
|
+
}
|
|
3176
|
+
}, 1e3);
|
|
3177
|
+
}
|
|
3178
|
+
/**
|
|
3179
|
+
* Handle file change event - read new content
|
|
3180
|
+
*/
|
|
3181
|
+
handleFileChange(config) {
|
|
3182
|
+
const filePath = path6.join(this.logDir, config.fileName);
|
|
3183
|
+
const lastPosition = this.filePositions.get(config.fileName) || 0;
|
|
3184
|
+
try {
|
|
3185
|
+
const stats = fs9.statSync(filePath);
|
|
3186
|
+
const currentSize = stats.size;
|
|
3187
|
+
if (currentSize < lastPosition) {
|
|
3188
|
+
this.log(`File ${config.fileName} was truncated, resetting position`);
|
|
3189
|
+
this.filePositions.set(config.fileName, 0);
|
|
3190
|
+
this.handleFileChange(config);
|
|
3191
|
+
return;
|
|
3192
|
+
}
|
|
3193
|
+
if (currentSize === lastPosition) {
|
|
3194
|
+
return;
|
|
3195
|
+
}
|
|
3196
|
+
const readSize = currentSize - lastPosition;
|
|
3197
|
+
const buffer = Buffer.alloc(readSize);
|
|
3198
|
+
const fd = fs9.openSync(filePath, "r");
|
|
3199
|
+
try {
|
|
3200
|
+
fs9.readSync(fd, buffer, 0, readSize, lastPosition);
|
|
3201
|
+
} finally {
|
|
3202
|
+
fs9.closeSync(fd);
|
|
3203
|
+
}
|
|
3204
|
+
this.filePositions.set(config.fileName, currentSize);
|
|
3205
|
+
const content = buffer.toString("utf8");
|
|
3206
|
+
const lines = content.split("\n");
|
|
3207
|
+
for (const line of lines) {
|
|
3208
|
+
if (!line.trim()) continue;
|
|
3209
|
+
try {
|
|
3210
|
+
const log = config.parser(line, config.source);
|
|
3211
|
+
if (log) {
|
|
3212
|
+
this.notifySubscribers(log);
|
|
3213
|
+
}
|
|
3214
|
+
} catch {
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
} catch (error) {
|
|
3218
|
+
this.log(`Error reading file ${config.fileName}:`, error);
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
/**
|
|
3222
|
+
* Notify all subscribers of new log
|
|
3223
|
+
*/
|
|
3224
|
+
notifySubscribers(log) {
|
|
3225
|
+
Array.from(this.subscribers).forEach((subscriber) => {
|
|
3226
|
+
try {
|
|
3227
|
+
subscriber(log);
|
|
3228
|
+
} catch (error) {
|
|
3229
|
+
this.log("Subscriber error:", error);
|
|
3230
|
+
}
|
|
3231
|
+
});
|
|
3232
|
+
}
|
|
3233
|
+
};
|
|
3234
|
+
|
|
3235
|
+
// src/middlewares/dev-logs/sse/client-manager.ts
|
|
3236
|
+
var ClientManager = class {
|
|
3237
|
+
static {
|
|
3238
|
+
__name(this, "ClientManager");
|
|
3239
|
+
}
|
|
3240
|
+
clients = /* @__PURE__ */ new Map();
|
|
3241
|
+
debug = false;
|
|
3242
|
+
constructor(options = {}) {
|
|
3243
|
+
this.debug = options.debug ?? false;
|
|
3244
|
+
}
|
|
3245
|
+
log(...args) {
|
|
3246
|
+
if (this.debug) {
|
|
3247
|
+
console.log("[ClientManager]", ...args);
|
|
3248
|
+
}
|
|
3249
|
+
}
|
|
3250
|
+
/**
|
|
3251
|
+
* Generate a unique client ID
|
|
3252
|
+
*/
|
|
3253
|
+
generateClientId() {
|
|
3254
|
+
return `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
3255
|
+
}
|
|
3256
|
+
/**
|
|
3257
|
+
* Add a new client connection
|
|
3258
|
+
*/
|
|
3259
|
+
addClient(id, res) {
|
|
3260
|
+
this.clients.set(id, {
|
|
3261
|
+
id,
|
|
3262
|
+
res,
|
|
3263
|
+
connectedAt: Date.now()
|
|
3264
|
+
});
|
|
3265
|
+
this.log(`Client connected: ${id}, total clients: ${this.clients.size}`);
|
|
3266
|
+
}
|
|
3267
|
+
/**
|
|
3268
|
+
* Remove a client connection
|
|
3269
|
+
*/
|
|
3270
|
+
removeClient(id) {
|
|
3271
|
+
if (this.clients.has(id)) {
|
|
3272
|
+
this.clients.delete(id);
|
|
3273
|
+
this.log(`Client disconnected: ${id}, total clients: ${this.clients.size}`);
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
/**
|
|
3277
|
+
* Get current client count
|
|
3278
|
+
*/
|
|
3279
|
+
getClientCount() {
|
|
3280
|
+
return this.clients.size;
|
|
3281
|
+
}
|
|
3282
|
+
/**
|
|
3283
|
+
* Check if a client exists
|
|
3284
|
+
*/
|
|
3285
|
+
hasClient(id) {
|
|
3286
|
+
return this.clients.has(id);
|
|
3287
|
+
}
|
|
3288
|
+
/**
|
|
3289
|
+
* Send SSE event to a specific client
|
|
3290
|
+
*/
|
|
3291
|
+
sendToClient(clientId, event, data) {
|
|
3292
|
+
const client = this.clients.get(clientId);
|
|
3293
|
+
if (!client) {
|
|
3294
|
+
return false;
|
|
3295
|
+
}
|
|
3296
|
+
try {
|
|
3297
|
+
const message = this.formatSSEMessage(event, data);
|
|
3298
|
+
client.res.write(message);
|
|
3299
|
+
return true;
|
|
3300
|
+
} catch (error) {
|
|
3301
|
+
this.log(`Failed to send to client ${clientId}:`, error);
|
|
3302
|
+
this.removeClient(clientId);
|
|
3303
|
+
return false;
|
|
3304
|
+
}
|
|
3305
|
+
}
|
|
3306
|
+
/**
|
|
3307
|
+
* Broadcast SSE event to all clients
|
|
3308
|
+
*/
|
|
3309
|
+
broadcast(event, data) {
|
|
3310
|
+
const message = this.formatSSEMessage(event, data);
|
|
3311
|
+
const failedClients = [];
|
|
3312
|
+
Array.from(this.clients.entries()).forEach(([id, client]) => {
|
|
3313
|
+
try {
|
|
3314
|
+
client.res.write(message);
|
|
3315
|
+
} catch (error) {
|
|
3316
|
+
this.log(`Broadcast failed for client ${id}:`, error);
|
|
3317
|
+
failedClients.push(id);
|
|
3318
|
+
}
|
|
3319
|
+
});
|
|
3320
|
+
failedClients.forEach((id) => {
|
|
3321
|
+
this.removeClient(id);
|
|
3322
|
+
});
|
|
3323
|
+
}
|
|
3324
|
+
/**
|
|
3325
|
+
* Format SSE message
|
|
3326
|
+
* SSE format: event: <event>\ndata: <json>\n\n
|
|
3327
|
+
*/
|
|
3328
|
+
formatSSEMessage(event, data) {
|
|
3329
|
+
const jsonData = JSON.stringify(data);
|
|
3330
|
+
return `event: ${event}
|
|
3331
|
+
data: ${jsonData}
|
|
3332
|
+
|
|
3333
|
+
`;
|
|
3334
|
+
}
|
|
3335
|
+
/**
|
|
3336
|
+
* Get all client IDs
|
|
3337
|
+
*/
|
|
3338
|
+
getClientIds() {
|
|
3339
|
+
return Array.from(this.clients.keys());
|
|
3340
|
+
}
|
|
3341
|
+
/**
|
|
3342
|
+
* Get client info
|
|
3343
|
+
*/
|
|
3344
|
+
getClientInfo(id) {
|
|
3345
|
+
const client = this.clients.get(id);
|
|
3346
|
+
if (!client) {
|
|
3347
|
+
return null;
|
|
3348
|
+
}
|
|
3349
|
+
return {
|
|
3350
|
+
id: client.id,
|
|
3351
|
+
connectedAt: client.connectedAt
|
|
3352
|
+
};
|
|
3353
|
+
}
|
|
3354
|
+
/**
|
|
3355
|
+
* Close all client connections
|
|
3356
|
+
*/
|
|
3357
|
+
closeAll() {
|
|
3358
|
+
this.log(`Closing all ${this.clients.size} clients`);
|
|
3359
|
+
Array.from(this.clients.entries()).forEach(([id, client]) => {
|
|
3360
|
+
try {
|
|
3361
|
+
client.res.end();
|
|
3362
|
+
} catch (error) {
|
|
3363
|
+
this.log(`Error closing client ${id}:`, error);
|
|
3364
|
+
}
|
|
3365
|
+
});
|
|
3366
|
+
this.clients.clear();
|
|
3367
|
+
}
|
|
3368
|
+
};
|
|
3369
|
+
|
|
3370
|
+
// src/middlewares/dev-logs/sse/sse.controller.ts
|
|
3371
|
+
function createSSEHandler(logDir, options = {}) {
|
|
3372
|
+
const { debug = false, heartbeatInterval = 3e4 } = options;
|
|
3373
|
+
const logWatcher = new LogWatcher({
|
|
3374
|
+
debug
|
|
3375
|
+
});
|
|
3376
|
+
const clientManager = new ClientManager({
|
|
3377
|
+
debug
|
|
3378
|
+
});
|
|
3379
|
+
const log = /* @__PURE__ */ __name((...args) => {
|
|
3380
|
+
if (debug) {
|
|
3381
|
+
console.log("[SSEHandler]", ...args);
|
|
3382
|
+
}
|
|
3383
|
+
}, "log");
|
|
3384
|
+
return (req, res) => {
|
|
3385
|
+
res.setHeader("Content-Type", "text/event-stream");
|
|
3386
|
+
res.setHeader("Cache-Control", "no-cache");
|
|
3387
|
+
res.setHeader("Connection", "keep-alive");
|
|
3388
|
+
res.setHeader("X-Accel-Buffering", "no");
|
|
3389
|
+
res.setTimeout(0);
|
|
3390
|
+
const clientId = clientManager.generateClientId();
|
|
3391
|
+
clientManager.addClient(clientId, res);
|
|
3392
|
+
log(`New SSE connection: ${clientId}`);
|
|
3393
|
+
if (clientManager.getClientCount() === 1) {
|
|
3394
|
+
log("First client connected, starting log watcher");
|
|
3395
|
+
logWatcher.start(logDir);
|
|
3396
|
+
}
|
|
3397
|
+
const unsubscribe = logWatcher.onLog((logEntry) => {
|
|
3398
|
+
clientManager.sendToClient(clientId, "log", logEntry);
|
|
3399
|
+
});
|
|
3400
|
+
clientManager.sendToClient(clientId, "connected", {
|
|
3401
|
+
clientId,
|
|
3402
|
+
timestamp: Date.now()
|
|
3403
|
+
});
|
|
3404
|
+
const heartbeat = setInterval(() => {
|
|
3405
|
+
const success = clientManager.sendToClient(clientId, "heartbeat", {
|
|
3406
|
+
timestamp: Date.now()
|
|
3407
|
+
});
|
|
3408
|
+
if (!success) {
|
|
3409
|
+
clearInterval(heartbeat);
|
|
3410
|
+
}
|
|
3411
|
+
}, heartbeatInterval);
|
|
3412
|
+
const cleanup = /* @__PURE__ */ __name(() => {
|
|
3413
|
+
log(`Client disconnected: ${clientId}`);
|
|
3414
|
+
clearInterval(heartbeat);
|
|
3415
|
+
unsubscribe();
|
|
3416
|
+
clientManager.removeClient(clientId);
|
|
3417
|
+
if (clientManager.getClientCount() === 0) {
|
|
3418
|
+
log("No more clients, stopping log watcher");
|
|
3419
|
+
logWatcher.stop();
|
|
3420
|
+
}
|
|
3421
|
+
}, "cleanup");
|
|
3422
|
+
req.on("close", cleanup);
|
|
3423
|
+
req.on("error", (error) => {
|
|
3424
|
+
log(`Client error ${clientId}:`, error);
|
|
3425
|
+
cleanup();
|
|
3426
|
+
});
|
|
3427
|
+
};
|
|
3428
|
+
}
|
|
3429
|
+
__name(createSSEHandler, "createSSEHandler");
|
|
3430
|
+
|
|
2916
3431
|
// src/middlewares/dev-logs/router.ts
|
|
2917
3432
|
function createDevLogRouter(options = {}) {
|
|
2918
3433
|
const logDir = resolveLogDir(options.logDir);
|
|
@@ -2921,6 +3436,7 @@ function createDevLogRouter(options = {}) {
|
|
|
2921
3436
|
router.get("/trace/recent", createGetRecentTracesHandler(logDir));
|
|
2922
3437
|
router.get("/files/:fileName", createGetLogFileHandler(logDir));
|
|
2923
3438
|
router.get("/server-logs", createGetServerLogsHandler(logDir));
|
|
3439
|
+
router.get("/server-logs/stream", createSSEHandler(logDir));
|
|
2924
3440
|
router.get("/trace/trigger/list", createGetTriggerListHandler(logDir));
|
|
2925
3441
|
router.get("/trace/trigger/:instanceID", createGetTriggerDetailHandler(logDir));
|
|
2926
3442
|
router.get("/trace/capability/list", createGetCapabilityTraceListHandler(logDir));
|