(runsDir: string)
| 26 | } |
| 27 | |
| 28 | export const buildManifest = (runsDir: string): void => { |
| 29 | const runs: ManifestRun[] = []; |
| 30 | const skips: ManifestSkip[] = []; |
| 31 | |
| 32 | for (const target of readdirSync(runsDir, { withFileTypes: true })) { |
| 33 | if (!target.isDirectory() || target.name === "assets") continue; |
| 34 | // Both vitest projects build the manifest concurrently while runs are |
| 35 | // being (re)written — tolerate dirs vanishing mid-scan. |
| 36 | let slugs: Dirent[]; |
| 37 | try { |
| 38 | slugs = readdirSync(join(runsDir, target.name), { withFileTypes: true }); |
| 39 | } catch { |
| 40 | continue; |
| 41 | } |
| 42 | for (const slug of slugs) { |
| 43 | if (!slug.isDirectory()) continue; |
| 44 | const dir = join(runsDir, target.name, slug.name); |
| 45 | const resultPath = join(dir, "result.json"); |
| 46 | if (existsSync(resultPath)) { |
| 47 | try { |
| 48 | const result = JSON.parse(readFileSync(resultPath, "utf8")); |
| 49 | runs.push({ |
| 50 | scenario: result.scenario, |
| 51 | target: target.name, |
| 52 | slug: slug.name, |
| 53 | ok: result.ok, |
| 54 | durationMs: result.durationMs, |
| 55 | endedAt: result.endedAt, |
| 56 | }); |
| 57 | continue; |
| 58 | } catch { |
| 59 | // unreadable result — fall through to the skip marker |
| 60 | } |
| 61 | } |
| 62 | const skipPath = join(dir, "skipped.json"); |
| 63 | if (existsSync(skipPath)) { |
| 64 | try { |
| 65 | const skip = JSON.parse(readFileSync(skipPath, "utf8")); |
| 66 | skips.push({ scenario: skip.scenario, target: target.name, missing: skip.missing }); |
| 67 | } catch { |
| 68 | // ignore |
| 69 | } |
| 70 | } |
| 71 | } |
| 72 | } |
| 73 | |
| 74 | // Write-then-rename so a concurrent reader/writer never sees a torn file. |
| 75 | const tmp = join(runsDir, `.manifest-${process.pid}.tmp`); |
| 76 | writeFileSync(tmp, JSON.stringify({ generatedAt: Date.now(), runs, skips }, null, 1)); |
| 77 | renameSync(tmp, join(runsDir, "manifest.json")); |
| 78 | }; |
no test coverage detected