import path from "path-browserify"; import { Buffer } from "buffer"; import { bodyToBuffer, getAuthHeader, isWriteable } from "./util"; import { Pointer } from "./pointers"; import { HTTPRequest, BasicAuth } from "./types"; import { PromiseFsClient } from "isomorphic-git"; interface LFSInfoResponse { objects: { actions: { download: { href: string; header?: Record; }; }; }[]; } function isValidLFSInfoResponseData( val: Record ): val is LFSInfoResponse { return val.objects?.[0]?.actions?.download?.href?.trim !== undefined; } /** * Downloads, caches and returns a blob corresponding to given LFS pointer. * Uses already cached object, if size matches. */ export default async function downloadBlobFromPointer( fs: PromiseFsClient | null, url: string, auth: BasicAuth | {}, { info, objectPath }: Pointer ): Promise { if (fs) { try { const cached = await fs?.promises.readFile(objectPath); if (cached.byteLength === info.size) { return cached; } } catch (e) { // Silence file not found errors (implies cache miss) if ((e as any).code !== "ENOENT") { throw e; } } } const authHeaders: Record = "username" in auth ? getAuthHeader(auth) : {}; // Request LFS transfer const lfsInfoRequestData = { operation: "download", transfers: ["basic"], objects: [info], }; const lfsInfoRes = await fetch(`${url}/info/lfs/objects/batch`, { method: "POST", headers: { // Github LFS doesn’t seem to accept this UA, but works fine without any // 'User-Agent': `git/isomorphic-git@${git.version()}`, ...authHeaders, Accept: "application/vnd.git-lfs+json", "Content-Type": "application/vnd.git-lfs+json", }, body: JSON.stringify(lfsInfoRequestData), }); const lfsInfoResponseData = await lfsInfoRes.json(); if (isValidLFSInfoResponseData(lfsInfoResponseData)) { // Request the actual blob const downloadAction = lfsInfoResponseData.objects[0].actions.download; const lfsObjectDownloadURL = downloadAction.href; const lfsObjectDownloadHeaders = downloadAction.header ?? {}; const lfsObjectRes = await fetch(lfsObjectDownloadURL, { method: "GET", headers: { ...authHeaders, ...lfsObjectDownloadHeaders, }, }); const blob = await lfsObjectRes.blob(); if (fs) { // Write LFS cache for this object, if cache path is accessible. if (await isWriteable(fs, objectPath)) { await fs.promises.mkdir(path.dirname(objectPath), { recursive: true }); await fs.promises.writeFile(objectPath, blob); } } return blob; } else { throw new Error( "Unexpected JSON structure received for LFS download request" ); } }