isogit-lfs/src/upload.ts

144 lines
4.1 KiB
TypeScript
Raw Normal View History

2022-12-07 23:39:09 +00:00
import { Buffer } from "buffer";
2022-12-07 23:39:09 +00:00
import { HTTPRequest } from "./types";
import { buildPointerInfo, PointerInfo } from "./pointers";
import { bodyToBuffer, getAuthHeader } from "./util";
interface LFSInfoResponse {
objects: {
actions?: {
upload: {
href: string;
header?: Record<string, string>;
};
verify?: {
href: string;
header?: Record<string, string>;
};
};
}[];
}
2022-12-07 23:39:09 +00:00
function isValidLFSInfoResponseData(
val: Record<string, any>
): val is LFSInfoResponse {
const obj = val.objects?.[0];
2022-12-07 23:39:09 +00:00
return obj && (!obj.actions || obj.actions.upload.href.trim !== undefined);
}
/**
* Given a blob, uploads the blob to LFS server and returns a PointerInfo,
* which the caller can then combine with object path into a Pointer
* and commit in place of the original Git blob.
*/
export default async function uploadBlob(
{ http: { request }, headers = {}, url, auth }: HTTPRequest,
2022-12-07 23:39:09 +00:00
content: Buffer
): Promise<PointerInfo> {
const info = await buildPointerInfo(content);
2022-12-07 23:39:09 +00:00
const authHeaders: Record<string, string> = auth ? getAuthHeader(auth) : {};
// Request LFS transfer
const lfsInfoRequestData = {
2022-12-07 23:39:09 +00:00
operation: "upload",
transfers: ["basic"],
objects: [info],
};
const { body: lfsInfoBody } = await request({
url: `${url}/info/lfs/objects/batch`,
2022-12-07 23:39:09 +00:00
method: "POST",
headers: {
// Github LFS doesnt seem to accept this UA
// 'User-Agent': `git/isomorphic-git@${git.version()}`,
...headers,
...authHeaders,
2022-12-07 23:39:09 +00:00
Accept: "application/vnd.git-lfs+json",
"Content-Type": "application/vnd.git-lfs+json",
},
body: [Buffer.from(JSON.stringify(lfsInfoRequestData))],
});
const lfsInfoResponseRaw = (await bodyToBuffer(lfsInfoBody)).toString();
let lfsInfoResponseData: any;
try {
lfsInfoResponseData = JSON.parse(lfsInfoResponseRaw);
} catch (e) {
2022-12-07 23:39:09 +00:00
throw new Error(
`Unexpected structure received from LFS server: unable to parse JSON ${lfsInfoResponseRaw}`
);
}
if (isValidLFSInfoResponseData(lfsInfoResponseData)) {
// Upload the actual blob
const actions = lfsInfoResponseData.objects[0].actions;
if (!actions) {
// Presume LFS already has the blob. Dont fail loudly.
return info;
} else {
const uploadAction = actions.upload;
const lfsObjectUploadURL = uploadAction.href;
const lfsObjectUploadHeaders = uploadAction.header ?? {};
const dlHeaders = {
...headers,
...authHeaders,
...lfsObjectUploadHeaders,
};
const resp = await request({
url: lfsObjectUploadURL,
2022-12-07 23:39:09 +00:00
method: "PUT",
headers: dlHeaders,
body: [content],
});
if (resp.statusCode === 200) {
const verifyAction = actions.verify;
// Upload verification was requested. Do that
if (verifyAction) {
const verificationResp = await request({
url: verifyAction.href,
2022-12-07 23:39:09 +00:00
method: "POST",
headers: {
// Isomorphic Gits UA header is considered invalid
// and missing UA header causes an error in this case;
// cURL is considered valid, so…
2022-12-07 23:39:09 +00:00
"User-Agent": `curl/7.54`,
// TODO: Generalize UA header handling
// - Leave UA header twiddling to callers?
// - Figure out which LFS implementation wants which UA header?
...(verifyAction.header ?? {}),
},
body: [Buffer.from(JSON.stringify(info))],
});
if (verificationResp.statusCode === 200) {
return info;
} else {
2022-12-07 23:39:09 +00:00
throw new Error(
`Upload might have been unsuccessful, verification action yielded HTTP ${verificationResp.statusCode}`
);
}
} else {
return info;
}
} else {
2022-12-07 23:39:09 +00:00
throw new Error(
`Upload might have been unsuccessful, upload action yielded HTTP ${resp.statusCode}`
);
}
}
} else {
2022-12-07 23:39:09 +00:00
throw new Error(
"Unexpected JSON structure received for LFS upload request"
);
}
}