1import minimatch from 'minimatch'; 2import path from 'path'; 3import prettyBytes from 'pretty-bytes'; 4 5import Git from '../../Git'; 6import logger from '../../Logger'; 7import { ReviewInput, ReviewOutput, ReviewStatus } from '../types'; 8 9const FILE_SIZE_LIMIT = 5 * 1000 * 1000; // 5MB 10const PRETTY_FILE_SIZE_LIMIT = prettyBytes(FILE_SIZE_LIMIT); 11 12const IGNORED_PATHS = ['android/versioned-abis/**/*.aar']; 13 14export default async function ({ pullRequest, diff }: ReviewInput): Promise<ReviewOutput | null> { 15 if (!pullRequest.head) { 16 logger.warn('Detached PR, we cannot asses the state of files!', pullRequest); 17 return null; 18 } 19 20 const listTree = await Git.listTreeAsync( 21 pullRequest.head.sha, 22 diff.filter((file) => !file.deleted).map((file) => file.path) 23 ); 24 25 const fileReports = listTree 26 .map((file) => { 27 const logs: string[] = []; 28 29 // We may ignore some paths where the files are allowed to be bigger (e.g. versioned .aar files). 30 if (IGNORED_PATHS.some((pattern) => minimatch(file.path, pattern))) { 31 return null; 32 } 33 34 const extname = path.extname(file.path).substr(1).toLowerCase(); 35 if (extname === 'gif') { 36 logs.push(`**GIF** files are forbidden, please consider using **MP4** instead`); 37 } 38 39 if (file.size > FILE_SIZE_LIMIT) { 40 const prettySize = prettyBytes(file.size); 41 logs.push(`File size **${prettySize}** exceeds the limit of **${PRETTY_FILE_SIZE_LIMIT}**`); 42 } 43 44 if (logs.length === 0) { 45 return null; 46 } 47 return `- ${linkToFile(pullRequest.head, file.path)}\n - ${logs.join('\n - ')}`; 48 }) 49 .filter(Boolean); 50 51 if (fileReports.length === 0) { 52 return null; 53 } 54 55 return { 56 status: ReviewStatus.ERROR, 57 title: 'Forbidden file size or format', 58 body: fileReports.join('\n'), 59 }; 60} 61 62function linkToFile(head: ReviewInput['pullRequest']['head'], path: string): string { 63 return `[${path}](${head.repo?.html_url}/blob/${head.ref}/${encodeURIComponent(path)})`; 64} 65