119 lines
3.5 KiB
TypeScript
119 lines
3.5 KiB
TypeScript
|
//
|
||
|
// Pointer file parsing
|
||
|
|
||
|
import { filter, flow, fromPairs, map } from 'lodash/fp';
|
||
|
import getBlobSHA from './getBlobSHA';
|
||
|
import { AssetProxy } from './implementation';
|
||
|
|
||
|
export interface PointerFile {
|
||
|
size: number;
|
||
|
sha: string;
|
||
|
}
|
||
|
|
||
|
const splitIntoLines = (str: string) => str.split('\n');
|
||
|
const splitIntoWords = (str: string) => str.split(/\s+/g);
|
||
|
const isNonEmptyString = (str: string) => 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
|
||
|
|
||
|
const removeGitAttributesCommentsFromLine = (line: string) => line.split('#')[0];
|
||
|
|
||
|
const 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 const createPointerFile = ({ size, sha }: PointerFile) => `\
|
||
|
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 };
|
||
|
}),
|
||
|
);
|
||
|
}
|