import { parseAtUri } from './at-uri.ts'; import { gunzipToString, listMarkdownFilesInDiff } from './patch.ts'; import type { PullRecord } from './types.ts'; export type FetchImpl = (input: string | URL, init?: RequestInit) => Promise; export function latestPatchCid(pull: PullRecord): string | null { const rounds = pull.rounds ?? []; const latest = rounds[rounds.length - 1]; return latest?.patchBlob?.ref?.$link ?? null; } /** Fetch and gunzip a patch blob from the author's PDS. Returns null on any failure. */ export async function fetchPatchText( pds: string, authorDid: string, patchCid: string, fetchImpl: FetchImpl = fetch, ): Promise { const url = new URL('/xrpc/com.atproto.sync.getBlob', pds); url.searchParams.set('did', authorDid); url.searchParams.set('cid', patchCid); const res = await fetchImpl(url); if (!res.ok) return null; const bytes = new Uint8Array(await res.arrayBuffer()); try { return await gunzipToString(bytes); } catch { return null; } } export interface PullPatchDeps { resolvePds: (did: string) => Promise; fetchImpl?: FetchImpl; } /** Resolve the pull author's PDS, fetch the latest patch, and return the `.md` paths it touches. */ export async function pullMarkdownPaths( pullUri: string, pull: PullRecord, deps: PullPatchDeps, ): Promise { const cid = latestPatchCid(pull); const parsed = parseAtUri(pullUri); if (!cid || !parsed) return []; let pds: string; try { pds = await deps.resolvePds(parsed.did); } catch { return []; } const text = await fetchPatchText(pds, parsed.did, cid, deps.fetchImpl); if (!text) return []; return listMarkdownFilesInDiff(text).map((entry) => entry.path); }