@superdangerous/app-framework 4.9.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.
Files changed (239) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +652 -0
  3. package/dist/api/logsRouter.d.ts +20 -0
  4. package/dist/api/logsRouter.d.ts.map +1 -0
  5. package/dist/api/logsRouter.js +515 -0
  6. package/dist/api/logsRouter.js.map +1 -0
  7. package/dist/cli/dev-server.d.ts +7 -0
  8. package/dist/cli/dev-server.d.ts.map +1 -0
  9. package/dist/cli/dev-server.js +640 -0
  10. package/dist/cli/dev-server.js.map +1 -0
  11. package/dist/cli/index.d.ts +7 -0
  12. package/dist/cli/index.d.ts.map +1 -0
  13. package/dist/cli/index.js +26 -0
  14. package/dist/cli/index.js.map +1 -0
  15. package/dist/core/StandardServer.d.ts +129 -0
  16. package/dist/core/StandardServer.d.ts.map +1 -0
  17. package/dist/core/StandardServer.js +453 -0
  18. package/dist/core/StandardServer.js.map +1 -0
  19. package/dist/core/apiResponse.d.ts +69 -0
  20. package/dist/core/apiResponse.d.ts.map +1 -0
  21. package/dist/core/apiResponse.js +127 -0
  22. package/dist/core/apiResponse.js.map +1 -0
  23. package/dist/core/healthCheck.d.ts +160 -0
  24. package/dist/core/healthCheck.d.ts.map +1 -0
  25. package/dist/core/healthCheck.js +398 -0
  26. package/dist/core/healthCheck.js.map +1 -0
  27. package/dist/core/index.d.ts +40 -0
  28. package/dist/core/index.d.ts.map +1 -0
  29. package/dist/core/index.js +40 -0
  30. package/dist/core/index.js.map +1 -0
  31. package/dist/core/logger.d.ts +117 -0
  32. package/dist/core/logger.d.ts.map +1 -0
  33. package/dist/core/logger.js +826 -0
  34. package/dist/core/logger.js.map +1 -0
  35. package/dist/core/portUtils.d.ts +71 -0
  36. package/dist/core/portUtils.d.ts.map +1 -0
  37. package/dist/core/portUtils.js +240 -0
  38. package/dist/core/portUtils.js.map +1 -0
  39. package/dist/core/storageService.d.ts +119 -0
  40. package/dist/core/storageService.d.ts.map +1 -0
  41. package/dist/core/storageService.js +405 -0
  42. package/dist/core/storageService.js.map +1 -0
  43. package/dist/desktop/bundler.d.ts +40 -0
  44. package/dist/desktop/bundler.d.ts.map +1 -0
  45. package/dist/desktop/bundler.js +176 -0
  46. package/dist/desktop/bundler.js.map +1 -0
  47. package/dist/desktop/index.d.ts +25 -0
  48. package/dist/desktop/index.d.ts.map +1 -0
  49. package/dist/desktop/index.js +15 -0
  50. package/dist/desktop/index.js.map +1 -0
  51. package/dist/desktop/native-modules.d.ts +66 -0
  52. package/dist/desktop/native-modules.d.ts.map +1 -0
  53. package/dist/desktop/native-modules.js +200 -0
  54. package/dist/desktop/native-modules.js.map +1 -0
  55. package/dist/index.d.ts +29 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +39 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/logging/LogCategories.d.ts +87 -0
  60. package/dist/logging/LogCategories.d.ts.map +1 -0
  61. package/dist/logging/LogCategories.js +205 -0
  62. package/dist/logging/LogCategories.js.map +1 -0
  63. package/dist/middleware/aiErrorHandler.d.ts +31 -0
  64. package/dist/middleware/aiErrorHandler.d.ts.map +1 -0
  65. package/dist/middleware/aiErrorHandler.js +181 -0
  66. package/dist/middleware/aiErrorHandler.js.map +1 -0
  67. package/dist/middleware/auth.d.ts +101 -0
  68. package/dist/middleware/auth.d.ts.map +1 -0
  69. package/dist/middleware/auth.js +230 -0
  70. package/dist/middleware/auth.js.map +1 -0
  71. package/dist/middleware/cors.d.ts +56 -0
  72. package/dist/middleware/cors.d.ts.map +1 -0
  73. package/dist/middleware/cors.js +123 -0
  74. package/dist/middleware/cors.js.map +1 -0
  75. package/dist/middleware/errorHandler.d.ts +13 -0
  76. package/dist/middleware/errorHandler.d.ts.map +1 -0
  77. package/dist/middleware/errorHandler.js +85 -0
  78. package/dist/middleware/errorHandler.js.map +1 -0
  79. package/dist/middleware/fileUpload.d.ts +62 -0
  80. package/dist/middleware/fileUpload.d.ts.map +1 -0
  81. package/dist/middleware/fileUpload.js +175 -0
  82. package/dist/middleware/fileUpload.js.map +1 -0
  83. package/dist/middleware/health.d.ts +48 -0
  84. package/dist/middleware/health.d.ts.map +1 -0
  85. package/dist/middleware/health.js +143 -0
  86. package/dist/middleware/health.js.map +1 -0
  87. package/dist/middleware/index.d.ts +20 -0
  88. package/dist/middleware/index.d.ts.map +1 -0
  89. package/dist/middleware/index.js +18 -0
  90. package/dist/middleware/index.js.map +1 -0
  91. package/dist/middleware/openapi.d.ts +64 -0
  92. package/dist/middleware/openapi.d.ts.map +1 -0
  93. package/dist/middleware/openapi.js +258 -0
  94. package/dist/middleware/openapi.js.map +1 -0
  95. package/dist/middleware/requestLogging.d.ts +22 -0
  96. package/dist/middleware/requestLogging.d.ts.map +1 -0
  97. package/dist/middleware/requestLogging.js +61 -0
  98. package/dist/middleware/requestLogging.js.map +1 -0
  99. package/dist/middleware/session.d.ts +84 -0
  100. package/dist/middleware/session.d.ts.map +1 -0
  101. package/dist/middleware/session.js +189 -0
  102. package/dist/middleware/session.js.map +1 -0
  103. package/dist/middleware/validation.d.ts +1337 -0
  104. package/dist/middleware/validation.d.ts.map +1 -0
  105. package/dist/middleware/validation.js +483 -0
  106. package/dist/middleware/validation.js.map +1 -0
  107. package/dist/services/aiService.d.ts +180 -0
  108. package/dist/services/aiService.d.ts.map +1 -0
  109. package/dist/services/aiService.js +547 -0
  110. package/dist/services/aiService.js.map +1 -0
  111. package/dist/services/conversationStorage.d.ts +38 -0
  112. package/dist/services/conversationStorage.d.ts.map +1 -0
  113. package/dist/services/conversationStorage.js +158 -0
  114. package/dist/services/conversationStorage.js.map +1 -0
  115. package/dist/services/crossPlatformBuffer.d.ts +84 -0
  116. package/dist/services/crossPlatformBuffer.d.ts.map +1 -0
  117. package/dist/services/crossPlatformBuffer.js +246 -0
  118. package/dist/services/crossPlatformBuffer.js.map +1 -0
  119. package/dist/services/index.d.ts +17 -0
  120. package/dist/services/index.d.ts.map +1 -0
  121. package/dist/services/index.js +18 -0
  122. package/dist/services/index.js.map +1 -0
  123. package/dist/services/networkService.d.ts +81 -0
  124. package/dist/services/networkService.d.ts.map +1 -0
  125. package/dist/services/networkService.js +268 -0
  126. package/dist/services/networkService.js.map +1 -0
  127. package/dist/services/queueService.d.ts +112 -0
  128. package/dist/services/queueService.d.ts.map +1 -0
  129. package/dist/services/queueService.js +338 -0
  130. package/dist/services/queueService.js.map +1 -0
  131. package/dist/services/settingsService.d.ts +135 -0
  132. package/dist/services/settingsService.d.ts.map +1 -0
  133. package/dist/services/settingsService.js +425 -0
  134. package/dist/services/settingsService.js.map +1 -0
  135. package/dist/services/systemMonitor.d.ts +208 -0
  136. package/dist/services/systemMonitor.d.ts.map +1 -0
  137. package/dist/services/systemMonitor.js +693 -0
  138. package/dist/services/systemMonitor.js.map +1 -0
  139. package/dist/services/updateService.d.ts +78 -0
  140. package/dist/services/updateService.d.ts.map +1 -0
  141. package/dist/services/updateService.js +252 -0
  142. package/dist/services/updateService.js.map +1 -0
  143. package/dist/services/websocketEvents.d.ts +372 -0
  144. package/dist/services/websocketEvents.d.ts.map +1 -0
  145. package/dist/services/websocketEvents.js +338 -0
  146. package/dist/services/websocketEvents.js.map +1 -0
  147. package/dist/services/websocketServer.d.ts +80 -0
  148. package/dist/services/websocketServer.d.ts.map +1 -0
  149. package/dist/services/websocketServer.js +299 -0
  150. package/dist/services/websocketServer.js.map +1 -0
  151. package/dist/settings/SettingsSchema.d.ts +151 -0
  152. package/dist/settings/SettingsSchema.d.ts.map +1 -0
  153. package/dist/settings/SettingsSchema.js +424 -0
  154. package/dist/settings/SettingsSchema.js.map +1 -0
  155. package/dist/testing/TestServer.d.ts +69 -0
  156. package/dist/testing/TestServer.d.ts.map +1 -0
  157. package/dist/testing/TestServer.js +250 -0
  158. package/dist/testing/TestServer.js.map +1 -0
  159. package/dist/types/index.d.ts +137 -0
  160. package/dist/types/index.d.ts.map +1 -0
  161. package/dist/types/index.js +5 -0
  162. package/dist/types/index.js.map +1 -0
  163. package/dist/utils/appPaths.d.ts +74 -0
  164. package/dist/utils/appPaths.d.ts.map +1 -0
  165. package/dist/utils/appPaths.js +162 -0
  166. package/dist/utils/appPaths.js.map +1 -0
  167. package/dist/utils/fs-utils.d.ts +50 -0
  168. package/dist/utils/fs-utils.d.ts.map +1 -0
  169. package/dist/utils/fs-utils.js +114 -0
  170. package/dist/utils/fs-utils.js.map +1 -0
  171. package/dist/utils/index.d.ts +12 -0
  172. package/dist/utils/index.d.ts.map +1 -0
  173. package/dist/utils/index.js +10 -0
  174. package/dist/utils/index.js.map +1 -0
  175. package/dist/utils/standardConfig.d.ts +61 -0
  176. package/dist/utils/standardConfig.d.ts.map +1 -0
  177. package/dist/utils/standardConfig.js +109 -0
  178. package/dist/utils/standardConfig.js.map +1 -0
  179. package/dist/utils/startupBanner.d.ts +34 -0
  180. package/dist/utils/startupBanner.d.ts.map +1 -0
  181. package/dist/utils/startupBanner.js +169 -0
  182. package/dist/utils/startupBanner.js.map +1 -0
  183. package/dist/utils/startupLogger.d.ts +45 -0
  184. package/dist/utils/startupLogger.d.ts.map +1 -0
  185. package/dist/utils/startupLogger.js +200 -0
  186. package/dist/utils/startupLogger.js.map +1 -0
  187. package/package.json +151 -0
  188. package/src/api/logsRouter.ts +600 -0
  189. package/src/cli/dev-server.ts +803 -0
  190. package/src/cli/index.ts +31 -0
  191. package/src/core/StandardServer.ts +587 -0
  192. package/src/core/apiResponse.ts +202 -0
  193. package/src/core/healthCheck.ts +565 -0
  194. package/src/core/index.ts +80 -0
  195. package/src/core/logger.ts +1092 -0
  196. package/src/core/portUtils.ts +319 -0
  197. package/src/core/storageService.ts +595 -0
  198. package/src/desktop/bundler.ts +271 -0
  199. package/src/desktop/index.ts +18 -0
  200. package/src/desktop/native-modules.ts +289 -0
  201. package/src/index.ts +142 -0
  202. package/src/logging/LogCategories.ts +302 -0
  203. package/src/middleware/aiErrorHandler.ts +278 -0
  204. package/src/middleware/auth.ts +329 -0
  205. package/src/middleware/cors.ts +187 -0
  206. package/src/middleware/errorHandler.ts +103 -0
  207. package/src/middleware/fileUpload.ts +252 -0
  208. package/src/middleware/health.ts +206 -0
  209. package/src/middleware/index.ts +71 -0
  210. package/src/middleware/openapi.ts +305 -0
  211. package/src/middleware/requestLogging.ts +92 -0
  212. package/src/middleware/session.ts +238 -0
  213. package/src/middleware/validation.ts +603 -0
  214. package/src/services/aiService.ts +789 -0
  215. package/src/services/conversationStorage.ts +232 -0
  216. package/src/services/crossPlatformBuffer.ts +341 -0
  217. package/src/services/index.ts +47 -0
  218. package/src/services/networkService.ts +351 -0
  219. package/src/services/queueService.ts +446 -0
  220. package/src/services/settingsService.ts +549 -0
  221. package/src/services/systemMonitor.ts +936 -0
  222. package/src/services/updateService.ts +334 -0
  223. package/src/services/websocketEvents.ts +409 -0
  224. package/src/services/websocketServer.ts +394 -0
  225. package/src/settings/SettingsSchema.ts +664 -0
  226. package/src/testing/TestServer.ts +312 -0
  227. package/src/types/index.ts +154 -0
  228. package/src/utils/appPaths.ts +196 -0
  229. package/src/utils/fs-utils.ts +130 -0
  230. package/src/utils/index.ts +15 -0
  231. package/src/utils/standardConfig.ts +178 -0
  232. package/src/utils/startupBanner.ts +287 -0
  233. package/src/utils/startupLogger.ts +268 -0
  234. package/ui/dist/index.d.mts +1221 -0
  235. package/ui/dist/index.d.ts +1221 -0
  236. package/ui/dist/index.js +73 -0
  237. package/ui/dist/index.js.map +1 -0
  238. package/ui/dist/index.mjs +73 -0
  239. package/ui/dist/index.mjs.map +1 -0
