2021-11-29 01:56:58 +00:00
|
|
|
|
import path from 'path';
|
|
|
|
|
import fsp from 'fs/promises';
|
2021-11-25 12:27:33 +00:00
|
|
|
|
|
2021-11-30 20:53:50 +00:00
|
|
|
|
import { bodyToBuffer, getAuthHeader, isWriteable } from './util';
|
2021-11-25 12:27:33 +00:00
|
|
|
|
import { Pointer } from './pointers';
|
2021-11-30 20:53:33 +00:00
|
|
|
|
import { HTTPRequest } from './types';
|
2021-11-25 12:27:33 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
interface LFSInfoResponse {
|
|
|
|
|
objects: {
|
|
|
|
|
actions: {
|
|
|
|
|
download: {
|
|
|
|
|
href: string;
|
2021-11-29 01:56:09 +00:00
|
|
|
|
header?: Record<string, string>;
|
2021-11-25 12:27:33 +00:00
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function isValidLFSInfoResponseData(val: Record<string, any>): val is LFSInfoResponse {
|
|
|
|
|
return val.objects?.[0]?.actions?.download?.href?.trim !== undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2021-11-29 01:56:58 +00:00
|
|
|
|
/**
|
|
|
|
|
* Downloads, caches and returns a blob corresponding to given LFS pointer.
|
|
|
|
|
*/
|
2021-11-25 12:27:33 +00:00
|
|
|
|
export default async function downloadBlobFromPointer(
|
2021-11-30 20:53:33 +00:00
|
|
|
|
{ http: { request }, headers = {}, url, auth }: HTTPRequest,
|
2021-11-29 01:54:57 +00:00
|
|
|
|
{ info, objectPath }: Pointer,
|
2021-11-25 12:27:33 +00:00
|
|
|
|
): Promise<Buffer> {
|
2021-11-25 12:36:13 +00:00
|
|
|
|
|
2021-11-29 13:27:07 +00:00
|
|
|
|
const authHeaders: Record<string, string> = auth
|
2021-11-30 20:53:50 +00:00
|
|
|
|
? getAuthHeader(auth)
|
2021-11-29 13:27:07 +00:00
|
|
|
|
: {};
|
|
|
|
|
|
2021-11-30 21:18:27 +00:00
|
|
|
|
// Request LFS transfer
|
2021-11-25 12:27:33 +00:00
|
|
|
|
|
|
|
|
|
const lfsInfoRequestData = {
|
|
|
|
|
operation: 'download',
|
|
|
|
|
transfers: ['basic'],
|
|
|
|
|
objects: [info],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const { body: lfsInfoBody } = await request({
|
|
|
|
|
url: `${url}/info/lfs/objects/batch`,
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
2021-11-29 01:55:23 +00:00
|
|
|
|
// Github LFS doesn’t seem to accept this UA :(
|
|
|
|
|
// 'User-Agent': `git/isomorphic-git@${git.version()}`,
|
2021-11-25 12:27:33 +00:00
|
|
|
|
...headers,
|
2021-11-29 13:27:07 +00:00
|
|
|
|
...authHeaders,
|
2021-11-25 12:27:33 +00:00
|
|
|
|
'Accept': 'application/vnd.git-lfs+json',
|
|
|
|
|
'Content-Type': 'application/vnd.git-lfs+json',
|
|
|
|
|
},
|
|
|
|
|
body: [Buffer.from(JSON.stringify(lfsInfoRequestData))],
|
|
|
|
|
});
|
|
|
|
|
|
2021-11-29 01:58:33 +00:00
|
|
|
|
const lfsInfoResponseRaw = (await bodyToBuffer(lfsInfoBody)).toString();
|
|
|
|
|
let lfsInfoResponseData: any;
|
|
|
|
|
try {
|
|
|
|
|
lfsInfoResponseData = JSON.parse(lfsInfoResponseRaw);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
throw new Error(`Unexpected structure received from LFS server: unable to parse JSON ${lfsInfoResponseRaw}`);
|
|
|
|
|
}
|
2021-11-25 12:27:33 +00:00
|
|
|
|
|
|
|
|
|
if (isValidLFSInfoResponseData(lfsInfoResponseData)) {
|
|
|
|
|
|
|
|
|
|
// Request the actual blob
|
2021-11-25 12:36:13 +00:00
|
|
|
|
|
2021-11-29 01:56:09 +00:00
|
|
|
|
const downloadAction = lfsInfoResponseData.objects[0].actions.download;
|
|
|
|
|
const lfsObjectDownloadURL = downloadAction.href;
|
|
|
|
|
const lfsObjectDownloadHeaders = downloadAction.header ?? {};
|
|
|
|
|
|
2021-11-29 13:27:07 +00:00
|
|
|
|
const dlHeaders = {
|
|
|
|
|
...headers,
|
|
|
|
|
...authHeaders,
|
|
|
|
|
...lfsObjectDownloadHeaders,
|
|
|
|
|
};
|
2021-11-25 12:27:33 +00:00
|
|
|
|
|
|
|
|
|
const { body: lfsObjectBody } = await request({
|
|
|
|
|
url: lfsObjectDownloadURL,
|
|
|
|
|
method: 'GET',
|
2021-11-29 01:56:09 +00:00
|
|
|
|
headers: dlHeaders,
|
2021-11-25 12:27:33 +00:00
|
|
|
|
});
|
|
|
|
|
|
2021-11-29 01:56:58 +00:00
|
|
|
|
const blob = await bodyToBuffer(lfsObjectBody);
|
|
|
|
|
|
2021-12-02 13:14:48 +00:00
|
|
|
|
// Write LFS cache for this object, if cache path is accessible.
|
2021-11-29 01:56:58 +00:00
|
|
|
|
if (await isWriteable(objectPath)) {
|
|
|
|
|
await fsp.mkdir(path.dirname(objectPath), { recursive: true });
|
|
|
|
|
await fsp.writeFile(objectPath, blob);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return blob;
|
2021-11-25 12:27:33 +00:00
|
|
|
|
|
|
|
|
|
} else {
|
2021-11-30 21:30:20 +00:00
|
|
|
|
throw new Error("Unexpected JSON structure received for LFS download request");
|
2021-11-25 12:27:33 +00:00
|
|
|
|
}
|
|
|
|
|
}
|