{"version":3,"file":"dump/screenshot-store.mjs","sources":["../../../src/dump/screenshot-store.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';\nimport { writeFile as writeFileAsync } from 'node:fs/promises';\nimport { dirname, isAbsolute, join } from 'node:path';\nimport type { ScreenshotItem } from '../screenshot-item';\nimport { extractImageByIdSync } from './html-utils';\n\nexport interface ScreenshotRef {\n  type: 'midscene_screenshot_ref';\n  id: string;\n  capturedAt: number;\n  mimeType: 'image/png' | 'image/jpeg';\n  storage: 'inline' | 'file';\n  path?: string;\n}\n\nexport function normalizeScreenshotRef(value: unknown): ScreenshotRef | null {\n  if (typeof value !== 'object' || value === null) return null;\n  const record = value as Record<string, unknown>;\n\n  if (\n    record.type === 'midscene_screenshot_ref' &&\n    typeof record.id === 'string' &&\n    typeof record.capturedAt === 'number' &&\n    (record.storage === 'inline' || record.storage === 'file') &&\n    (record.mimeType === 'image/png' || record.mimeType === 'image/jpeg')\n  ) {\n    if (record.storage === 'file' && typeof record.path !== 'string') {\n      return null;\n    }\n    return record as unknown as ScreenshotRef;\n  }\n\n  return null;\n}\n\ntype ResolvedScreenshotSource =\n  | {\n      type: 'data-uri';\n      id: string;\n      mimeType: ScreenshotRef['mimeType'];\n      dataUri: string;\n    }\n  | {\n      type: 'file';\n      id: string;\n      mimeType: ScreenshotRef['mimeType'];\n      filePath: string;\n    };\n\nfunction extensionByMimeType(mimeType: ScreenshotRef['mimeType']): string {\n  return mimeType === 'image/jpeg' ? 'jpeg' : 'png';\n}\n\nexport function resolveScreenshotSource(\n  refInput: unknown,\n  options: {\n    reportPath: string;\n    fallbackId?: string;\n    fallbackMimeType?: ScreenshotRef['mimeType'];\n  },\n): ResolvedScreenshotSource {\n  const ref = normalizeScreenshotRef(refInput);\n  const id = ref?.id ?? options.fallbackId;\n  const mimeType = ref?.mimeType ?? options.fallbackMimeType;\n\n  if (!id || !mimeType) {\n    throw new Error(\n      'ScreenshotStore: screenshot id and mimeType are required to resolve screenshot',\n    );\n  }\n\n  const resolveReportRelativePath = (filePath: string): string =>\n    isAbsolute(filePath)\n      ? filePath\n      : join(dirname(options.reportPath), filePath);\n\n  if (ref?.storage === 'file') {\n    if (!ref.path) {\n      throw new Error(\n        `ScreenshotStore: screenshot ref \"${ref.id}\" missing file path`,\n      );\n    }\n\n    const explicitFilePath = resolveReportRelativePath(ref.path);\n    if (existsSync(explicitFilePath)) {\n      return {\n        type: 'file',\n        id,\n        mimeType,\n        filePath: explicitFilePath,\n      };\n    }\n  }\n\n  const inlineDataUri = extractImageByIdSync(options.reportPath, id);\n  if (inlineDataUri) {\n    return {\n      type: 'data-uri',\n      id,\n      mimeType,\n      dataUri: inlineDataUri,\n    };\n  }\n\n  const siblingScreenshotPath = join(\n    dirname(options.reportPath),\n    'screenshots',\n    `${id}.${extensionByMimeType(mimeType)}`,\n  );\n  if (existsSync(siblingScreenshotPath)) {\n    return {\n      type: 'file',\n      id,\n      mimeType,\n      filePath: siblingScreenshotPath,\n    };\n  }\n\n  throw new Error(\n    `ScreenshotStore: cannot resolve screenshot \"${id}\" from ${options.reportPath}`,\n  );\n}\n\nexport class ScreenshotStore {\n  private readonly mode: 'inline' | 'directory';\n  private readonly reportPath: string;\n  private readonly screenshotsDir?: string;\n  private readonly writeInlineImage?: (\n    id: string,\n    base64: string,\n  ) => void | Promise<void>;\n  private readonly alsoWriteFileCopy: boolean;\n  private readonly writtenInlineIds = new Set<string>();\n  private readonly writtenFileIds = new Set<string>();\n\n  constructor(options: {\n    mode: 'inline' | 'directory';\n    reportPath: string;\n    screenshotsDir?: string;\n    writeInlineImage?: (id: string, base64: string) => void | Promise<void>;\n    alsoWriteFileCopy?: boolean;\n    /** @deprecated Use alsoWriteFileCopy instead. */\n    ensureFileCopy?: boolean;\n  }) {\n    this.mode = options.mode;\n    this.reportPath = options.reportPath;\n    this.screenshotsDir = options.screenshotsDir;\n    this.writeInlineImage = options.writeInlineImage;\n    this.alsoWriteFileCopy =\n      options.alsoWriteFileCopy ?? options.ensureFileCopy ?? false;\n  }\n\n  async persist(screenshot: ScreenshotItem): Promise<ScreenshotRef> {\n    const shouldWriteFileCopy =\n      this.mode === 'directory' || this.alsoWriteFileCopy;\n    const fileRef = shouldWriteFileCopy\n      ? await this.persistToSharedFileIfNeeded(screenshot, {\n          markAsPersisted: this.mode === 'directory',\n        })\n      : null;\n\n    if (this.mode === 'inline') {\n      if (!this.writeInlineImage) {\n        throw new Error(\n          'ScreenshotStore: writeInlineImage is required in inline mode',\n        );\n      }\n      if (!this.writtenInlineIds.has(screenshot.id)) {\n        await this.writeInlineImage(screenshot.id, screenshot.base64);\n        this.writtenInlineIds.add(screenshot.id);\n      }\n      return screenshot.markPersistedInline(this.reportPath);\n    }\n\n    if (!fileRef) {\n      throw new Error(\n        'ScreenshotStore: file persistence is required in directory mode',\n      );\n    }\n    return fileRef;\n  }\n\n  private async persistToSharedFileIfNeeded(\n    screenshot: ScreenshotItem,\n    options: {\n      markAsPersisted: boolean;\n    },\n  ): Promise<ScreenshotRef> {\n    const screenshotsDir = this.screenshotsDir;\n    if (!screenshotsDir) {\n      throw new Error(\n        'ScreenshotStore: screenshotsDir is required when file persistence is enabled',\n      );\n    }\n    if (!existsSync(screenshotsDir)) {\n      mkdirSync(screenshotsDir, { recursive: true });\n    }\n\n    const relativePath = `./screenshots/${screenshot.id}.${screenshot.extension}`;\n    const absolutePath = join(\n      screenshotsDir,\n      `${screenshot.id}.${screenshot.extension}`,\n    );\n\n    if (!this.writtenFileIds.has(screenshot.id)) {\n      const buffer = Buffer.from(screenshot.rawBase64, 'base64');\n      await writeFileAsync(absolutePath, buffer);\n      this.writtenFileIds.add(screenshot.id);\n    }\n\n    if (options.markAsPersisted) {\n      return screenshot.markPersistedToPath(relativePath, absolutePath);\n    }\n\n    return screenshot.registerPersistedFileCopy(relativePath, absolutePath);\n  }\n\n  loadBase64(refInput: unknown): string {\n    const ref = normalizeScreenshotRef(refInput);\n    if (!ref) {\n      throw new Error('ScreenshotStore: invalid screenshot reference');\n    }\n\n    const resolved = resolveScreenshotSource(ref, {\n      reportPath: this.reportPath,\n    });\n\n    if (resolved.type === 'data-uri') {\n      return resolved.dataUri;\n    }\n\n    const data = readFileSync(resolved.filePath);\n    return `data:${resolved.mimeType};base64,${data.toString('base64')}`;\n  }\n\n  cleanup(): void {\n    if (\n      this.mode === 'directory' &&\n      this.screenshotsDir &&\n      existsSync(this.screenshotsDir)\n    ) {\n      rmSync(this.screenshotsDir, { recursive: true, force: true });\n    }\n    this.writtenInlineIds.clear();\n    this.writtenFileIds.clear();\n  }\n}\n"],"names":["normalizeScreenshotRef","value","record","extensionByMimeType","mimeType","resolveScreenshotSource","refInput","options","ref","id","Error","resolveReportRelativePath","filePath","isAbsolute","join","dirname","explicitFilePath","existsSync","inlineDataUri","extractImageByIdSync","siblingScreenshotPath","ScreenshotStore","screenshot","shouldWriteFileCopy","fileRef","screenshotsDir","mkdirSync","relativePath","absolutePath","buffer","Buffer","writeFileAsync","resolved","data","readFileSync","rmSync","Set"],"mappings":"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"}