@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.js CHANGED
@@ -1465,7 +1465,7 @@ function sendSimpleRedirect(req, res) {
1465
1465
  __name(sendSimpleRedirect, "sendSimpleRedirect");
1466
1466
 
1467
1467
  // src/middlewares/index.ts
1468
- import path6 from "path";
1468
+ import path7 from "path";
1469
1469
 
1470
1470
  // src/middlewares/openapi/router.ts
1471
1471
  import express from "express";
@@ -1809,8 +1809,8 @@ function hasSpecialPatterns(pattern) {
1809
1809
  return /[{*]/.test(pattern);
1810
1810
  }
1811
1811
  __name(hasSpecialPatterns, "hasSpecialPatterns");
1812
- function normalizePathForMatching(path7) {
1813
- return path7.replace(/\/+/g, "/").replace(/\/+$/, "");
1812
+ function normalizePathForMatching(path8) {
1813
+ return path8.replace(/\/+/g, "/").replace(/\/+$/, "");
1814
1814
  }
1815
1815
  __name(normalizePathForMatching, "normalizePathForMatching");
1816
1816
 
@@ -2163,20 +2163,20 @@ async function readServerLogs(logDir, options = {}) {
2163
2163
  }
2164
2164
  __name(readServerLogs, "readServerLogs");
2165
2165
  async function readLogsBySource(logDir, source) {
2166
- const { join: join6 } = await import("path");
2166
+ const { join: join7 } = await import("path");
2167
2167
  let filePath;
2168
2168
  let parser;
2169
2169
  if (source === "server") {
2170
- filePath = join6(logDir, "server.log");
2170
+ filePath = join7(logDir, "server.log");
2171
2171
  parser = /* @__PURE__ */ __name((line) => parsePinoLog(line, "server"), "parser");
2172
2172
  } else if (source === "trace") {
2173
- filePath = join6(logDir, "trace.log");
2173
+ filePath = join7(logDir, "trace.log");
2174
2174
  parser = /* @__PURE__ */ __name((line) => parsePinoLog(line, "trace"), "parser");
2175
2175
  } else if (source === "server-std") {
2176
- filePath = join6(logDir, "server.std.log");
2176
+ filePath = join7(logDir, "server.std.log");
2177
2177
  parser = /* @__PURE__ */ __name((line) => parseStdLog(line, "server-std"), "parser");
2178
2178
  } else if (source === "client-std") {
2179
- filePath = join6(logDir, "client.std.log");
2179
+ filePath = join7(logDir, "client.std.log");
2180
2180
  parser = /* @__PURE__ */ __name((line) => parseStdLog(line, "client-std"), "parser");
2181
2181
  } else {
2182
2182
  console.warn(`[readLogsBySource] Unknown source: ${source}`);
@@ -2344,7 +2344,7 @@ function generateUUID() {
2344
2344
  });
2345
2345
  }
2346
2346
  __name(generateUUID, "generateUUID");
2347
- async function readTriggerList(filePath, trigger, path7, limit, triggerID) {
2347
+ async function readTriggerList(filePath, trigger, path8, limit, triggerID) {
2348
2348
  if (!await fileExists(filePath)) {
2349
2349
  return void 0;
2350
2350
  }
@@ -2370,7 +2370,7 @@ async function readTriggerList(filePath, trigger, path7, limit, triggerID) {
2370
2370
  if (alreadyAdded) {
2371
2371
  return false;
2372
2372
  }
2373
- const isAutomationTrigger = builder.path?.endsWith(path7);
2373
+ const isAutomationTrigger = builder.path?.endsWith(path8);
2374
2374
  if (!isAutomationTrigger) {
2375
2375
  return false;
2376
2376
  }
@@ -2453,7 +2453,7 @@ async function readTriggerList(filePath, trigger, path7, limit, triggerID) {
2453
2453
  };
2454
2454
  }
2455
2455
  __name(readTriggerList, "readTriggerList");
2456
- async function readTriggerDetail(filePath, path7, instanceID) {
2456
+ async function readTriggerDetail(filePath, path8, instanceID) {
2457
2457
  const exists = await fileExists(filePath);
2458
2458
  if (!exists) {
2459
2459
  return void 0;
@@ -2469,7 +2469,7 @@ async function readTriggerDetail(filePath, path7, instanceID) {
2469
2469
  for await (const line of rl) {
2470
2470
  const entry = parseLogLine2(line);
2471
2471
  if (!entry) continue;
2472
- const isAutomationTrigger = entry.path?.endsWith(path7);
2472
+ const isAutomationTrigger = entry.path?.endsWith(path8);
2473
2473
  const hasInstanceID = entry.instance_id === instanceID && entry.trigger;
2474
2474
  if (!isAutomationTrigger || !hasInstanceID) continue;
2475
2475
  matches.push(entry);
@@ -2611,16 +2611,16 @@ function createGetTriggerListHandler(logDir) {
2611
2611
  });
2612
2612
  }
2613
2613
  const triggerID = typeof req.query.triggerID === "string" ? req.query.triggerID.trim() : void 0;
2614
- const path7 = typeof req.query.path === "string" ? req.query.path.trim() : "/__innerapi__/automation/invoke";
2614
+ const path8 = typeof req.query.path === "string" ? req.query.path.trim() : "/__innerapi__/automation/invoke";
2615
2615
  const limit = parseLimit(req.query.limit, 10, 200);
2616
2616
  try {
2617
- const result = await readTriggerList(traceLogPath, trigger, path7, limit, triggerID);
2617
+ const result = await readTriggerList(traceLogPath, trigger, path8, limit, triggerID);
2618
2618
  if (!result) {
2619
2619
  return handleNotFound(res, traceLogPath);
2620
2620
  }
2621
2621
  res.json({
2622
2622
  file: getRelativePath(traceLogPath),
2623
- path: path7,
2623
+ path: path8,
2624
2624
  ...result
2625
2625
  });
2626
2626
  } catch (error) {
@@ -2638,9 +2638,9 @@ function createGetTriggerDetailHandler(logDir) {
2638
2638
  message: "instanceID is required"
2639
2639
  });
2640
2640
  }
2641
- const path7 = typeof req.query.path === "string" ? req.query.path.trim() : "/__innerapi__/automation/invoke";
2641
+ const path8 = typeof req.query.path === "string" ? req.query.path.trim() : "/__innerapi__/automation/invoke";
2642
2642
  try {
2643
- const result = await readTriggerDetail(traceLogPath, path7, instanceID);
2643
+ const result = await readTriggerDetail(traceLogPath, path8, instanceID);
2644
2644
  if (!result) {
2645
2645
  return handleNotFound(res, traceLogPath);
2646
2646
  }
@@ -2722,6 +2722,521 @@ function createHealthCheckHandler(options = {}) {
2722
2722
  }
2723
2723
  __name(createHealthCheckHandler, "createHealthCheckHandler");
2724
2724
 
2725
+ // src/middlewares/dev-logs/sse/log-watcher.ts
2726
+ import * as fs9 from "fs";
2727
+ import * as path6 from "path";
2728
+ function mapPinoLevelToServerLogLevel2(pinoLevel) {
2729
+ if (typeof pinoLevel === "string") {
2730
+ const lower = pinoLevel.toLowerCase();
2731
+ if (lower === "fatal") return "fatal";
2732
+ if (lower === "error") return "error";
2733
+ if (lower === "warn" || lower === "warning") return "warn";
2734
+ if (lower === "info" || lower === "log") return "log";
2735
+ if (lower === "debug") return "debug";
2736
+ if (lower === "trace" || lower === "verbose") return "verbose";
2737
+ return "log";
2738
+ }
2739
+ if (pinoLevel >= 60) return "fatal";
2740
+ if (pinoLevel >= 50) return "error";
2741
+ if (pinoLevel >= 40) return "warn";
2742
+ if (pinoLevel >= 30) return "log";
2743
+ if (pinoLevel >= 20) return "debug";
2744
+ return "verbose";
2745
+ }
2746
+ __name(mapPinoLevelToServerLogLevel2, "mapPinoLevelToServerLogLevel");
2747
+ function extractLogLevel2(text) {
2748
+ const lower = text.toLowerCase();
2749
+ if (lower.includes("fatal") || lower.includes("critical")) return "fatal";
2750
+ if (lower.includes("error") || lower.includes("<e>") || lower.includes("\u2716")) return "error";
2751
+ if (lower.includes("warn") || lower.includes("warning") || lower.includes("<w>") || lower.includes("\u26A0")) return "warn";
2752
+ if (lower.includes("debug") || lower.includes("<d>")) return "debug";
2753
+ if (lower.includes("verbose") || lower.includes("trace")) return "verbose";
2754
+ return "log";
2755
+ }
2756
+ __name(extractLogLevel2, "extractLogLevel");
2757
+ function generateUUID2() {
2758
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
2759
+ const r = Math.random() * 16 | 0;
2760
+ const v = c === "x" ? r : r & 3 | 8;
2761
+ return v.toString(16);
2762
+ });
2763
+ }
2764
+ __name(generateUUID2, "generateUUID");
2765
+ function parsePinoLog2(line, source) {
2766
+ try {
2767
+ const pinoLog = JSON.parse(line);
2768
+ const id = generateUUID2();
2769
+ return {
2770
+ id,
2771
+ level: mapPinoLevelToServerLogLevel2(pinoLog.level),
2772
+ timestamp: new Date(pinoLog.time).getTime(),
2773
+ message: pinoLog.message || pinoLog.msg || "",
2774
+ context: pinoLog.context || null,
2775
+ traceId: pinoLog.trace_id || null,
2776
+ userId: pinoLog.user_id || null,
2777
+ appId: pinoLog.app_id || null,
2778
+ tenantId: pinoLog.tenant_id || null,
2779
+ stack: pinoLog.stack || null,
2780
+ meta: {
2781
+ pid: pinoLog.pid,
2782
+ hostname: pinoLog.hostname,
2783
+ path: pinoLog.path,
2784
+ method: pinoLog.method,
2785
+ statusCode: pinoLog.status_code,
2786
+ durationMs: pinoLog.duration_ms,
2787
+ ip: pinoLog.ip,
2788
+ requestBody: pinoLog.request_body,
2789
+ responseBody: pinoLog.response_body
2790
+ },
2791
+ tags: [
2792
+ source
2793
+ ]
2794
+ };
2795
+ } catch {
2796
+ return null;
2797
+ }
2798
+ }
2799
+ __name(parsePinoLog2, "parsePinoLog");
2800
+ function parseStdLog2(line, source) {
2801
+ const id = generateUUID2();
2802
+ const match = line.match(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] \[(server|client)\] (.*)$/);
2803
+ if (!match) {
2804
+ return {
2805
+ id,
2806
+ level: extractLogLevel2(line),
2807
+ timestamp: Date.now(),
2808
+ message: line,
2809
+ context: null,
2810
+ traceId: null,
2811
+ userId: null,
2812
+ appId: null,
2813
+ tenantId: null,
2814
+ stack: null,
2815
+ meta: null,
2816
+ tags: [
2817
+ source
2818
+ ]
2819
+ };
2820
+ }
2821
+ const [, timeStr, , content] = match;
2822
+ let timestamp;
2823
+ try {
2824
+ const isoStr = timeStr.replace(" ", "T");
2825
+ timestamp = new Date(isoStr).getTime();
2826
+ if (isNaN(timestamp)) {
2827
+ timestamp = Date.now();
2828
+ }
2829
+ } catch {
2830
+ timestamp = Date.now();
2831
+ }
2832
+ return {
2833
+ id,
2834
+ level: extractLogLevel2(content),
2835
+ timestamp,
2836
+ message: content,
2837
+ context: null,
2838
+ traceId: null,
2839
+ userId: null,
2840
+ appId: null,
2841
+ tenantId: null,
2842
+ stack: null,
2843
+ meta: null,
2844
+ tags: [
2845
+ source
2846
+ ]
2847
+ };
2848
+ }
2849
+ __name(parseStdLog2, "parseStdLog");
2850
+ var LogWatcher = class {
2851
+ static {
2852
+ __name(this, "LogWatcher");
2853
+ }
2854
+ logDir = "";
2855
+ watchers = /* @__PURE__ */ new Map();
2856
+ filePositions = /* @__PURE__ */ new Map();
2857
+ subscribers = /* @__PURE__ */ new Set();
2858
+ isRunning = false;
2859
+ debug = false;
2860
+ logFiles = [
2861
+ {
2862
+ fileName: "server.log",
2863
+ source: "server",
2864
+ parser: parsePinoLog2
2865
+ },
2866
+ {
2867
+ fileName: "trace.log",
2868
+ source: "trace",
2869
+ parser: parsePinoLog2
2870
+ },
2871
+ {
2872
+ fileName: "server.std.log",
2873
+ source: "server-std",
2874
+ parser: parseStdLog2
2875
+ },
2876
+ {
2877
+ fileName: "client.std.log",
2878
+ source: "client-std",
2879
+ parser: parseStdLog2
2880
+ }
2881
+ ];
2882
+ constructor(options = {}) {
2883
+ this.debug = options.debug ?? false;
2884
+ }
2885
+ log(...args) {
2886
+ if (this.debug) {
2887
+ console.log("[LogWatcher]", ...args);
2888
+ }
2889
+ }
2890
+ /**
2891
+ * Start watching log files
2892
+ */
2893
+ start(logDir) {
2894
+ if (this.isRunning) {
2895
+ this.log("Already running, ignoring start call");
2896
+ return;
2897
+ }
2898
+ this.logDir = logDir;
2899
+ this.isRunning = true;
2900
+ this.log(`Starting to watch log files in: ${logDir}`);
2901
+ for (const config of this.logFiles) {
2902
+ this.watchFile(config);
2903
+ }
2904
+ }
2905
+ /**
2906
+ * Stop watching all files
2907
+ */
2908
+ stop() {
2909
+ if (!this.isRunning) {
2910
+ return;
2911
+ }
2912
+ this.log("Stopping file watchers");
2913
+ this.isRunning = false;
2914
+ Array.from(this.watchers.entries()).forEach(([fileName, watcher]) => {
2915
+ watcher.close();
2916
+ this.log(`Closed watcher for: ${fileName}`);
2917
+ });
2918
+ this.watchers.clear();
2919
+ this.filePositions.clear();
2920
+ }
2921
+ /**
2922
+ * Subscribe to new log events
2923
+ */
2924
+ onLog(callback) {
2925
+ this.subscribers.add(callback);
2926
+ this.log(`Subscriber added, total: ${this.subscribers.size}`);
2927
+ return () => {
2928
+ this.subscribers.delete(callback);
2929
+ this.log(`Subscriber removed, total: ${this.subscribers.size}`);
2930
+ };
2931
+ }
2932
+ /**
2933
+ * Get subscriber count
2934
+ */
2935
+ getSubscriberCount() {
2936
+ return this.subscribers.size;
2937
+ }
2938
+ /**
2939
+ * Watch a single log file
2940
+ */
2941
+ watchFile(config) {
2942
+ const filePath = path6.join(this.logDir, config.fileName);
2943
+ if (!fs9.existsSync(filePath)) {
2944
+ this.log(`File not found, skipping: ${config.fileName}`);
2945
+ return;
2946
+ }
2947
+ try {
2948
+ const stats = fs9.statSync(filePath);
2949
+ this.filePositions.set(config.fileName, stats.size);
2950
+ this.log(`Initialized position for ${config.fileName}: ${stats.size} bytes`);
2951
+ } catch (error) {
2952
+ this.log(`Failed to get initial position for ${config.fileName}:`, error);
2953
+ this.filePositions.set(config.fileName, 0);
2954
+ }
2955
+ try {
2956
+ const watcher = fs9.watch(filePath, (eventType) => {
2957
+ if (eventType === "change") {
2958
+ this.handleFileChange(config);
2959
+ }
2960
+ });
2961
+ watcher.on("error", (error) => {
2962
+ this.log(`Watcher error for ${config.fileName}:`, error);
2963
+ this.restartWatcher(config);
2964
+ });
2965
+ this.watchers.set(config.fileName, watcher);
2966
+ this.log(`Started watching: ${config.fileName}`);
2967
+ } catch (error) {
2968
+ this.log(`Failed to start watcher for ${config.fileName}:`, error);
2969
+ }
2970
+ }
2971
+ /**
2972
+ * Restart a file watcher after error
2973
+ */
2974
+ restartWatcher(config) {
2975
+ const existingWatcher = this.watchers.get(config.fileName);
2976
+ if (existingWatcher) {
2977
+ existingWatcher.close();
2978
+ this.watchers.delete(config.fileName);
2979
+ }
2980
+ setTimeout(() => {
2981
+ if (this.isRunning) {
2982
+ this.log(`Restarting watcher for: ${config.fileName}`);
2983
+ this.watchFile(config);
2984
+ }
2985
+ }, 1e3);
2986
+ }
2987
+ /**
2988
+ * Handle file change event - read new content
2989
+ */
2990
+ handleFileChange(config) {
2991
+ const filePath = path6.join(this.logDir, config.fileName);
2992
+ const lastPosition = this.filePositions.get(config.fileName) || 0;
2993
+ try {
2994
+ const stats = fs9.statSync(filePath);
2995
+ const currentSize = stats.size;
2996
+ if (currentSize < lastPosition) {
2997
+ this.log(`File ${config.fileName} was truncated, resetting position`);
2998
+ this.filePositions.set(config.fileName, 0);
2999
+ this.handleFileChange(config);
3000
+ return;
3001
+ }
3002
+ if (currentSize === lastPosition) {
3003
+ return;
3004
+ }
3005
+ const readSize = currentSize - lastPosition;
3006
+ const buffer = Buffer.alloc(readSize);
3007
+ const fd = fs9.openSync(filePath, "r");
3008
+ try {
3009
+ fs9.readSync(fd, buffer, 0, readSize, lastPosition);
3010
+ } finally {
3011
+ fs9.closeSync(fd);
3012
+ }
3013
+ this.filePositions.set(config.fileName, currentSize);
3014
+ const content = buffer.toString("utf8");
3015
+ const lines = content.split("\n");
3016
+ for (const line of lines) {
3017
+ if (!line.trim()) continue;
3018
+ try {
3019
+ const log = config.parser(line, config.source);
3020
+ if (log) {
3021
+ this.notifySubscribers(log);
3022
+ }
3023
+ } catch {
3024
+ }
3025
+ }
3026
+ } catch (error) {
3027
+ this.log(`Error reading file ${config.fileName}:`, error);
3028
+ }
3029
+ }
3030
+ /**
3031
+ * Notify all subscribers of new log
3032
+ */
3033
+ notifySubscribers(log) {
3034
+ Array.from(this.subscribers).forEach((subscriber) => {
3035
+ try {
3036
+ subscriber(log);
3037
+ } catch (error) {
3038
+ this.log("Subscriber error:", error);
3039
+ }
3040
+ });
3041
+ }
3042
+ };
3043
+
3044
+ // src/middlewares/dev-logs/sse/client-manager.ts
3045
+ var ClientManager = class {
3046
+ static {
3047
+ __name(this, "ClientManager");
3048
+ }
3049
+ clients = /* @__PURE__ */ new Map();
3050
+ debug = false;
3051
+ constructor(options = {}) {
3052
+ this.debug = options.debug ?? false;
3053
+ }
3054
+ log(...args) {
3055
+ if (this.debug) {
3056
+ console.log("[ClientManager]", ...args);
3057
+ }
3058
+ }
3059
+ /**
3060
+ * Generate a unique client ID
3061
+ */
3062
+ generateClientId() {
3063
+ return `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
3064
+ }
3065
+ /**
3066
+ * Add a new client connection
3067
+ */
3068
+ addClient(id, res) {
3069
+ this.clients.set(id, {
3070
+ id,
3071
+ res,
3072
+ connectedAt: Date.now()
3073
+ });
3074
+ this.log(`Client connected: ${id}, total clients: ${this.clients.size}`);
3075
+ }
3076
+ /**
3077
+ * Remove a client connection
3078
+ */
3079
+ removeClient(id) {
3080
+ if (this.clients.has(id)) {
3081
+ this.clients.delete(id);
3082
+ this.log(`Client disconnected: ${id}, total clients: ${this.clients.size}`);
3083
+ }
3084
+ }
3085
+ /**
3086
+ * Get current client count
3087
+ */
3088
+ getClientCount() {
3089
+ return this.clients.size;
3090
+ }
3091
+ /**
3092
+ * Check if a client exists
3093
+ */
3094
+ hasClient(id) {
3095
+ return this.clients.has(id);
3096
+ }
3097
+ /**
3098
+ * Send SSE event to a specific client
3099
+ */
3100
+ sendToClient(clientId, event, data) {
3101
+ const client = this.clients.get(clientId);
3102
+ if (!client) {
3103
+ return false;
3104
+ }
3105
+ try {
3106
+ const message = this.formatSSEMessage(event, data);
3107
+ client.res.write(message);
3108
+ return true;
3109
+ } catch (error) {
3110
+ this.log(`Failed to send to client ${clientId}:`, error);
3111
+ this.removeClient(clientId);
3112
+ return false;
3113
+ }
3114
+ }
3115
+ /**
3116
+ * Broadcast SSE event to all clients
3117
+ */
3118
+ broadcast(event, data) {
3119
+ const message = this.formatSSEMessage(event, data);
3120
+ const failedClients = [];
3121
+ Array.from(this.clients.entries()).forEach(([id, client]) => {
3122
+ try {
3123
+ client.res.write(message);
3124
+ } catch (error) {
3125
+ this.log(`Broadcast failed for client ${id}:`, error);
3126
+ failedClients.push(id);
3127
+ }
3128
+ });
3129
+ failedClients.forEach((id) => {
3130
+ this.removeClient(id);
3131
+ });
3132
+ }
3133
+ /**
3134
+ * Format SSE message
3135
+ * SSE format: event: <event>\ndata: <json>\n\n
3136
+ */
3137
+ formatSSEMessage(event, data) {
3138
+ const jsonData = JSON.stringify(data);
3139
+ return `event: ${event}
3140
+ data: ${jsonData}
3141
+
3142
+ `;
3143
+ }
3144
+ /**
3145
+ * Get all client IDs
3146
+ */
3147
+ getClientIds() {
3148
+ return Array.from(this.clients.keys());
3149
+ }
3150
+ /**
3151
+ * Get client info
3152
+ */
3153
+ getClientInfo(id) {
3154
+ const client = this.clients.get(id);
3155
+ if (!client) {
3156
+ return null;
3157
+ }
3158
+ return {
3159
+ id: client.id,
3160
+ connectedAt: client.connectedAt
3161
+ };
3162
+ }
3163
+ /**
3164
+ * Close all client connections
3165
+ */
3166
+ closeAll() {
3167
+ this.log(`Closing all ${this.clients.size} clients`);
3168
+ Array.from(this.clients.entries()).forEach(([id, client]) => {
3169
+ try {
3170
+ client.res.end();
3171
+ } catch (error) {
3172
+ this.log(`Error closing client ${id}:`, error);
3173
+ }
3174
+ });
3175
+ this.clients.clear();
3176
+ }
3177
+ };
3178
+
3179
+ // src/middlewares/dev-logs/sse/sse.controller.ts
3180
+ function createSSEHandler(logDir, options = {}) {
3181
+ const { debug = false, heartbeatInterval = 3e4 } = options;
3182
+ const logWatcher = new LogWatcher({
3183
+ debug
3184
+ });
3185
+ const clientManager = new ClientManager({
3186
+ debug
3187
+ });
3188
+ const log = /* @__PURE__ */ __name((...args) => {
3189
+ if (debug) {
3190
+ console.log("[SSEHandler]", ...args);
3191
+ }
3192
+ }, "log");
3193
+ return (req, res) => {
3194
+ res.setHeader("Content-Type", "text/event-stream");
3195
+ res.setHeader("Cache-Control", "no-cache");
3196
+ res.setHeader("Connection", "keep-alive");
3197
+ res.setHeader("X-Accel-Buffering", "no");
3198
+ res.setTimeout(0);
3199
+ const clientId = clientManager.generateClientId();
3200
+ clientManager.addClient(clientId, res);
3201
+ log(`New SSE connection: ${clientId}`);
3202
+ if (clientManager.getClientCount() === 1) {
3203
+ log("First client connected, starting log watcher");
3204
+ logWatcher.start(logDir);
3205
+ }
3206
+ const unsubscribe = logWatcher.onLog((logEntry) => {
3207
+ clientManager.sendToClient(clientId, "log", logEntry);
3208
+ });
3209
+ clientManager.sendToClient(clientId, "connected", {
3210
+ clientId,
3211
+ timestamp: Date.now()
3212
+ });
3213
+ const heartbeat = setInterval(() => {
3214
+ const success = clientManager.sendToClient(clientId, "heartbeat", {
3215
+ timestamp: Date.now()
3216
+ });
3217
+ if (!success) {
3218
+ clearInterval(heartbeat);
3219
+ }
3220
+ }, heartbeatInterval);
3221
+ const cleanup = /* @__PURE__ */ __name(() => {
3222
+ log(`Client disconnected: ${clientId}`);
3223
+ clearInterval(heartbeat);
3224
+ unsubscribe();
3225
+ clientManager.removeClient(clientId);
3226
+ if (clientManager.getClientCount() === 0) {
3227
+ log("No more clients, stopping log watcher");
3228
+ logWatcher.stop();
3229
+ }
3230
+ }, "cleanup");
3231
+ req.on("close", cleanup);
3232
+ req.on("error", (error) => {
3233
+ log(`Client error ${clientId}:`, error);
3234
+ cleanup();
3235
+ });
3236
+ };
3237
+ }
3238
+ __name(createSSEHandler, "createSSEHandler");
3239
+
2725
3240
  // src/middlewares/dev-logs/router.ts
2726
3241
  function createDevLogRouter(options = {}) {
2727
3242
  const logDir = resolveLogDir(options.logDir);
@@ -2730,6 +3245,9 @@ function createDevLogRouter(options = {}) {
2730
3245
  router.get("/trace/recent", createGetRecentTracesHandler(logDir));
2731
3246
  router.get("/files/:fileName", createGetLogFileHandler(logDir));
2732
3247
  router.get("/server-logs", createGetServerLogsHandler(logDir));
3248
+ router.get("/server-logs/stream", createSSEHandler(logDir, {
3249
+ debug: true
3250
+ }));
2733
3251
  router.get("/trace/trigger/list", createGetTriggerListHandler(logDir));
2734
3252
  router.get("/trace/trigger/:instanceID", createGetTriggerDetailHandler(logDir));
2735
3253
  router.get("/health", createHealthCheckHandler());
@@ -2790,22 +3308,22 @@ __name(createDevLogsMiddleware, "createDevLogsMiddleware");
2790
3308
  import express3 from "express";
2791
3309
 
2792
3310
  // src/middlewares/collect-logs/controller.ts
2793
- import { join as join5 } from "path";
2794
- import fs10 from "fs";
3311
+ import { join as join6 } from "path";
3312
+ import fs11 from "fs";
2795
3313
 
2796
3314
  // src/middlewares/collect-logs/utils.ts
2797
- import { isAbsolute as isAbsolute2, join as join4 } from "path";
2798
- import fs9 from "fs";
3315
+ import { isAbsolute as isAbsolute2, join as join5 } from "path";
3316
+ import fs10 from "fs";
2799
3317
  function resolveLogDir2(provided) {
2800
3318
  if (!provided) {
2801
- return join4(process.cwd(), "logs");
3319
+ return join5(process.cwd(), "logs");
2802
3320
  }
2803
- return isAbsolute2(provided) ? provided : join4(process.cwd(), provided);
3321
+ return isAbsolute2(provided) ? provided : join5(process.cwd(), provided);
2804
3322
  }
2805
3323
  __name(resolveLogDir2, "resolveLogDir");
2806
3324
  function ensureDir(dir) {
2807
- if (!fs9.existsSync(dir)) {
2808
- fs9.mkdirSync(dir, {
3325
+ if (!fs10.existsSync(dir)) {
3326
+ fs10.mkdirSync(dir, {
2809
3327
  recursive: true
2810
3328
  });
2811
3329
  }
@@ -2823,7 +3341,7 @@ __name(serializeError2, "serializeError");
2823
3341
 
2824
3342
  // src/middlewares/collect-logs/controller.ts
2825
3343
  function collectLogsHandler(logDir, fileName) {
2826
- const filePath = join5(logDir, fileName);
3344
+ const filePath = join6(logDir, fileName);
2827
3345
  ensureDir(logDir);
2828
3346
  return async (req, res) => {
2829
3347
  try {
@@ -2837,7 +3355,7 @@ function collectLogsHandler(logDir, fileName) {
2837
3355
  ...logContent,
2838
3356
  server_time: (/* @__PURE__ */ new Date()).toISOString()
2839
3357
  }) + "\n";
2840
- await fs10.promises.appendFile(filePath, logLine);
3358
+ await fs11.promises.appendFile(filePath, logLine);
2841
3359
  res.json({
2842
3360
  success: true
2843
3361
  });
@@ -2848,7 +3366,7 @@ function collectLogsHandler(logDir, fileName) {
2848
3366
  }
2849
3367
  __name(collectLogsHandler, "collectLogsHandler");
2850
3368
  function collectLogsBatchHandler(logDir, fileName) {
2851
- const filePath = join5(logDir, fileName);
3369
+ const filePath = join6(logDir, fileName);
2852
3370
  ensureDir(logDir);
2853
3371
  return async (req, res) => {
2854
3372
  try {
@@ -2865,7 +3383,7 @@ function collectLogsBatchHandler(logDir, fileName) {
2865
3383
  server_time: (/* @__PURE__ */ new Date()).toISOString()
2866
3384
  }) + "\n");
2867
3385
  }
2868
- await fs10.promises.appendFile(filePath, logLines.join(""));
3386
+ await fs11.promises.appendFile(filePath, logLines.join(""));
2869
3387
  res.json({
2870
3388
  success: true
2871
3389
  });
@@ -2928,7 +3446,7 @@ function isGlobalMiddleware(middleware) {
2928
3446
  }
2929
3447
  __name(isGlobalMiddleware, "isGlobalMiddleware");
2930
3448
  function computeMountPath(basePath, mountPath) {
2931
- const routePath = path6.posix.join(basePath, mountPath);
3449
+ const routePath = path7.posix.join(basePath, mountPath);
2932
3450
  return routePath.startsWith("/") ? routePath : `/${routePath}`;
2933
3451
  }
2934
3452
  __name(computeMountPath, "computeMountPath");
@@ -2936,7 +3454,7 @@ function logMiddlewareRegistration(middleware, fullMountPath) {
2936
3454
  if (middleware.routes && middleware.routes.length > 0) {
2937
3455
  console.log(`[Middleware] Registered: ${middleware.name} at ${fullMountPath}`);
2938
3456
  middleware.routes.forEach((route) => {
2939
- const routePath = route.path === "/" ? fullMountPath : path6.posix.join(fullMountPath, route.path);
3457
+ const routePath = route.path === "/" ? fullMountPath : path7.posix.join(fullMountPath, route.path);
2940
3458
  console.log(` ${route.method} ${routePath} - ${route.description}`);
2941
3459
  });
2942
3460
  } else {