@lark-apaas/devtool-kits 1.2.10 → 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 CHANGED
@@ -1850,8 +1850,8 @@ function hasSpecialPatterns(pattern) {
1850
1850
  return /[{*]/.test(pattern);
1851
1851
  }
1852
1852
  __name(hasSpecialPatterns, "hasSpecialPatterns");
1853
- function normalizePathForMatching(path7) {
1854
- return path7.replace(/\/+/g, "/").replace(/\/+$/, "");
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: join6 } = await import("path");
2207
+ const { join: join7 } = await import("path");
2208
2208
  let filePath;
2209
2209
  let parser;
2210
2210
  if (source === "server") {
2211
- filePath = join6(logDir, "server.log");
2211
+ filePath = join7(logDir, "server.log");
2212
2212
  parser = /* @__PURE__ */ __name((line) => parsePinoLog(line, "server"), "parser");
2213
2213
  } else if (source === "trace") {
2214
- filePath = join6(logDir, "trace.log");
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 = join6(logDir, "server.std.log");
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 = join6(logDir, "client.std.log");
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, path7, limit, triggerID) {
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(path7);
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, path7, instanceID) {
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(path7);
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 path7 = typeof req.query.path === "string" ? req.query.path.trim() : "/__innerapi__/automation/invoke";
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, path7, limit, triggerID);
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: path7,
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 path7 = typeof req.query.path === "string" ? req.query.path.trim() : "/__innerapi__/automation/invoke";
2682
+ const path8 = typeof req.query.path === "string" ? req.query.path.trim() : "/__innerapi__/automation/invoke";
2683
2683
  try {
2684
- const result = await readTriggerDetail(traceLogPath, path7, instanceID);
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());