{"version":3,"file":"dump/report-dump-compactor.mjs","sources":["../../../src/dump/report-dump-compactor.ts"],"sourcesContent":["import { createReadStream } from 'node:fs';\nimport { type FileHandle, open, rename, stat, unlink } from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport {\n  antiEscapeScriptTag,\n  escapeScriptTag,\n  uuid,\n} from '@midscene/shared/utils';\nimport { type ExecutionDump, ReportActionDump } from '../types';\n\nconst SCRIPT_OPEN = Buffer.from('<script');\nconst SCRIPT_CLOSE = Buffer.from('</script>');\nconst READ_CHUNK_SIZE = 32 * 1024 * 1024;\nconst DUMP_TYPE_ATTRIBUTE = 'type=\"midscene_web_dump\"';\nconst GROUP_ID_REGEXP = /data-group-id=\"([^\"]+)\"/;\n\ninterface ScriptTag {\n  openTag: string;\n  content: Buffer;\n}\n\ninterface DumpGroup {\n  baseDump: ReportActionDump;\n  executionsByKey: Map<string, ExecutionDump>;\n  firstOpenTag: string;\n  noIdExecutionIndex: number;\n  tagCount: number;\n}\n\ninterface DumpAnalysis {\n  groups: Map<string, DumpGroup>;\n  tagCount: number;\n}\n\nexport interface ReportDumpCompactionResult {\n  beforeBytes: number;\n  afterBytes: number;\n  beforeDumpCount: number;\n  afterDumpCount: number;\n}\n\nfunction decodeGroupId(openTag: string): string | undefined {\n  if (!openTag.includes(DUMP_TYPE_ATTRIBUTE)) return undefined;\n  const encodedGroupId = openTag.match(GROUP_ID_REGEXP)?.[1];\n  return encodedGroupId ? decodeURIComponent(encodedGroupId) : undefined;\n}\n\nasync function scanScriptTags(\n  reportPath: string,\n  onScript: (script: ScriptTag) => void | Promise<void>,\n): Promise<void> {\n  let pending: Buffer = Buffer.alloc(0);\n\n  for await (const chunk of createReadStream(reportPath, {\n    highWaterMark: READ_CHUNK_SIZE,\n  })) {\n    pending =\n      pending.length === 0\n        ? (chunk as Buffer)\n        : Buffer.concat([pending, chunk as Buffer]);\n\n    while (pending.length > 0) {\n      const scriptStart = pending.indexOf(SCRIPT_OPEN);\n      if (scriptStart === -1) {\n        pending = pending.subarray(\n          Math.max(0, pending.length - SCRIPT_OPEN.length + 1),\n        );\n        break;\n      }\n\n      if (scriptStart > 0) {\n        pending = pending.subarray(scriptStart);\n      }\n\n      const openTagEnd = pending.indexOf(0x3e, SCRIPT_OPEN.length);\n      if (openTagEnd === -1) break;\n\n      const closeTagStart = pending.indexOf(SCRIPT_CLOSE, openTagEnd + 1);\n      if (closeTagStart === -1) break;\n\n      const scriptEnd = closeTagStart + SCRIPT_CLOSE.length;\n      await onScript({\n        openTag: pending.subarray(0, openTagEnd + 1).toString('utf-8'),\n        content: pending.subarray(openTagEnd + 1, closeTagStart),\n      });\n      pending = pending.subarray(scriptEnd);\n    }\n  }\n\n  if (pending.indexOf(SCRIPT_OPEN) !== -1) {\n    throw new Error(\n      `Report contains an unterminated script tag: ${reportPath}`,\n    );\n  }\n}\n\nasync function analyzeDumpGroups(reportPath: string): Promise<DumpAnalysis> {\n  const groups = new Map<string, DumpGroup>();\n  let tagCount = 0;\n\n  await scanScriptTags(reportPath, ({ openTag, content }) => {\n    const groupId = decodeGroupId(openTag);\n    if (!groupId) return;\n\n    const dump = ReportActionDump.fromSerializedString(\n      antiEscapeScriptTag(content.toString('utf-8').trim()),\n    );\n    let group = groups.get(groupId);\n    if (!group) {\n      group = {\n        baseDump: dump,\n        executionsByKey: new Map(),\n        firstOpenTag: openTag,\n        noIdExecutionIndex: 0,\n        tagCount: 0,\n      };\n      groups.set(groupId, group);\n    }\n\n    group.tagCount += 1;\n    tagCount += 1;\n    for (const execution of dump.executions) {\n      const executionKey = execution.id\n        ? `id:${execution.id}`\n        : `no-id:${group.noIdExecutionIndex++}`;\n      group.executionsByKey.set(executionKey, execution);\n    }\n  });\n\n  return { groups, tagCount };\n}\n\nfunction compactedDumpTag(group: DumpGroup): Buffer {\n  group.baseDump.executions = Array.from(group.executionsByKey.values());\n  const serialized = escapeScriptTag(group.baseDump.serialize());\n  return Buffer.from(\n    `${group.firstOpenTag}${serialized}${SCRIPT_CLOSE.toString('utf-8')}`,\n  );\n}\n\nasync function writeAll(handle: FileHandle, data: Buffer): Promise<void> {\n  let offset = 0;\n  while (offset < data.length) {\n    const { bytesWritten } = await handle.write(\n      data,\n      offset,\n      data.length - offset,\n      null,\n    );\n    if (bytesWritten === 0) {\n      throw new Error('Failed to make progress while writing compacted report');\n    }\n    offset += bytesWritten;\n  }\n}\n\nasync function rewriteReport(\n  sourcePath: string,\n  targetPath: string,\n  analysis: DumpAnalysis,\n): Promise<void> {\n  const compactedTags = new Map(\n    Array.from(analysis.groups, ([groupId, group]) => [\n      groupId,\n      compactedDumpTag(group),\n    ]),\n  );\n  const seenTagsByGroup = new Map<string, number>();\n  const emittedGroups = new Set<string>();\n  const output = await open(targetPath, 'wx');\n  let pending: Buffer = Buffer.alloc(0);\n\n  try {\n    for await (const chunk of createReadStream(sourcePath, {\n      highWaterMark: READ_CHUNK_SIZE,\n    })) {\n      pending =\n        pending.length === 0\n          ? (chunk as Buffer)\n          : Buffer.concat([pending, chunk as Buffer]);\n\n      while (pending.length > 0) {\n        const scriptStart = pending.indexOf(SCRIPT_OPEN);\n        if (scriptStart === -1) {\n          const writableLength = Math.max(\n            0,\n            pending.length - SCRIPT_OPEN.length + 1,\n          );\n          await writeAll(output, pending.subarray(0, writableLength));\n          pending = pending.subarray(writableLength);\n          break;\n        }\n\n        await writeAll(output, pending.subarray(0, scriptStart));\n        pending = pending.subarray(scriptStart);\n\n        const openTagEnd = pending.indexOf(0x3e, SCRIPT_OPEN.length);\n        if (openTagEnd === -1) break;\n\n        const closeTagStart = pending.indexOf(SCRIPT_CLOSE, openTagEnd + 1);\n        if (closeTagStart === -1) break;\n\n        const scriptEnd = closeTagStart + SCRIPT_CLOSE.length;\n        const openTag = pending.subarray(0, openTagEnd + 1).toString('utf-8');\n        const groupId = decodeGroupId(openTag);\n\n        if (!groupId || !analysis.groups.has(groupId)) {\n          await writeAll(output, pending.subarray(0, scriptEnd));\n        } else {\n          const seenCount = (seenTagsByGroup.get(groupId) ?? 0) + 1;\n          seenTagsByGroup.set(groupId, seenCount);\n          if (seenCount === analysis.groups.get(groupId)!.tagCount) {\n            await writeAll(output, compactedTags.get(groupId)!);\n            emittedGroups.add(groupId);\n          }\n        }\n        pending = pending.subarray(scriptEnd);\n      }\n    }\n\n    if (pending.indexOf(SCRIPT_OPEN) !== -1) {\n      throw new Error(\n        `Report contains an unterminated script tag: ${sourcePath}`,\n      );\n    }\n    await writeAll(output, pending);\n\n    for (const [groupId, group] of analysis.groups) {\n      if (\n        seenTagsByGroup.get(groupId) !== group.tagCount ||\n        !emittedGroups.has(groupId)\n      ) {\n        throw new Error(\n          `Report compaction did not rewrite every dump in group: ${groupId}`,\n        );\n      }\n    }\n    await output.sync();\n  } finally {\n    await output.close();\n  }\n}\n\nfunction canRetryRenameOnWindows(error: unknown): boolean {\n  if (process.platform !== 'win32') return false;\n  const code = (error as NodeJS.ErrnoException).code;\n  return code === 'EACCES' || code === 'EEXIST' || code === 'EPERM';\n}\n\nasync function replaceReportFile(\n  temporaryPath: string,\n  reportPath: string,\n): Promise<void> {\n  try {\n    await rename(temporaryPath, reportPath);\n    return;\n  } catch (error) {\n    if (!canRetryRenameOnWindows(error)) throw error;\n  }\n\n  const backupPath = join(\n    dirname(reportPath),\n    `.${basename(reportPath)}.${uuid()}.backup`,\n  );\n  await rename(reportPath, backupPath);\n  try {\n    await rename(temporaryPath, reportPath);\n  } catch (replaceError) {\n    try {\n      await rename(backupPath, reportPath);\n    } catch (restoreError) {\n      throw new Error(\n        `Failed to replace compacted report and restore the original report: ${String(replaceError)}; restore error: ${String(restoreError)}`,\n      );\n    }\n    throw replaceError;\n  }\n  await unlink(backupPath);\n}\n\nexport async function compactReportDumps(\n  reportPath: string,\n): Promise<ReportDumpCompactionResult> {\n  const before = await stat(reportPath);\n  const analysis = await analyzeDumpGroups(reportPath);\n  const afterDumpCount = analysis.groups.size;\n\n  if (analysis.tagCount <= afterDumpCount) {\n    return {\n      beforeBytes: before.size,\n      afterBytes: before.size,\n      beforeDumpCount: analysis.tagCount,\n      afterDumpCount,\n    };\n  }\n\n  const temporaryPath = join(\n    dirname(reportPath),\n    `.${basename(reportPath)}.${uuid()}.tmp`,\n  );\n\n  try {\n    await rewriteReport(reportPath, temporaryPath, analysis);\n    const after = await stat(temporaryPath);\n    await replaceReportFile(temporaryPath, reportPath);\n    return {\n      beforeBytes: before.size,\n      afterBytes: after.size,\n      beforeDumpCount: analysis.tagCount,\n      afterDumpCount,\n    };\n  } catch (error) {\n    try {\n      await unlink(temporaryPath);\n    } catch (cleanupError) {\n      if ((cleanupError as NodeJS.ErrnoException).code !== 'ENOENT') {\n        throw new Error(\n          `Report compaction failed and the temporary file could not be removed: ${String(error)}; cleanup error: ${String(cleanupError)}`,\n        );\n      }\n    }\n    throw error;\n  }\n}\n"],"names":["SCRIPT_OPEN","Buffer","SCRIPT_CLOSE","READ_CHUNK_SIZE","DUMP_TYPE_ATTRIBUTE","GROUP_ID_REGEXP","decodeGroupId","openTag","encodedGroupId","decodeURIComponent","undefined","scanScriptTags","reportPath","onScript","pending","chunk","createReadStream","scriptStart","Math","openTagEnd","closeTagStart","scriptEnd","Error","analyzeDumpGroups","groups","Map","tagCount","content","groupId","dump","ReportActionDump","antiEscapeScriptTag","group","execution","executionKey","compactedDumpTag","Array","serialized","escapeScriptTag","writeAll","handle","data","offset","bytesWritten","rewriteReport","sourcePath","targetPath","analysis","compactedTags","seenTagsByGroup","emittedGroups","Set","output","open","writableLength","seenCount","canRetryRenameOnWindows","error","process","code","replaceReportFile","temporaryPath","rename","backupPath","join","dirname","basename","uuid","replaceError","restoreError","String","unlink","compactReportDumps","before","stat","afterDumpCount","after","cleanupError"],"mappings":"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"}