All files / src/aggregator/manifest reader.ts

100% Statements 11/11
100% Branches 2/2
100% Functions 2/2
100% Lines 11/11

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71                                                                                    617x 617x 2x   615x 615x 615x 615x   3x                         3x 3x   2x      
// SPDX-FileCopyrightText: 2024-2026 Hack23 AB
// SPDX-License-Identifier: Apache-2.0
 
/**
 * @module Aggregator/Manifest/Reader
 * @description Filesystem-aware manifest reader. Encapsulates the
 * "read manifest.json next to a run directory" pattern so callers don't
 * need to remember the JSON parse + try/catch + path-join recipe in three
 * different files.
 */
 
import fs from 'fs';
import path from 'path';
import type { Manifest } from './types.js';
 
/**
 * Result of {@link readManifest}. The `path` is always set; `manifest`
 * carries the parsed JSON when the file exists and is valid, otherwise
 * `null` (silent failure — the aggregator falls back to `articleType:
 * 'unknown'` and discovery-based file lists in that case, matching the
 * pre-refactor behaviour).
 */
export interface ReadManifestResult {
  /** Absolute path of the manifest file (whether or not it exists). */
  readonly path: string;
  /** Parsed manifest, or `null` if missing / unreadable / malformed. */
  readonly manifest: Manifest | null;
}
 
/**
 * Read and parse `manifest.json` from a run directory.
 *
 * Silent-failure semantics match the pre-refactor `analysis-aggregator.ts`
 * and `article-generator.ts` callers: missing files, malformed JSON, and
 * I/O errors all yield `manifest: null` rather than throwing. Callers that
 * need to distinguish "file missing" from "file malformed" can compare
 * `manifest === null` against `fs.existsSync(result.path)`.
 *
 * @param runDir - Absolute path of an analysis run directory
 * @returns {@link ReadManifestResult} with the parsed manifest or `null`
 */
export function readManifest(runDir: string): ReadManifestResult {
  const manifestPath = path.join(runDir, 'manifest.json');
  if (!fs.existsSync(manifestPath)) {
    return { path: manifestPath, manifest: null };
  }
  try {
    const raw = fs.readFileSync(manifestPath, 'utf8');
    const parsed = JSON.parse(raw) as Manifest;
    return { path: manifestPath, manifest: parsed };
  } catch {
    return { path: manifestPath, manifest: null };
  }
}
 
/**
 * Parse a manifest from a JSON string. Useful for backport callers that
 * already have the manifest in memory or for tests that synthesise
 * manifests without touching disk.
 *
 * @param json - Raw JSON text
 * @returns Parsed manifest, or `null` when the input is not valid JSON
 */
export function parseManifest(json: string): Manifest | null {
  try {
    return JSON.parse(json) as Manifest;
  } catch {
    return null;
  }
}