| 156 | |
| 157 | /** Recursively list files under `dir`, capped by size and age. */ |
| 158 | const collectFiles = (dir: string, prefix: string): ZipEntry[] => { |
| 159 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: fs probing of optional directories (crash dumps may not exist) |
| 160 | try { |
| 161 | const cutoff = Date.now() - EXPORT_MAX_AGE_MS; |
| 162 | return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { |
| 163 | const full = join(dir, entry.name); |
| 164 | if (entry.isDirectory()) return collectFiles(full, `${prefix}/${entry.name}`); |
| 165 | if (!entry.isFile()) return []; |
| 166 | const info = statSync(full); |
| 167 | if (info.size > MAX_EXPORT_FILE_BYTES) return []; |
| 168 | if (info.mtimeMs < cutoff) return []; |
| 169 | return [{ name: `${prefix}/${entry.name}`, path: full }]; |
| 170 | }); |
| 171 | } catch { |
| 172 | return []; |
| 173 | } |
| 174 | }; |
| 175 | |
| 176 | const exportStamp = () => |
| 177 | new Date() |