@@ -0,0 +1,826 @@
1
+ /**
2
+ * Logger Service
3
+ * Provides centralized logging with rotation, compression, and management
4
+ */
5
+ import winston, { format, transports } from "winston";
6
+ import DailyRotateFile from "winston-daily-rotate-file";
7
+ import path from "path";
8
+ import fs from "fs/promises";
9
+ import { createReadStream, createWriteStream, existsSync, } from "fs";
10
+ import { createGzip } from "zlib";
11
+ import { pipeline } from "stream/promises";
12
+ // Use process.cwd() based paths for better compatibility
13
+ const getLogsDir = () => path.join(process.cwd(), "data", "logs");
14
+ // Log levels with colors
15
+ const logLevels = {
16
+ levels: {
17
+ error: 0,
18
+ warn: 1,
19
+ info: 2,
20
+ http: 3,
21
+ verbose: 4,
22
+ debug: 5,
23
+ silly: 6,
24
+ },
25
+ colors: {
26
+ error: "red",
27
+ warn: "yellow",
28
+ info: "green",
29
+ http: "magenta",
30
+ verbose: "cyan",
31
+ debug: "blue",
32
+ silly: "gray",
33
+ },
34
+ };
35
+ // Apply colors to winston
36
+ winston.addColors(logLevels.colors);
37
+ class Logger {
38
+ logsDir;
39
+ archiveDir;
40
+ loggers;
41
+ initialized;
42
+ messageQueue;
43
+ initPromise;
44
+ constructor() {
45
+ this.logsDir = getLogsDir(); // Now defaults to data/logs
46
+ this.archiveDir = path.join(this.logsDir, "archive");
47
+ this.loggers = new Map();
48
+ this.initialized = false;
49
+ this.messageQueue = [];
50
+ }
51
+ async initialize(options) {
52
+ if (this.initialized)
53
+ return;
54
+ // If already initializing, wait for it
55
+ if (this.initPromise) {
56
+ return this.initPromise;
57
+ }
58
+ // Start initialization
59
+ this.initPromise = this._doInitialize(options);
60
+ return this.initPromise;
61
+ }
62
+ async _doInitialize(options) {
63
+ // Apply options if provided
64
+ if (options?.logsDir) {
65
+ this.logsDir = options.logsDir;
66
+ }
67
+ // Create log directories
68
+ await this.ensureDirectories();
69
+ // Clean up old root-level log files and subdirectories
70
+ await this.cleanupOldLogs();
71
+ await this.cleanupSubdirectories();
72
+ // Create default logger with options
73
+ this.createLogger(options?.appName || "system", {
74
+ level: options?.logLevel || "info",
75
+ console: options?.consoleOutput !== false,
76
+ file: options?.fileOutput !== false,
77
+ });
78
+ this.initialized = true;
79
+ // Process any queued messages after a small delay to ensure transports are ready
80
+ setTimeout(() => this.processMessageQueue(), 100);
81
+ }
82
+ processMessageQueue() {
83
+ if (this.messageQueue.length === 0)
84
+ return;
85
+ const messages = [...this.messageQueue];
86
+ this.messageQueue = [];
87
+ for (const { name, level, args } of messages) {
88
+ const logger = this.createLogger(name);
89
+ if (logger) {
90
+ // Use a type-safe way to call the logger method
91
+ switch (level) {
92
+ case "info":
93
+ logger.info.apply(logger, args);
94
+ break;
95
+ case "error":
96
+ logger.error.apply(logger, args);
97
+ break;
98
+ case "warn":
99
+ logger.warn.apply(logger, args);
100
+ break;
101
+ case "debug":
102
+ logger.debug.apply(logger, args);
103
+ break;
104
+ case "verbose":
105
+ logger.verbose.apply(logger, args);
106
+ break;
107
+ case "http":
108
+ logger.http.apply(logger, args);
109
+ break;
110
+ case "silly":
111
+ logger.silly.apply(logger, args);
112
+ break;
113
+ }
114
+ }
115
+ }
116
+ }
117
+ isInitialized() {
118
+ return this.initialized;
119
+ }
120
+ /**
121
+ * Get recent log entries from memory or file
122
+ */
123
+ async getRecentLogs(limit = 100, level = "all") {
124
+ const logsDir = this.logsDir;
125
+ try {
126
+ // Find the most recent log file
127
+ const files = await fs.readdir(logsDir);
128
+ const logFiles = files.filter((f) => f.startsWith("app-") && f.endsWith(".log"));
129
+ if (logFiles.length === 0) {
130
+ // Fallback to app.log if no dated files exist
131
+ const fallbackFile = path.join(logsDir, "app.log");
132
+ if (!existsSync(fallbackFile)) {
133
+ return [];
134
+ }
135
+ logFiles.push("app.log");
136
+ }
137
+ // Sort to get most recent
138
+ logFiles.sort().reverse();
139
+ const logFile = path.join(logsDir, logFiles[0]);
140
+ const content = await fs.readFile(logFile, "utf-8");
141
+ const lines = content.split("\n").filter((line) => line.trim());
142
+ // Parse log lines into structured format
143
+ const logs = lines.map((line) => {
144
+ // Try to parse JSON format first
145
+ try {
146
+ const jsonLog = JSON.parse(line);
147
+ return {
148
+ timestamp: jsonLog.timestamp || new Date().toISOString(),
149
+ level: (jsonLog.level || "info").toLowerCase(),
150
+ source: jsonLog.source || jsonLog.service || undefined,
151
+ message: jsonLog.message || jsonLog.msg || line,
152
+ };
153
+ }
154
+ catch {
155
+ // Try to parse standard text format: [timestamp] [level] [source] message
156
+ const match = line.match(/\[([\d-T:.Z]+)\]\s*\[(\w+)\]\s*(?:\[([^\]]+)\])?\s*(.*)/);
157
+ if (match) {
158
+ return {
159
+ timestamp: match[1],
160
+ level: match[2].toLowerCase(),
161
+ source: match[3] || undefined,
162
+ message: match[4],
163
+ };
164
+ }
165
+ // Fallback for non-standard format
166
+ return {
167
+ timestamp: new Date().toISOString(),
168
+ level: "info",
169
+ message: line,
170
+ };
171
+ }
172
+ });
173
+ // Filter by level if specified
174
+ const filtered = level === "all"
175
+ ? logs
176
+ : logs.filter((log) => log.level === level.toLowerCase());
177
+ // Return most recent entries, newest first
178
+ return filtered.slice(-limit).reverse();
179
+ }
180
+ catch (_error) {
181
+ console.error("Failed to read logs:", _error);
182
+ return [];
183
+ }
184
+ }
185
+ /**
186
+ * Clear all log files
187
+ */
188
+ async clearLogs() {
189
+ const logsDir = this.logsDir;
190
+ try {
191
+ if (existsSync(logsDir)) {
192
+ const files = await fs.readdir(logsDir);
193
+ for (const file of files) {
194
+ if (file.endsWith(".log") || file.endsWith(".txt")) {
195
+ await fs.unlink(path.join(logsDir, file));
196
+ }
197
+ }
198
+ }
199
+ }
200
+ catch (_error) {
201
+ console.error("Failed to clear logs:", _error);
202
+ throw _error;
203
+ }
204
+ }
205
+ child(name) {
206
+ // Return a smart proxy that handles both pre and post initialization
207
+ // eslint-disable-next-line @typescript-eslint/no-this-alias
208
+ const self = this;
209
+ const logMethod = (level) => {
210
+ return (...args) => {
211
+ if (self.initialized) {
212
+ // Use the real logger
213
+ const logger = self.createLogger(name);
214
+ if (logger) {
215
+ const method = logger[level];
216
+ if (typeof method === "function") {
217
+ method.apply(logger, args);
218
+ }
219
+ }
220
+ }
221
+ else {
222
+ // Log to console immediately
223
+ const prefix = level === "error"
224
+ ? `[${name} ERROR]`
225
+ : level === "warn"
226
+ ? `[${name} WARN]`
227
+ : level === "debug"
228
+ ? `[${name} DEBUG]`
229
+ : `[${name}]`;
230
+ const consoleFn = level === "error"
231
+ ? console.error
232
+ : level === "warn"
233
+ ? console.warn
234
+ : level === "debug"
235
+ ? console.debug
236
+ : console.log;
237
+ consoleFn(prefix, ...args);
238
+ // Queue for file logging
239
+ self.messageQueue.push({ name, level, args });
240
+ // Trigger initialization if not already started
241
+ if (!self.initPromise) {
242
+ self
243
+ .initialize({
244
+ appName: "app",
245
+ logLevel: process.env.LOG_LEVEL || "info",
246
+ consoleOutput: true,
247
+ fileOutput: true,
248
+ logsDir: path.join(process.cwd(), "data", "logs"),
249
+ })
250
+ .catch((err) => {
251
+ console.error("[Logger] Failed to initialize:", err);
252
+ });
253
+ }
254
+ }
255
+ };
256
+ };
257
+ return {
258
+ info: logMethod("info"),
259
+ error: logMethod("error"),
260
+ warn: logMethod("warn"),
261
+ debug: logMethod("debug"),
262
+ verbose: logMethod("verbose"),
263
+ http: logMethod("http"),
264
+ silly: logMethod("silly"),
265
+ logRequest: (req, res, duration) => {
266
+ if (self.initialized) {
267
+ const real = self.createLogger(name);
268
+ real?.logRequest?.(req, res, duration);
269
+ return;
270
+ }
271
+ logMethod("info")(`Request ${req.method} ${req.url} completed in ${duration}ms`);
272
+ },
273
+ logError: (error, context = {}) => {
274
+ if (self.initialized) {
275
+ const real = self.createLogger(name);
276
+ real?.logError?.(error, context);
277
+ return;
278
+ }
279
+ logMethod("error")(error, context);
280
+ },
281
+ compactLogs: (daysToKeep) => self.compactLogs(daysToKeep),
282
+ getLogStats: () => self.getLogStats(),
283
+ };
284
+ }
285
+ async ensureDirectories() {
286
+ try {
287
+ // Create only the main logs directory and archive subdirectory
288
+ await fs.mkdir(this.logsDir, { recursive: true });
289
+ await fs.mkdir(this.archiveDir, { recursive: true });
290
+ }
291
+ catch (_error) {
292
+ console.error("Failed to create log directories:", _error);
293
+ }
294
+ }
295
+ /**
296
+ * Clean up subdirectories to maintain flat structure
297
+ */
298
+ async cleanupSubdirectories() {
299
+ try {
300
+ const items = await fs.readdir(this.logsDir);
301
+ for (const item of items) {
302
+ if (item === "archive")
303
+ continue; // Keep archive directory
304
+ const itemPath = path.join(this.logsDir, item);
305
+ const stat = await fs.stat(itemPath);
306
+ if (stat.isDirectory()) {
307
+ // Remove empty subdirectories
308
+ try {
309
+ const contents = await fs.readdir(itemPath);
310
+ if (contents.length === 0) {
311
+ await fs.rmdir(itemPath);
312
+ console.log(`Removed empty log subdirectory: ${item}`);
313
+ }
314
+ }
315
+ catch (_err) {
316
+ console.warn(`Could not clean up subdirectory ${item}:`, _err);
317
+ }
318
+ }
319
+ }
320
+ }
321
+ catch (_error) {
322
+ // Ignore errors during cleanup
323
+ }
324
+ }
325
+ async cleanupOldLogs() {
326
+ const rootDir = process.cwd();
327
+ const topLevelLogsDir = path.join(rootDir, "logs");
328
+ // Check if there's a top-level logs directory
329
+ if (existsSync(topLevelLogsDir)) {
330
+ try {
331
+ // Move any log files to data/logs
332
+ const files = await fs.readdir(topLevelLogsDir);
333
+ for (const file of files) {
334
+ if (file.endsWith(".log") || file.endsWith(".txt")) {
335
+ const oldPath = path.join(topLevelLogsDir, file);
336
+ const newPath = path.join(this.logsDir, file);
337
+ try {
338
+ await fs.rename(oldPath, newPath);
339
+ console.log(`Moved log file from /logs to /data/logs: ${file}`);
340
+ }
341
+ catch (_err) {
342
+ // File might not exist or already moved
343
+ }
344
+ }
345
+ }
346
+ // Remove top-level logs directory if empty
347
+ const remainingFiles = await fs.readdir(topLevelLogsDir);
348
+ const hasOnlyDsStore = remainingFiles.length === 1 && remainingFiles[0] === ".DS_Store";
349
+ if (remainingFiles.length === 0 || hasOnlyDsStore) {
350
+ if (hasOnlyDsStore) {
351
+ await fs
352
+ .unlink(path.join(topLevelLogsDir, ".DS_Store"))
353
+ .catch(() => { });
354
+ }
355
+ await fs.rmdir(topLevelLogsDir);
356
+ console.log("Removed empty top-level logs directory");
357
+ }
358
+ }
359
+ catch (_error) {
360
+ // Directory might not exist or already cleaned
361
+ }
362
+ }
363
+ }
364
+ createLogger(category = "general", options = {}) {
365
+ const loggerKey = `${category}-${JSON.stringify(options)}`;
366
+ if (this.loggers.has(loggerKey)) {
367
+ return this.loggers.get(loggerKey);
368
+ }
369
+ // Use flat structure - all logs in data/logs directly
370
+ const logDir = this.logsDir;
371
+ const level = options.level || process.env.LOG_LEVEL || "info";
372
+ // Console format with colors - no JSON output
373
+ const consoleFormat = format.combine(format.timestamp({ format: "HH:mm:ss.SSS" }), format.colorize(), format.printf(({ timestamp, level, message, source, ...meta }) => {
374
+ // Extract commonly used metadata
375
+ const sourceStr = source ? ` [${source}]` : "";
376
+ // For errors, show the error message inline
377
+ if (meta.error && typeof meta.error === "string") {
378
+ return `[${timestamp}] ${level}:${sourceStr} ${message}: ${meta.error}`;
379
+ }
380
+ // For other metadata, format key fields inline (no JSON)
381
+ const importantMeta = [];
382
+ // Handle all common metadata fields
383
+ for (const [key, value] of Object.entries(meta)) {
384
+ // Skip internal winston fields and stack traces
385
+ if (key === "stack" ||
386
+ key === "timestamp" ||
387
+ key === "level" ||
388
+ key === "message")
389
+ continue;
390
+ // Format the value appropriately
391
+ if (value !== undefined && value !== null) {
392
+ importantMeta.push(`${key}=${value}`);
393
+ }
394
+ }
395
+ const metaStr = importantMeta.length > 0 ? ` (${importantMeta.join(", ")})` : "";
396
+ return `[${timestamp}] ${level}:${sourceStr} ${message}${metaStr}`;
397
+ }));
398
+ // File format (structured JSON for machine parsing)
399
+ // Strip ANSI codes from messages before writing to file
400
+ const ansiPattern = new RegExp("\\x1b\\[[0-9;]*m", "g"); // eslint-disable-line no-control-regex
401
+ const stripAnsi = format((info) => {
402
+ if (info.message && typeof info.message === "string") {
403
+ info.message = info.message.replace(ansiPattern, "");
404
+ }
405
+ if (info.source && typeof info.source === "string") {
406
+ info.source = info.source.replace(ansiPattern, "");
407
+ }
408
+ return info;
409
+ });
410
+ const fileFormat = format.combine(stripAnsi(), format.timestamp({ format: "YYYY-MM-DD HH:mm:ss.SSS" }), format.errors({ stack: true }), format.json());
411
+ // Create transports
412
+ const logTransports = [];
413
+ // Console transport
414
+ if (options.console !== false &&
415
+ (process.env.NODE_ENV !== "production" ||
416
+ process.env.LOG_TO_CONSOLE === "true")) {
417
+ logTransports.push(new transports.Console({
418
+ format: consoleFormat,
419
+ level: level,
420
+ }));
421
+ }
422
+ // File transports - flat structure in data/logs (skip in tests)
423
+ if (options.file !== false && process.env.NODE_ENV !== "test") {
424
+ // Daily rotating file transport for all logs
425
+ logTransports.push(new DailyRotateFile({
426
+ filename: path.join(logDir, "app-%DATE%.log"),
427
+ datePattern: "YYYY-MM-DD",
428
+ zippedArchive: options.compress !== false,
429
+ maxSize: options.maxSize || "20m",
430
+ maxFiles: options.maxFiles || "14d",
431
+ format: fileFormat,
432
+ level: level,
433
+ auditFile: path.join(logDir, ".app-audit.json"),
434
+ }));
435
+ // Separate error log file
436
+ logTransports.push(new DailyRotateFile({
437
+ filename: path.join(logDir, "error-%DATE%.log"),
438
+ datePattern: "YYYY-MM-DD",
439
+ zippedArchive: options.compress !== false,
440
+ maxSize: options.maxSize || "20m",
441
+ maxFiles: "30d",
442
+ format: fileFormat,
443
+ level: "error",
444
+ auditFile: path.join(logDir, ".error-audit.json"),
445
+ }));
446
+ }
447
+ // Create the logger
448
+ const logger = winston.createLogger({
449
+ levels: logLevels.levels,
450
+ transports: logTransports.filter(Boolean),
451
+ exitOnError: false,
452
+ defaultMeta: { source: category },
453
+ });
454
+ // Add convenience methods
455
+ logger.logRequest = (req, res, duration) => {
456
+ const logData = {
457
+ method: req.method,
458
+ url: req.url,
459
+ status: res.statusCode,
460
+ duration: `${duration}ms`,
461
+ ip: req.ip,
462
+ userAgent: req.get("user-agent"),
463
+ };
464
+ if (res.statusCode >= 400) {
465
+ logger.error("Request failed", logData);
466
+ }
467
+ else {
468
+ logger.http("Request completed", logData);
469
+ }
470
+ };
471
+ logger.logSimulator = (simulatorId, action, data = {}) => {
472
+ logger.info(`Simulator ${action}`, {
473
+ simulatorId,
474
+ action,
475
+ ...data,
476
+ });
477
+ };
478
+ logger.logError = (error, context = {}) => {
479
+ logger.error(error.message, {
480
+ stack: error.stack,
481
+ name: error.name,
482
+ ...context,
483
+ });
484
+ };
485
+ this.loggers.set(loggerKey, logger);
486
+ return logger;
487
+ }
488
+ async getLogStats() {
489
+ const stats = {
490
+ totalSize: 0,
491
+ fileCount: 0,
492
+ files: [],
493
+ oldestLog: undefined,
494
+ newestLog: undefined,
495
+ };
496
+ try {
497
+ const files = await fs.readdir(this.logsDir);
498
+ for (const file of files) {
499
+ // Skip directories and non-log files
500
+ if (file === "archive" || file.startsWith("."))
501
+ continue;
502
+ const filePath = path.join(this.logsDir, file);
503
+ const stat = await fs.stat(filePath);
504
+ if (stat.isFile() && (file.endsWith(".log") || file.endsWith(".gz"))) {
505
+ const fileInfo = {
506
+ name: file,
507
+ size: stat.size,
508
+ modified: stat.mtime,
509
+ compressed: file.endsWith(".gz"),
510
+ };
511
+ stats.files.push(fileInfo);
512
+ stats.totalSize += stat.size;
513
+ stats.fileCount++;
514
+ // Track oldest and newest
515
+ if (!stats.oldestLog || stat.mtime < stats.oldestLog) {
516
+ stats.oldestLog = stat.mtime;
517
+ }
518
+ if (!stats.newestLog || stat.mtime > stats.newestLog) {
519
+ stats.newestLog = stat.mtime;
520
+ }
521
+ }
522
+ }
523
+ // Sort files by date (newest first)
524
+ stats.files.sort((a, b) => b.modified.getTime() - a.modified.getTime());
525
+ }
526
+ catch (_error) {
527
+ console.error("Failed to get log stats:", _error);
528
+ }
529
+ return stats;
530
+ }
531
+ async archiveLogs(olderThanDays = 7) {
532
+ const cutoffDate = new Date();
533
+ cutoffDate.setDate(cutoffDate.getDate() - olderThanDays);
534
+ try {
535
+ const files = await fs.readdir(this.logsDir);
536
+ for (const file of files) {
537
+ if (file === "archive" || file.startsWith("."))
538
+ continue;
539
+ const filePath = path.join(this.logsDir, file);
540
+ const stat = await fs.stat(filePath);
541
+ if (stat.isFile() && stat.mtime < cutoffDate) {
542
+ const archivePath = path.join(this.archiveDir, file);
543
+ if (file.endsWith(".log")) {
544
+ // Compress before archiving
545
+ const gzipPath = `${archivePath}.gz`;
546
+ const source = createReadStream(filePath);
547
+ const destination = createWriteStream(gzipPath);
548
+ const gzip = createGzip();
549
+ await pipeline(source, gzip, destination);
550
+ await fs.unlink(filePath);
551
+ }
552
+ else {
553
+ // Already compressed, just move
554
+ await fs.rename(filePath, archivePath);
555
+ }
556
+ }
557
+ }
558
+ }
559
+ catch (_error) {
560
+ console.error("Failed to archive logs:", _error);
561
+ }
562
+ }
563
+ async downloadLogFile(filename) {
564
+ const safeName = path.basename(filename); // Prevent path traversal
565
+ const logPath = path.join(this.logsDir, safeName);
566
+ const archivePath = path.join(this.archiveDir, safeName);
567
+ // Check in main logs directory first
568
+ if (existsSync(logPath)) {
569
+ return createReadStream(logPath);
570
+ }
571
+ // Check in archive
572
+ if (existsSync(archivePath)) {
573
+ return createReadStream(archivePath);
574
+ }
575
+ return null;
576
+ }
577
+ /**
578
+ * Compact logs by archiving old files
579
+ */
580
+ async compactLogs(daysToKeep = 7) {
581
+ await this.ensureDirectories();
582
+ const cutoffDate = new Date();
583
+ cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
584
+ let archivedCount = 0;
585
+ let totalSize = 0;
586
+ try {
587
+ const files = await fs.readdir(this.logsDir);
588
+ for (const file of files) {
589
+ if (!file.endsWith(".log"))
590
+ continue;
591
+ const filePath = path.join(this.logsDir, file);
592
+ const stats = await fs.stat(filePath);
593
+ if (stats.mtime < cutoffDate) {
594
+ // Archive the file
595
+ const archivePath = path.join(this.archiveDir, `${file}.gz`);
596
+ await pipeline(createReadStream(filePath), createGzip({ level: 9 }), createWriteStream(archivePath));
597
+ await fs.unlink(filePath);
598
+ archivedCount++;
599
+ totalSize += stats.size;
600
+ }
601
+ }
602
+ // Get updated stats
603
+ const updatedStats = await this.getLogStats();
604
+ const appLogger = this.createLogger("system");
605
+ appLogger.info(`Compacted ${archivedCount} log files (${totalSize} bytes)`);
606
+ return updatedStats;
607
+ }
608
+ catch (_error) {
609
+ const appLogger = this.createLogger("system");
610
+ appLogger.error("Failed to compact logs:", _error);
611
+ throw _error;
612
+ }
613
+ }
614
+ /**
615
+ * Clean up zero-length files and corrupted archives
616
+ */
617
+ async cleanupZeroFiles() {
618
+ await this.ensureDirectories();
619
+ let removed = 0;
620
+ const directories = [this.logsDir, this.archiveDir];
621
+ for (const dir of directories) {
622
+ try {
623
+ const files = await fs.readdir(dir);
624
+ for (const file of files) {
625
+ if (file.endsWith(".gz") || file.endsWith(".log")) {
626
+ const filePath = path.join(dir, file);
627
+ const stats = await fs.stat(filePath);
628
+ if (stats.size === 0) {
629
+ await fs.unlink(filePath);
630
+ removed++;
631
+ }
632
+ }
633
+ }
634
+ }
635
+ catch (_error) {
636
+ const appLogger = this.createLogger("system");
637
+ appLogger.warn(`Failed to clean directory ${dir}:`, _error);
638
+ }
639
+ }
640
+ const appLogger = this.createLogger("system");
641
+ appLogger.info(`Cleaned up ${removed} zero-length files`);
642
+ return { removed };
643
+ }
644
+ /**
645
+ * Purge all logs (dangerous operation)
646
+ */
647
+ async purgeAllLogs() {
648
+ const removeRecursive = async (dirPath) => {
649
+ if (!existsSync(dirPath))
650
+ return;
651
+ const entries = await fs.readdir(dirPath);
652
+ for (const entry of entries) {
653
+ const fullPath = path.join(dirPath, entry);
654
+ const stats = await fs.stat(fullPath);
655
+ if (stats.isDirectory()) {
656
+ await removeRecursive(fullPath);
657
+ await fs.rmdir(fullPath);
658
+ }
659
+ else {
660
+ await fs.unlink(fullPath);
661
+ }
662
+ }
663
+ };
664
+ await removeRecursive(this.logsDir);
665
+ // Recreate directories
666
+ await this.ensureDirectories();
667
+ const appLogger = this.createLogger("system");
668
+ appLogger.warn("All logs have been purged");
669
+ }
670
+ /**
671
+ * Export logs in various formats
672
+ */
673
+ async exportLogs(options = {}) {
674
+ const { level = "all", format = "txt", startDate, endDate } = options;
675
+ const currentLogFile = path.join(this.logsDir, `app-${new Date().toISOString().split("T")[0]}.log`);
676
+ if (!existsSync(currentLogFile)) {
677
+ throw new Error("No logs found for export");
678
+ }
679
+ const content = await fs.readFile(currentLogFile, "utf8");
680
+ const lines = content.split("\n").filter((line) => line.trim());
681
+ // Filter by level if specified
682
+ let filteredLines = lines;
683
+ if (level !== "all") {
684
+ const levelUpper = level.toUpperCase();
685
+ filteredLines = lines.filter((line) => line.includes(`[${levelUpper}]`) || line.includes(` ${levelUpper} `));
686
+ }
687
+ // Filter by date range if specified
688
+ if (startDate || endDate) {
689
+ filteredLines = filteredLines.filter((line) => {
690
+ const timestampMatch = line.match(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/);
691
+ if (!timestampMatch)
692
+ return true;
693
+ const lineDate = new Date(timestampMatch[0]);
694
+ if (startDate && lineDate < startDate)
695
+ return false;
696
+ if (endDate && lineDate > endDate)
697
+ return false;
698
+ return true;
699
+ });
700
+ }
701
+ // Format output based on requested format
702
+ switch (format) {
703
+ case "json": {
704
+ const entries = filteredLines.map((line) => {
705
+ try {
706
+ // Try to parse as JSON first
707
+ if (line.trim().startsWith("{")) {
708
+ return JSON.parse(line);
709
+ }
710
+ // Parse text format
711
+ const match = line.match(/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+\[(\w+)\]\s+(?:\[([^\]]+)\]\s+)?(.*)$/);
712
+ if (match) {
713
+ const [, timestamp, level, category, message] = match;
714
+ return {
715
+ timestamp,
716
+ level: level.toLowerCase(),
717
+ category: category || "general",
718
+ message,
719
+ };
720
+ }
721
+ return { message: line };
722
+ }
723
+ catch {
724
+ return { message: line };
725
+ }
726
+ });
727
+ return JSON.stringify(entries, null, 2);
728
+ }
729
+ case "csv": {
730
+ const csv = ["Timestamp,Level,Category,Message"];
731
+ filteredLines.forEach((line) => {
732
+ const match = line.match(/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+\[(\w+)\]\s+(?:\[([^\]]+)\]\s+)?(.*)$/);
733
+ if (match) {
734
+ const [, timestamp, level, category, message] = match;
735
+ csv.push(`"${timestamp}","${level}","${category || ""}","${message.replace(/"/g, '""')}"`);
736
+ }
737
+ });
738
+ return csv.join("\n");
739
+ }
740
+ case "txt":
741
+ default:
742
+ return filteredLines.join("\n");
743
+ }
744
+ }
745
+ /**
746
+ * Get list of all log files including archives
747
+ */
748
+ async getAllLogFiles() {
749
+ const files = [];
750
+ // Get files from logs directory
751
+ if (existsSync(this.logsDir)) {
752
+ const logFiles = await fs.readdir(this.logsDir);
753
+ for (const file of logFiles) {
754
+ if (file.startsWith("app-") || file.startsWith("error-")) {
755
+ const fullPath = path.join(this.logsDir, file);
756
+ const stats = await fs.stat(fullPath);
757
+ files.push({
758
+ name: file,
759
+ size: stats.size,
760
+ modified: stats.mtime,
761
+ compressed: false,
762
+ });
763
+ }
764
+ }
765
+ }
766
+ // Get files from archive directory
767
+ if (existsSync(this.archiveDir)) {
768
+ const archiveFiles = await fs.readdir(this.archiveDir);
769
+ for (const file of archiveFiles) {
770
+ const fullPath = path.join(this.archiveDir, file);
771
+ const stats = await fs.stat(fullPath);
772
+ files.push({
773
+ name: `archive/${file}`,
774
+ size: stats.size,
775
+ modified: stats.mtime,
776
+ compressed: file.endsWith(".gz"),
777
+ });
778
+ }
779
+ }
780
+ // Sort by modified date (newest first)
781
+ files.sort((a, b) => b.modified.getTime() - a.modified.getTime());
782
+ return files;
783
+ }
784
+ /**
785
+ * Get all logger instances (for rotation)
786
+ */
787
+ getLoggers() {
788
+ return this.loggers;
789
+ }
790
+ }
791
+ // Export singleton instance and functions
792
+ const logger = new Logger();
793
+ export function createLogger(name) {
794
+ if (!name) {
795
+ return logger.child("App");
796
+ }
797
+ return logger.child(name);
798
+ }
799
+ export function initializeLogger(options) {
800
+ return logger.initialize(options);
801
+ }
802
+ export function configureLogging(options) {
803
+ // For backward compatibility - just trigger initialization
804
+ logger.initialize(options).catch((err) => {
805
+ console.error("Failed to configure logging:", err);
806
+ });
807
+ }
808
+ export async function getLogStats() {
809
+ return logger.getLogStats();
810
+ }
811
+ export async function archiveLogs(olderThanDays) {
812
+ return logger.archiveLogs(olderThanDays);
813
+ }
814
+ export async function downloadLogFile(filename) {
815
+ return logger.downloadLogFile(filename);
816
+ }
817
+ export async function getRecentLogs(limit, level) {
818
+ return logger.getRecentLogs(limit, level);
819
+ }
820
+ export async function clearLogs() {
821
+ return logger.clearLogs();
822
+ }
823
+ // Export both as default and named export
824
+ export default logger;
825
+ export { logger as getLogger };
826
+ //# sourceMappingURL=logger.js.map