132 lines
3.6 KiB
TypeScript
Raw Normal View History

//
// Pointer file parsing
import { filter, flow, fromPairs, map } from 'lodash/fp';
import getBlobSHA from './getBlobSHA';
import type { AssetProxy } from './implementation';
export interface PointerFile {
size: number;
sha: string;
}
function splitIntoLines(str: string) {
return str.split('\n');
}
function splitIntoWords(str: string) {
return str.split(/\s+/g);
}
function isNonEmptyString(str: string) {
return str !== '';
}
const withoutEmptyLines = flow([map((str: string) => str.trim()), filter(isNonEmptyString)]);
export const parsePointerFile: (data: string) => PointerFile = flow([
splitIntoLines,
withoutEmptyLines,
map(splitIntoWords),
fromPairs,
({ size, oid, ...rest }) => ({
size: parseInt(size),
sha: oid?.split(':')[1],
...rest,
}),
]);
//
// .gitattributes file parsing
function removeGitAttributesCommentsFromLine(line: string) {
return line.split('#')[0];
}
function parseGitPatternAttribute(attributeString: string) {
// There are three kinds of attribute settings:
// - a key=val pair sets an attribute to a specific value
// - a key without a value and a leading hyphen sets an attribute to false
// - a key without a value and no leading hyphen sets an attribute
// to true
if (attributeString.includes('=')) {
return attributeString.split('=');
}
if (attributeString.startsWith('-')) {
return [attributeString.slice(1), false];
}
return [attributeString, true];
}
const parseGitPatternAttributes = flow([map(parseGitPatternAttribute), fromPairs]);
const parseGitAttributesPatternLine = flow([
splitIntoWords,
([pattern, ...attributes]) => [pattern, parseGitPatternAttributes(attributes)],
]);
const parseGitAttributesFileToPatternAttributePairs = flow([
splitIntoLines,
map(removeGitAttributesCommentsFromLine),
withoutEmptyLines,
map(parseGitAttributesPatternLine),
]);
export const getLargeMediaPatternsFromGitAttributesFile = flow([
parseGitAttributesFileToPatternAttributePairs,
filter(
([, attributes]) =>
attributes.filter === 'lfs' && attributes.diff === 'lfs' && attributes.merge === 'lfs',
),
map(([pattern]) => pattern),
]);
export function createPointerFile({ size, sha }: PointerFile) {
return `\
version https://git-lfs.github.com/spec/v1
oid sha256:${sha}
size ${size}
`;
}
export async function getPointerFileForMediaFileObj(
client: { uploadResource: (pointer: PointerFile, resource: Blob) => Promise<string> },
fileObj: File,
path: string,
) {
const { name, size } = fileObj;
const sha = await getBlobSHA(fileObj);
await client.uploadResource({ sha, size }, fileObj);
const pointerFileString = createPointerFile({ sha, size });
const pointerFileBlob = new Blob([pointerFileString]);
const pointerFile = new File([pointerFileBlob], name, { type: 'text/plain' });
const pointerFileSHA = await getBlobSHA(pointerFile);
return {
fileObj: pointerFile,
size: pointerFileBlob.size,
sha: pointerFileSHA,
raw: pointerFileString,
path,
};
}
export async function getLargeMediaFilteredMediaFiles(
client: {
uploadResource: (pointer: PointerFile, resource: Blob) => Promise<string>;
matchPath: (path: string) => boolean;
},
mediaFiles: AssetProxy[],
) {
return await Promise.all(
mediaFiles.map(async mediaFile => {
const { fileObj, path } = mediaFile;
const fixedPath = path.startsWith('/') ? path.slice(1) : path;
if (!client.matchPath(fixedPath)) {
return mediaFile;
}
const pointerFileDetails = await getPointerFileForMediaFileObj(client, fileObj as File, path);
return { ...mediaFile, ...pointerFileDetails };
}),
);
}