UNPKG

@dxatscale/sfprofiles

Version:
336 lines (292 loc) 12.5 kB
import * as path from 'path'; import * as fs from 'fs-extra'; import * as _ from 'lodash'; import MetadataFiles from '@impl/metadata/metadataFiles'; import { SOURCE_EXTENSION_REGEX } from '@impl/metadata/metadataInfo'; import { METADATA_INFO } from '@impl/metadata/metadataInfo'; import SFPLogger, {LoggerLevel } from '@dxatscale/sfp-logger'; import simplegit, { SimpleGit } from 'simple-git'; const SEP = /\/|\\/; export interface DiffFileStatus { revisionFrom: string; revisionTo: string; path: string; renamedPath?: string; } export interface DiffFile { deleted: DiffFileStatus[]; addedEdited: DiffFileStatus[]; } const git: SimpleGit = simplegit(); export default class DiffUtil { public static gitTreeRevisionTo: { revision: string; path: string; }[]; public static async isFormulaField(diffFile: DiffFileStatus): Promise<boolean> { let content = await git.show(['--raw', diffFile.revisionFrom]); let result = content.includes('<formula>'); return result; } public static async fetchFileListRevisionTo(revisionTo: string) { SFPLogger.log('Fetching file list from target revision ' + revisionTo, LoggerLevel.INFO); DiffUtil.gitTreeRevisionTo = []; let revisionTree = await git.raw(['ls-tree', '-r', revisionTo]); const sepRegex = /\n|\r/; let lines = revisionTree.split(sepRegex); for (let i = 0; i < lines.length; i++) { if (lines[i] === '') continue; let fields = lines[i].split(/\t/); let pathStr = fields[1]; let revisionSha = fields[0].split(/\s/)[2]; let oneFIle = { revision: revisionSha, path: path.join('.', pathStr), }; DiffUtil.gitTreeRevisionTo.push(oneFIle); } return DiffUtil.gitTreeRevisionTo; } public static async getRelativeFiles( filePath: string ): Promise< { revision: string; path: string; }[] > { let relativeFiles = []; let filePathParts = filePath.split(SEP); const statResourcesRegExp = new RegExp(METADATA_INFO.StaticResource.directoryName); const experienceBundleRegExp = new RegExp(METADATA_INFO.ExperienceBundle.directoryName); const auraRegExp = new RegExp(METADATA_INFO.AuraDefinitionBundle.directoryName); const lwcRegExp = new RegExp(METADATA_INFO.LightningComponentBundle.directoryName); if (filePath.endsWith('Translation-meta.xml') && filePath.indexOf('globalValueSet') < 0) { let parentFolder = filePathParts[filePathParts.length - 2]; let objectTranslation = parentFolder + METADATA_INFO.CustomObjectTranslation.sourceExtension; DiffUtil.gitTreeRevisionTo.forEach((file) => { //copy objectTranslation if fieldTranslation changes if (file.path === filePath || file.path.endsWith(objectTranslation)) { relativeFiles.push(file); } }); } else if ( statResourcesRegExp.test(filePath) || experienceBundleRegExp.test(filePath) || auraRegExp.test(filePath) || lwcRegExp.test(filePath) ) { // handle static recources let baseFile = ''; for (let i = 0; i < filePathParts.length; i++) { baseFile = path.join(baseFile, filePathParts[i]); if ( filePathParts[i] === METADATA_INFO.StaticResource.directoryName || filePathParts[i] === METADATA_INFO.ExperienceBundle.directoryName || filePathParts[i] === METADATA_INFO.AuraDefinitionBundle.directoryName || filePathParts[i] === METADATA_INFO.LightningComponentBundle.directoryName ) { let fileOrDirname = filePathParts[i + 1]; if (SOURCE_EXTENSION_REGEX.test(fileOrDirname)) { fileOrDirname = fileOrDirname.replace(SOURCE_EXTENSION_REGEX, ''); } else { let extension = path.parse(fileOrDirname).ext; fileOrDirname = fileOrDirname.replace(extension, ''); } baseFile = path.join(baseFile, fileOrDirname); break; } } DiffUtil.gitTreeRevisionTo.forEach((file) => { let fileToCompare = file.path; if (fileToCompare.startsWith(baseFile)) { relativeFiles.push(file); } }); } else { let baseFile = filePath; if (SOURCE_EXTENSION_REGEX.test(filePath)) { baseFile = filePath.replace(SOURCE_EXTENSION_REGEX, ''); } else { let extension = path.parse(filePath).ext; baseFile = filePath.replace(extension, ''); } DiffUtil.gitTreeRevisionTo.forEach((file) => { let fileToCompare = file.path; if (SOURCE_EXTENSION_REGEX.test(fileToCompare)) { fileToCompare = fileToCompare.replace(SOURCE_EXTENSION_REGEX, ''); } else { let extension = path.parse(fileToCompare).ext; fileToCompare = fileToCompare.replace(extension, ''); } if (baseFile === fileToCompare) { relativeFiles.push(file); } }); } return relativeFiles; } public static async copyFile(filePath: string, outputFolder: string) { SFPLogger.log(`Copying file ${filePath} from git to ${outputFolder}`, LoggerLevel.INFO); if (fs.existsSync(path.join(outputFolder, filePath))) { SFPLogger.log(`File ${filePath} already in output folder. `, LoggerLevel.TRACE); return; } let gitFiles = await DiffUtil.getRelativeFiles(filePath); let copyOutputFolder = outputFolder; for (let i = 0; i < gitFiles.length; i++) { outputFolder = copyOutputFolder; let gitFile = gitFiles[i]; SFPLogger.log(`Associated file ${i}: ${gitFile.path} Revision: ${gitFile.revision}`, LoggerLevel.TRACE); let outputPath = path.join(outputFolder, gitFile.path); let filePathParts = gitFile.path.split(SEP); if (fs.existsSync(outputFolder) == false) { fs.mkdirSync(outputFolder); } // Create folder structure for (let i = 0; i < filePathParts.length - 1; i++) { let folder = filePathParts[i].replace('"', ''); outputFolder = path.join(outputFolder, folder); if (fs.existsSync(outputFolder) == false) { fs.mkdirSync(outputFolder); } } let fileContent = await git.binaryCatFile(['-p', gitFile.revision]); fs.writeFileSync(outputPath, fileContent); } } public static async parseContent(fileContents): Promise<DiffFile> { const statusRegEx = /\sA\t|\sM\t|\sD\t/; const renamedRegEx = /\sR[0-9]{3}\t|\sC[0-9]{3}\t/; const tabRegEx = /\t/; const deletedFileRegEx = new RegExp(/\sD\t/); const lineBreakRegEx = /\r?\n|\r|( $)/; let metadataFiles = new MetadataFiles(); let diffFile: DiffFile = { deleted: [], addedEdited: [], }; for (let i = 0; i < fileContents.length; i++) { if (statusRegEx.test(fileContents[i])) { let lineParts = fileContents[i].split(statusRegEx); let finalPath = path.join('.', lineParts[1].replace(lineBreakRegEx, '')); finalPath = finalPath.trim(); finalPath = finalPath.replace('\\303\\251', 'é'); if (!(await metadataFiles.isInModuleFolder(finalPath))) { continue; } if (!metadataFiles.accepts(finalPath)) { continue; } let revisionPart = lineParts[0].split(/\t|\s/); if (deletedFileRegEx.test(fileContents[i])) { //Deleted diffFile.deleted.push({ revisionFrom: revisionPart[2].substring(0, 9), revisionTo: revisionPart[3].substring(0, 9), path: finalPath, }); } else { // Added or edited diffFile.addedEdited.push({ revisionFrom: revisionPart[2].substring(0, 9), revisionTo: revisionPart[3].substring(0, 9), path: finalPath, }); } } else if (renamedRegEx.test(fileContents[i])) { let lineParts = fileContents[i].split(renamedRegEx); let paths = lineParts[1].trim().split(tabRegEx); let finalPath = path.join('.', paths[1].trim()); finalPath = finalPath.replace('\\303\\251', 'é'); let revisionPart = lineParts[0].split(/\t|\s/); if (!(await metadataFiles.isInModuleFolder(finalPath))) { continue; } if (!metadataFiles.accepts(paths[0].trim())) { continue; } diffFile.addedEdited.push({ revisionFrom: '0000000', revisionTo: revisionPart[3], renamedPath: path.join('.', paths[0].trim()), path: finalPath, }); //allow deletion of renamed components diffFile.deleted.push({ revisionFrom: revisionPart[2], revisionTo: '0000000', path: paths[0].trim(), }); } } return diffFile; } public static getChangedOrAdded(list1: any[], list2: any[], key: string) { let result: any = { addedEdited: [], deleted: [], }; //Ensure array if (!_.isNil(list1) && !Array.isArray(list1)) { list1 = [list1]; } if (!_.isNil(list2) && !Array.isArray(list2)) { list2 = [list2]; } if (_.isNil(list1) && !_.isNil(list2) && list2.length > 0) { result.addedEdited.push(...list2); } if (_.isNil(list2) && !_.isNil(list1) && list1.length > 0) { result.deleted.push(...list1); } if (!_.isNil(list1) && !_.isNil(list2)) { list1.forEach((elem1) => { let found = false; for (let i = 0; i < list2.length; i++) { let elem2 = list2[i]; if (elem1[key] === elem2[key]) { //check if edited if (!_.isEqual(elem1, elem2)) { result.addedEdited.push(elem2); } found = true; break; } } if (!found) { result.deleted.push(elem1); } }); //Check for added elements let addedElement = _.differenceWith(list2, list1, function (element1: any, element2: any) { return element1[key] === element2[key]; }); if (!_.isNil(addedElement)) { result.addedEdited.push(...addedElement); } } return result; } public static addMemberToPackage(packageObj, name, member) { let typeIsPresent = false; for (let i = 0; i < packageObj.length; i++) { if (packageObj[i].name === name) { typeIsPresent = true; if (!packageObj[i].members.includes(member)) { packageObj[i].members.push(member); } break; } } let typeNode: any; if (typeIsPresent === false) { typeNode = { name: name, members: [member], }; packageObj.push(typeNode); } return packageObj; } }