diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index ec0e37c..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,48 +0,0 @@ -# Contributing - -Any contributions you would like to make to this repository are encouraged. - -## Requirements - -``` - "node": ">=18", - "pnpm": ">=8" -``` - -## Setup - -1. Clone or fork the repository - -``` -git clone git@github.com:mcarvin8/xml-disassembler.git -``` - -2. Install dependencies - -``` -pnpm install -``` - -## Branching - -Please create a new feature branch before making changes. - -When your changes are ready for review, please create a Pull Request into the `main` branch on this repository. - -All feature branches will run the `Build` CI/CD workflow which will build and test (see `Testing` below) the code upon push. - -## Testing - -The test suite will disassemble and reassemble XMLs with different attributes and will test different class flags. - -Use the following command from the root directory: - -``` -pnpm test -``` - -The test suite will copy all of the files found in `test/baselines` into a new `mock` directory before running the tests. After each disassemble test, the original file should be deleted to confirm it is reassembled correctly (whether it's via the `--postPurge` Boolean flag or manually deleting it to confirm coverage without the flag). - -The final test in the suite should always be the comparison test. This test compares the `baseline` files against the `mock` files to confirm there are no changes. This will not compare files if they are only found in the `mock` directory (mostly disassembled files except for the error condition tests). - -Ensure when you are adding new code & tests that all code reaches full code coverage. diff --git a/README.md b/README.md index 38b9f46..8e74817 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ Import the `DisassembleXMLFileHandler` class from the package. ```typescript /* FLAGS -- xmlPath: Path to 1 XML file or a directory of XML files to disassemble. If the path provided is a directory, only the files in the immediate directory will be disassembled. +- filePath: Path to 1 XML file or a directory of XML files to disassemble. If the path provided is a directory, only the files in the immediate directory will be disassembled. - uniqueIdElements: (Optional) Comma-separated list of unique and required ID elements used to name disassembled files for nested elements. Defaults to SHA-256 hash if unique ID elements are undefined or not found. - prePurge: (Optional) Boolean value. If set to true, purge pre-existing disassembled directories prior to disassembling the file. @@ -112,7 +112,7 @@ import { DisassembleXMLFileHandler } from "xml-disassembler"; const handler = new DisassembleXMLFileHandler(); await handler.disassemble({ - xmlPath: "test/baselines/general", + filePath: "test/baselines/general", uniqueIdElements: "application,apexClass,name,externalDataSource,flow,object,apexPage,recordType,tab,field", prePurge: true, @@ -122,25 +122,25 @@ await handler.disassemble({ ## Reassembling Files -Reassemble 1 XML directory (`xmlPath`) containing disassembled files back into 1 XML file. +Reassemble 1 XML directory (`filePath`) containing disassembled files back into 1 XML file. -**NOTE**: You should be reassembling files created by this package's `DisassembleXMLFileHandler` class for intended results. This class will assume all disassembled files in `xmlPath` have the same XML Root Element. The reassembled XML file will be created in the parent directory of `xmlPath` and will overwrite the original file used to create the original disassembled directories, if it still exists and the `fileExtension` flag matches the original file extension. +**NOTE**: You should be reassembling files created by this package's `DisassembleXMLFileHandler` class for intended results. This class will assume all disassembled files in `filePath` have the same XML Root Element. The reassembled XML file will be created in the parent directory of `filePath` and will overwrite the original file used to create the original disassembled directories, if it still exists and the `fileExtension` flag matches the original file extension. Import the `ReassembleXMLFileHandler` class from the package. ```typescript /* FLAGS -- xmlPath: Path to the disassembled XML files to reassemble (must be a directory) +- filePath: Path to the disassembled XML files to reassemble (must be a directory) - fileExtension: (Optional) Desired file extension for the final XML (default: `.xml`) -- postPurge: (Optional) Boolean value. If set to true, purge the disassembled file directory (xmlPath) after reassembly. +- postPurge: (Optional) Boolean value. If set to true, purge the disassembled file directory (filePath) after reassembly. Defaults to false. */ import { ReassembleXMLFileHandler } from "xml-disassembler"; const handler = new ReassembleXMLFileHandler(); await handler.reassemble({ - xmlPath: "test/baselines/general/HR_Admin", + filePath: "test/baselines/general/HR_Admin", fileExtension: "permissionset-meta.xml", postPurge: true, }); @@ -204,7 +204,7 @@ if (debug) { const disassembleHandler = new DisassembleXMLFileHandler(); await disassembleHandler.disassemble({ - xmlPath: "test/baselines/general", + filePath: "test/baselines/general", uniqueIdElements: "application,apexClass,name,externalDataSource,flow,object,apexPage,recordType,tab,field", prePurge: true, @@ -213,7 +213,7 @@ await disassembleHandler.disassemble({ const reassembleHandler = new ReassembleXMLFileHandler(); await reassembleHandler.reassemble({ - xmlPath: "test/baselines/general/HR_Admin", + filePath: "test/baselines/general/HR_Admin", fileExtension: "permissionset-meta.xml", }); ``` @@ -223,7 +223,3 @@ await reassembleHandler.reassemble({ This project was created from a template provided by [Allan Oricil](https://github.com/AllanOricil). Thank you Allan! His original [license](https://github.com/AllanOricil/js-template/blob/main/LICENSE) remains in this project. - -## Contributing - -Any contributions you would like to make are appreciated. Please see [CONTRIBUTING](https://github.com/mcarvin8/xml-disassembler/blob/main/CONTRIBUTING.md). diff --git a/src/helpers/types.ts b/src/helpers/types.ts index 8d58da1..af7125c 100644 --- a/src/helpers/types.ts +++ b/src/helpers/types.ts @@ -28,7 +28,7 @@ export interface XmlElement { export interface ProcessElementParams { element: XmlElement; - metadataPath: string; + disassembledPath: string; uniqueIdElements: string | undefined; rootElementName: string; rootElementHeader: string; diff --git a/src/service/buildDisassembledFiles.ts b/src/service/buildDisassembledFiles.ts index 472184b..9be0d3d 100644 --- a/src/service/buildDisassembledFiles.ts +++ b/src/service/buildDisassembledFiles.ts @@ -10,14 +10,14 @@ import { buildLeafFile } from "@src/service/buildLeafFile"; import { parseXML } from "@src/service/parseXML"; export async function buildDisassembledFiles( - xmlPath: string, - metadataPath: string, + filePath: string, + disassembledPath: string, uniqueIdElements: string | undefined, baseName: string, indent: string, postPurge: boolean, ): Promise { - const parsedXml = await parseXML(xmlPath); + const parsedXml = await parseXML(filePath); if (parsedXml === undefined) return; const rootElementName = Object.keys(parsedXml)[1]; @@ -39,7 +39,7 @@ export async function buildDisassembledFiles( const [updatedLeafContent, updatedLeafCount, updatedHasNestedElements] = await processElement({ element, - metadataPath, + disassembledPath, uniqueIdElements, rootElementName, rootElementHeader, @@ -57,7 +57,7 @@ export async function buildDisassembledFiles( const [updatedLeafContent, updatedLeafCount, updatedHasNestedElements] = await processElement({ element: rootElement[key] as XmlElement, - metadataPath, + disassembledPath, uniqueIdElements, rootElementName, rootElementHeader, @@ -75,7 +75,7 @@ export async function buildDisassembledFiles( if (!hasNestedElements) { logger.error( - `The XML file ${xmlPath} only has leaf elements. This file will not be disassembled.`, + `The XML file ${filePath} only has leaf elements. This file will not be disassembled.`, ); return; } @@ -83,13 +83,13 @@ export async function buildDisassembledFiles( if (leafCount > 0) { await buildLeafFile( leafContent, - metadataPath, + disassembledPath, baseName, rootElementName, rootElementHeader, ); } if (postPurge) { - unlink(xmlPath); + unlink(filePath); } } diff --git a/src/service/buildLeafFile.ts b/src/service/buildLeafFile.ts index 6f7aa45..bd18d94 100644 --- a/src/service/buildLeafFile.ts +++ b/src/service/buildLeafFile.ts @@ -8,7 +8,7 @@ import { XML_HEADER } from "@src/helpers/constants"; export async function buildLeafFile( leafContent: string, - metadataPath: string, + disassembledPath: string, baseName: string, rootElementName: string, rootElementHeader: string, @@ -17,8 +17,8 @@ export async function buildLeafFile( leafFile += `${rootElementHeader}\n`; leafFile += leafContent; - leafFile += `\n`; - const leafOutputPath = join(metadataPath, `${baseName}.xml`); + leafFile += ``; + const leafOutputPath = join(disassembledPath, `${baseName}.xml`); await writeFile(leafOutputPath, leafFile); logger.debug(`Created disassembled file: ${leafOutputPath}`); diff --git a/src/service/buildNestedFiles.ts b/src/service/buildNestedFiles.ts index 24054aa..e72ef7d 100644 --- a/src/service/buildNestedFiles.ts +++ b/src/service/buildNestedFiles.ts @@ -12,7 +12,7 @@ import { buildRootElementHeader } from "@src/service/buildRootElementHeader"; export async function buildNestedFile( element: XmlElement, - metadataPath: string, + disassembledPath: string, uniqueIdElements: string | undefined, rootElementName: string, rootElementHeader: string, @@ -23,7 +23,7 @@ export async function buildNestedFile( const fieldName = findUniqueIdElement(element, uniqueIdElements); - const outputDirectory = join(metadataPath, parentKey); + const outputDirectory = join(disassembledPath, parentKey); const outputFileName: string = `${fieldName}.${parentKey}-meta.xml`; const outputPath = join(outputDirectory, outputFileName); diff --git a/src/service/buildReassembledFiles.ts b/src/service/buildReassembledFiles.ts index 76a9c0a..3345bf4 100644 --- a/src/service/buildReassembledFiles.ts +++ b/src/service/buildReassembledFiles.ts @@ -7,7 +7,7 @@ import { XML_HEADER, INDENT } from "@src/helpers/constants"; export async function buildReassembledFile( combinedXmlContents: string[], - filePath: string, + reassembledPath: string, xmlElement: string, xmlRootElementHeader: string | undefined, ): Promise { @@ -51,8 +51,8 @@ export async function buildReassembledFile( const closeTag = ``; await writeFile( - filePath, + reassembledPath, `${XML_HEADER}\n${xmlRootElementHeader}${finalXmlContent}${closeTag}`, ); - logger.debug(`Created reassembled file: ${filePath}`); + logger.debug(`Created reassembled file: ${reassembledPath}`); } diff --git a/src/service/disassembleXMLFileHandler.ts b/src/service/disassembleXMLFileHandler.ts index db4f951..c7cba78 100644 --- a/src/service/disassembleXMLFileHandler.ts +++ b/src/service/disassembleXMLFileHandler.ts @@ -10,41 +10,43 @@ import { buildDisassembledFiles } from "@src/service/buildDisassembledFiles"; export class DisassembleXMLFileHandler { async disassemble(xmlAttributes: { - xmlPath: string; + filePath: string; uniqueIdElements?: string; prePurge?: boolean; postPurge?: boolean; }): Promise { const { - xmlPath, + filePath, uniqueIdElements, prePurge = false, postPurge = false, } = xmlAttributes; - const fileStat = await stat(xmlPath); + const fileStat = await stat(filePath); if (fileStat.isFile()) { - const filePath = resolve(xmlPath); - if (!filePath.endsWith(".xml")) { - logger.error(`The file path ${filePath} is not an XML file.`); + const resolvedPath = resolve(filePath); + if (!resolvedPath.endsWith(".xml")) { + logger.error( + `The file path provided is not an XML file: ${resolvedPath}`, + ); return; } - const basePath = dirname(filePath); + const dirPath = dirname(resolvedPath); await this.processFile({ - xmlPath: basePath, - filePath, + dirPath, + filePath: resolvedPath, uniqueIdElements, prePurge, postPurge, }); } else if (fileStat.isDirectory()) { - const files = await readdir(xmlPath); - for (const file of files) { - const filePath = join(xmlPath, file); - if (filePath.endsWith(".xml")) { + const subFiles = await readdir(filePath); + for (const subFile of subFiles) { + const subFilePath = join(filePath, subFile); + if (subFilePath.endsWith(".xml")) { await this.processFile({ - xmlPath, - filePath, + dirPath: filePath, + filePath: subFilePath, uniqueIdElements, prePurge, postPurge, @@ -55,13 +57,13 @@ export class DisassembleXMLFileHandler { } async processFile(xmlAttributes: { - xmlPath: string; + dirPath: string; filePath: string; uniqueIdElements?: string; prePurge: boolean; postPurge: boolean; }): Promise { - const { xmlPath, filePath, uniqueIdElements, prePurge, postPurge } = + const { dirPath, filePath, uniqueIdElements, prePurge, postPurge } = xmlAttributes; logger.debug(`Parsing file to disassemble: ${filePath}`); @@ -69,7 +71,7 @@ export class DisassembleXMLFileHandler { const baseName = fullName.split(".")[0]; let outputPath; - outputPath = join(xmlPath, baseName); + outputPath = join(dirPath, baseName); if (prePurge && existsSync(outputPath)) await rm(outputPath, { recursive: true }); diff --git a/src/service/processElement.ts b/src/service/processElement.ts index 173df65..0ebba3f 100644 --- a/src/service/processElement.ts +++ b/src/service/processElement.ts @@ -8,7 +8,7 @@ export async function processElement( ): Promise<[string, number, boolean]> { const { element, - metadataPath, + disassembledPath, uniqueIdElements, rootElementName, rootElementHeader, @@ -22,7 +22,7 @@ export async function processElement( if (typeof element === "object") { await buildNestedFile( element, - metadataPath, + disassembledPath, uniqueIdElements, rootElementName, rootElementHeader, diff --git a/src/service/reassembleXMLFileHandler.ts b/src/service/reassembleXMLFileHandler.ts index a819b8a..8358656 100644 --- a/src/service/reassembleXMLFileHandler.ts +++ b/src/service/reassembleXMLFileHandler.ts @@ -45,44 +45,44 @@ export class ReassembleXMLFileHandler { } async reassemble(xmlAttributes: { - xmlPath: string; + filePath: string; fileExtension?: string; postPurge?: boolean; }): Promise { - const { xmlPath, fileExtension, postPurge = false } = xmlAttributes; + const { filePath, fileExtension, postPurge = false } = xmlAttributes; let combinedXmlContents: string[] = []; - const fileStat = await stat(xmlPath); + const fileStat = await stat(filePath); if (!fileStat.isDirectory()) { logger.error( - `The provided xmlPath ${xmlPath} to reassemble is not a directory.`, + `The provided path to reassemble is not a directory: ${filePath}`, ); return; } - logger.debug(`Parsing directory to reassemble: ${xmlPath}`); + logger.debug(`Parsing directory to reassemble: ${filePath}`); const [subCombinedXmlContents, rootResult] = - await this.processFilesInDirectory(xmlPath); + await this.processFilesInDirectory(filePath); combinedXmlContents = subCombinedXmlContents; - const parentDirectory = dirname(xmlPath); - const subdirectoryBasename = basename(xmlPath); + const parentDirectory = dirname(filePath); + const subdirectoryBasename = basename(filePath); const fileName = fileExtension ? `${subdirectoryBasename}.${fileExtension}` : `${subdirectoryBasename}.xml`; - const filePath = join(parentDirectory, fileName); + const outputPath = join(parentDirectory, fileName); if (rootResult !== undefined) { const [rootElementName, rootElementHeader] = rootResult; await buildReassembledFile( combinedXmlContents, - filePath, + outputPath, rootElementName, rootElementHeader, ); - if (postPurge) await rm(xmlPath, { recursive: true }); + if (postPurge) await rm(filePath, { recursive: true }); } else { logger.error( - `No files under ${xmlPath} were parsed successfully. A reassembled XML file was not created.`, + `No files under ${filePath} were parsed successfully. A reassembled XML file was not created.`, ); } } diff --git a/test/main.spec.ts b/test/main.spec.ts index 2172a7e..21007d0 100644 --- a/test/main.spec.ts +++ b/test/main.spec.ts @@ -46,7 +46,7 @@ describe("main function", () => { it('should disassemble a general XML file (nested and leaf elements) with unique ID elements."', async () => { await disassembleHandler.disassemble({ - xmlPath: "mock/general", + filePath: "mock/general", uniqueIdElements: "application,apexClass,name,externalDataSource,flow,object,apexPage,recordType,tab,field", postPurge: true, @@ -56,7 +56,7 @@ describe("main function", () => { }); it('should reassemble a general XML file (nested and leaf elements) with a namespace and alternate file extension."', async () => { await reassembleHandler.reassemble({ - xmlPath: "mock/general/HR_Admin", + filePath: "mock/general/HR_Admin", fileExtension: "permissionset-meta.xml", }); @@ -64,7 +64,7 @@ describe("main function", () => { }); it("should disassemble an XML file with attributes in the root element.", async () => { await disassembleHandler.disassemble({ - xmlPath: "mock/attributes", + filePath: "mock/attributes", postPurge: true, }); @@ -72,14 +72,14 @@ describe("main function", () => { }); it("should reassemble an XML file with attributes in the root element", async () => { await reassembleHandler.reassemble({ - xmlPath: "mock/attributes/notes", + filePath: "mock/attributes/notes", }); expect(logger.error).not.toHaveBeenCalled(); }); it("should disassemble a XML file with CDATA.", async () => { await disassembleHandler.disassemble({ - xmlPath: "mock/cdata", + filePath: "mock/cdata", uniqueIdElements: "apiName", postPurge: true, }); @@ -88,7 +88,7 @@ describe("main function", () => { }); it("should reassemble a XML file with CDATA and use the default file extension.", async () => { await reassembleHandler.reassemble({ - xmlPath: "mock/cdata/VidLand_US", + filePath: "mock/cdata/VidLand_US", }); // rename file manually to confirm file is identical to baseline @@ -101,7 +101,7 @@ describe("main function", () => { }); it("should disassemble a XML file with comments and an invalid unique ID element.", async () => { await disassembleHandler.disassemble({ - xmlPath: "mock/comments", + filePath: "mock/comments", uniqueIdElements: "invalid", postPurge: true, }); @@ -110,7 +110,7 @@ describe("main function", () => { }); it("should reassemble a XML file with comments.", async () => { await reassembleHandler.reassemble({ - xmlPath: "mock/comments/Numbers-fr", + filePath: "mock/comments/Numbers-fr", fileExtension: "globalValueSetTranslation-meta.xml", }); @@ -118,7 +118,7 @@ describe("main function", () => { }); it("should disassemble a XML file with a deeply nested unique ID element.", async () => { await disassembleHandler.disassemble({ - xmlPath: "mock/deeply-nested-unique-id-element", + filePath: "mock/deeply-nested-unique-id-element", uniqueIdElements: "apexClass,name,object,field,layout,actionName,targetReference,assignToReference,choiceText,promptText", postPurge: true, @@ -128,7 +128,7 @@ describe("main function", () => { }); it("should reassemble a XML file with a deeply nested unique ID element.", async () => { await reassembleHandler.reassemble({ - xmlPath: "mock/deeply-nested-unique-id-element/Get_Info", + filePath: "mock/deeply-nested-unique-id-element/Get_Info", fileExtension: "flow-meta.xml", }); @@ -136,7 +136,7 @@ describe("main function", () => { }); it("should disassemble a XML file with an array of leaf elements and no defined unique ID element.", async () => { await disassembleHandler.disassemble({ - xmlPath: "mock/array-of-leafs", + filePath: "mock/array-of-leafs", postPurge: true, }); @@ -144,7 +144,7 @@ describe("main function", () => { }); it("should reassemble a XML file with an array of leaf elements and no defined unique ID element.", async () => { await reassembleHandler.reassemble({ - xmlPath: "mock/array-of-leafs/Dreamhouse", + filePath: "mock/array-of-leafs/Dreamhouse", fileExtension: "app-meta.xml", }); @@ -152,7 +152,7 @@ describe("main function", () => { }); it("should purge the existing disassembled directory before disassembling the file.", async () => { await disassembleHandler.disassemble({ - xmlPath: "mock/array-of-leafs", + filePath: "mock/array-of-leafs", prePurge: true, postPurge: true, }); @@ -161,7 +161,7 @@ describe("main function", () => { }); it("should reassemble the files from the previous test (prePurge) and delete the disassemble files afterwards.", async () => { await reassembleHandler.reassemble({ - xmlPath: "mock/array-of-leafs/Dreamhouse", + filePath: "mock/array-of-leafs/Dreamhouse", fileExtension: "app-meta.xml", postPurge: true, }); @@ -170,7 +170,7 @@ describe("main function", () => { }); it("should disassemble a XML file with no namespace.", async () => { await disassembleHandler.disassemble({ - xmlPath: "mock/no-namespace/HR_Admin.permissionset-meta.xml", + filePath: "mock/no-namespace/HR_Admin.permissionset-meta.xml", uniqueIdElements: "application,apexClass,name,externalDataSource,flow,object,apexPage,recordType,tab,field", }); @@ -183,7 +183,7 @@ describe("main function", () => { }); it("should reassemble a XML file with no namespace.", async () => { await reassembleHandler.reassemble({ - xmlPath: "mock/no-namespace/HR_Admin", + filePath: "mock/no-namespace/HR_Admin", fileExtension: "permissionset-meta.xml", }); @@ -195,35 +195,35 @@ describe("main function", () => { const fakeFileContents = "Testing error condition."; await writeFile(fakeFile, fakeFileContents); await disassembleHandler.disassemble({ - xmlPath: fakeFile, + filePath: fakeFile, }); await rm(fakeFile); expect(logger.error).toHaveBeenCalled(); }); it("should test reassemble error condition (file path provided).", async () => { await reassembleHandler.reassemble({ - xmlPath: "mock/no-namespace/HR_Admin/HR_Admin.permissionset-meta.xml", + filePath: "mock/no-namespace/HR_Admin/HR_Admin.permissionset-meta.xml", }); expect(logger.error).toHaveBeenCalled(); }); it("should test disassemble error condition (no root element in XML).", async () => { await disassembleHandler.disassemble({ - xmlPath: "mock/no-root-element", + filePath: "mock/no-root-element", }); expect(logger.error).toHaveBeenCalled(); }); it("should test reassemble error condition (no root element in XML).", async () => { await reassembleHandler.reassemble({ - xmlPath: "mock/no-root-element/Assessment_Bot", + filePath: "mock/no-root-element/Assessment_Bot", }); expect(logger.error).toHaveBeenCalled(); }); it("should test disassemble error condition (XML file only has leaf elements).", async () => { await disassembleHandler.disassemble({ - xmlPath: "mock/no-nested-elements", + filePath: "mock/no-nested-elements", }); expect(logger.error).toHaveBeenCalled();