(limit: number = 50)
| 247 | * Returns up to `limit` entries; empty array if none. |
| 248 | */ |
| 249 | export async function listRunnerLogFiles(limit: number = 50): Promise<LogFileInfo[]> { |
| 250 | try { |
| 251 | const logsDir = configuration.logsDir; |
| 252 | if (!existsSync(logsDir)) { |
| 253 | return []; |
| 254 | } |
| 255 | |
| 256 | const logs = readdirSync(logsDir) |
| 257 | .filter(file => file.endsWith('-runner.log')) |
| 258 | .map(file => { |
| 259 | const fullPath = join(logsDir, file); |
| 260 | const stats = statSync(fullPath); |
| 261 | return { file, path: fullPath, modified: stats.mtime } as LogFileInfo; |
| 262 | }) |
| 263 | .sort((a, b) => b.modified.getTime() - a.modified.getTime()); |
| 264 | |
| 265 | // Prefer the path persisted by the runner if present (return 0th element if present) |
| 266 | try { |
| 267 | const state = await readRunnerState(); |
| 268 | |
| 269 | if (!state) { |
| 270 | return logs; |
| 271 | } |
| 272 | |
| 273 | if (state.runnerLogPath && existsSync(state.runnerLogPath)) { |
| 274 | const stats = statSync(state.runnerLogPath); |
| 275 | const persisted: LogFileInfo = { |
| 276 | file: basename(state.runnerLogPath), |
| 277 | path: state.runnerLogPath, |
| 278 | modified: stats.mtime |
| 279 | }; |
| 280 | const idx = logs.findIndex(l => l.path === persisted.path); |
| 281 | if (idx >= 0) { |
| 282 | const [found] = logs.splice(idx, 1); |
| 283 | logs.unshift(found); |
| 284 | } else { |
| 285 | logs.unshift(persisted); |
| 286 | } |
| 287 | } |
| 288 | } catch { |
| 289 | // Ignore errors reading runner state; fall back to directory listing |
| 290 | } |
| 291 | |
| 292 | return logs.slice(0, Math.max(0, limit)); |
| 293 | } catch { |
| 294 | return []; |
| 295 | } |
| 296 | } |
| 297 | |
| 298 | /** |
| 299 | * Get the most recent runner log file, or null if none exist. |
no test coverage detected