Skip to content

Commit

Permalink
Merge pull request #14 from mcarvin8/beta
Browse files Browse the repository at this point in the history
Add Post Purge Flag, Rename Pre Purge Flag, and Use Fast XML Parser for Reassembly
  • Loading branch information
mcarvin8 authored Mar 10, 2024
2 parents 51981c3 + 325064c commit 685fc51
Show file tree
Hide file tree
Showing 12 changed files with 341 additions and 179 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ on:
push:
branches-ignore:
- main
- release
- beta

jobs:
build:
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# [1.1.0-beta.1](https://github.com/mcarvin8/xml-disassembler/compare/v1.0.10...v1.1.0-beta.1) (2024-03-10)

### Features

- rename `purge` flag to `prePurge` and add `postPurge` flag to disassemble class ([ff9fdc3](https://github.com/mcarvin8/xml-disassembler/commit/ff9fdc326aa745bdb0fcd88fb000de64e2624427))
- use fast xml parser for reassembly ([bf045a9](https://github.com/mcarvin8/xml-disassembler/commit/bf045a942e7a3d918999df8ff770255db2a5e785))

## [1.0.10](https://github.com/mcarvin8/xml-disassembler/compare/v1.0.9...v1.0.10) (2024-03-07)

### Bug Fixes
Expand Down
48 changes: 48 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# 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
```

## 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.
70 changes: 40 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,31 +29,11 @@ npm install xml-disassembler

## Disassembling Files

Import the `DisassembleXMLFileHandler` class from the package.

```typescript
/*
FLAGS
- xmlPath: Directory containing the XML files to disassemble (must be directory). This will only disassemble files in the immediate directory.
- 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.
- purge: (Optional) Boolean value. If set to true, purge pre-existing disassembled directories prior to disassembling the file.
Defaults to false.
*/
import { DisassembleXMLFileHandler } from "xml-disassembler";

const handler = new DisassembleXMLFileHandler();
await handler.disassemble({
xmlPath: "test/baselines/general",
uniqueIdElements:
"application,apexClass,name,externalDataSource,flow,object,apexPage,recordType,tab,field",
purge: true,
});
```
Disassemble 1 or multiple XML files in the immediate directory (`xmlPath`), without recursion. Each XML file will be disassembled into their own sub-directories using their base name (everything before the first `.` in the file-name).

Example:

An XML with the following nested and leaf elements
An XML file (`HR_Admin.permissionset-meta.xml`) with the following nested and leaf elements

```xml
<?xml version="1.0" encoding="UTF-8"?>
Expand Down Expand Up @@ -102,10 +82,10 @@ An XML with the following nested and leaf elements
</PermissionSet>
```

will be disassembled as such:
will be disassembled into a sub-directory named `HR_Admin` as such:

- Each nested element (`<recordTypeVisibilities>`, `<applicationVisibilities>`, `pageAccesses`, etc.) will be disassembled into sub-directories by the nested element name. If a unique & required ID element (`application` is the unique ID element for `<applicationVisibilities>`) is found, the disassembled file will be named using it. Otherwise, the disassembled files for nested elements will be named using the SHA-256 of the element contents.
- Each leaf element (`<description>`, `<label>`, `<userLicense>`) will be disassembled into the same file, which will have the same file-name as the original file.
- Each nested element (`<recordTypeVisibilities>`, `<applicationVisibilities>`, `pageAccesses`, etc.) will be disassembled into further sub-directories by the nested element name. If a unique & required ID element (`application` is the unique ID element for `<applicationVisibilities>`) is found, the disassembled file will be named using it. Otherwise, the disassembled files for nested elements will be named using the SHA-256 of the element contents.
- Each leaf element (`<description>`, `<label>`, `<userLicense>`) will be disassembled into the same file in the first sub-directory, which will have the same file-name as the original file.

<img src="https://raw.githubusercontent.com/mcarvin8/xml-disassembler/main/.github/images/disassembled.png">

Expand All @@ -115,8 +95,37 @@ will be disassembled as such:

<br>

Import the `DisassembleXMLFileHandler` class from the package.

```typescript
/*
FLAGS
- xmlPath: Directory containing the XML files to disassemble (must be directory). This will only disassemble files in the immediate directory.
- 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.
Defaults to false.
- postPurge: (Optional) Boolean value. If set to true, purge the original XML file after disassembling it.
Defaults to false.
*/
import { DisassembleXMLFileHandler } from "xml-disassembler";

const handler = new DisassembleXMLFileHandler();
await handler.disassemble({
xmlPath: "test/baselines/general",
uniqueIdElements:
"application,apexClass,name,externalDataSource,flow,object,apexPage,recordType,tab,field",
prePurge: true,
postPurge: true,
});
```

## Reassembling Files

Reassemble 1 XML directory (`xmlPath`) 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.

Import the `ReassembleXMLFileHandler` class from the package.

```typescript
Expand All @@ -134,10 +143,6 @@ await handler.reassemble({
});
```

_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.

## Use Case

Refer to the Salesforce plugin, [SFDX Decomposer](https://github.com/mcarvin8/sfdx-decomposer-plugin), to see a use case of this package:
Expand Down Expand Up @@ -177,7 +182,8 @@ await disassembleHandler.disassemble({
xmlPath: "test/baselines/general",
uniqueIdElements:
"application,apexClass,name,externalDataSource,flow,object,apexPage,recordType,tab,field",
purge: true,
prePurge: true,
postPurge: true,
});

const reassembleHandler = new ReassembleXMLFileHandler();
Expand All @@ -192,3 +198,7 @@ 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).
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "xml-disassembler",
"version": "1.0.10",
"version": "1.1.0-beta.1",
"description": "A JavaScript package to disassemble XML files into smaller, more manageable files and reassemble them when needed.",
"author": "Matt Carvin",
"license": "ISC",
Expand Down Expand Up @@ -36,6 +36,7 @@
"@semantic-release/github": "^9.2.6",
"@semantic-release/npm": "^11.0.2",
"@semantic-release/release-notes-generator": "^12.1.0",
"@types/fs-extra": "^11.0.4",
"@types/jest": "^29.5.11",
"@types/node": "^20.11.19",
"eslint": "^8.56.0",
Expand Down
22 changes: 22 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions src/service/buildDisassembledFiles.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use strict";

import * as fs from "node:fs";
import * as promise from "node:fs/promises";
import * as path from "node:path";
import { logger } from "@src/index";
import { XMLParser } from "fast-xml-parser";
Expand All @@ -16,6 +17,8 @@ export function buildDisassembledFiles(
uniqueIdElements: string | undefined,
baseName: string,
indent: string,
postPurge: boolean,
parentPath: string,
): void {
const xmlParser = new XMLParser(XML_PARSER_OPTION);
const result = xmlParser.parse(xmlString) as Record<string, XmlElement>;
Expand Down Expand Up @@ -105,6 +108,10 @@ export function buildDisassembledFiles(
fs.writeFileSync(leafOutputPath, leafFile);

logger.debug(`Created disassembled file: ${leafOutputPath}`);
if (postPurge) {
const originalFilePath = path.resolve(`${parentPath}/${baseName}.xml`);
promise.unlink(originalFilePath);
}
}
}

Expand Down
71 changes: 42 additions & 29 deletions src/service/disassembleXMLFileHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,55 +8,68 @@ export class DisassembleXMLFileHandler {
async disassemble(xmlAttributes: {
xmlPath: string;
uniqueIdElements?: string;
purge?: boolean;
prePurge?: boolean;
postPurge?: boolean;
}): Promise<void> {
const { xmlPath, uniqueIdElements, purge = false } = xmlAttributes;
const {
xmlPath,
uniqueIdElements,
prePurge = false,
postPurge = false,
} = xmlAttributes;
const fileStat = await fs.stat(xmlPath);

if (!fileStat.isDirectory()) {
logger.error(`The provided xmlPath ${xmlPath} is not a directory.`);
logger.error(
`The provided xmlPath ${xmlPath} to disassemble is not a directory.`,
);
return;
}
const files = await fs.readdir(xmlPath);
for (const file of files) {
const filePath = path.join(xmlPath, file);
await this.processFile({
xmlPath,
filePath,
uniqueIdElements,
purge,
});
if (filePath.endsWith(".xml")) {
await this.processFile({
xmlPath,
filePath,
uniqueIdElements,
prePurge,
postPurge,
});
}
}
}

async processFile(xmlAttributes: {
xmlPath: string;
filePath: string;
uniqueIdElements?: string;
purge?: boolean;
prePurge: boolean;
postPurge: boolean;
}): Promise<void> {
const { xmlPath, filePath, uniqueIdElements, purge } = xmlAttributes;

if (filePath.endsWith(".xml")) {
logger.debug(`Parsing file to disassemble: ${filePath}`);
const xmlContent = await fs.readFile(filePath, "utf-8");
const fullName = path.basename(filePath, path.extname(filePath));
const baseName = fullName.split(".")[0];
const { xmlPath, filePath, uniqueIdElements, prePurge, postPurge } =
xmlAttributes;

let outputPath;
outputPath = path.join(xmlPath, baseName);
logger.debug(`Parsing file to disassemble: ${filePath}`);
const xmlContent = await fs.readFile(filePath, "utf-8");
const fullName = path.basename(filePath, path.extname(filePath));
const baseName = fullName.split(".")[0];

if (purge) {
await fs.rm(outputPath, { recursive: true });
}
let outputPath;
outputPath = path.join(xmlPath, baseName);

buildDisassembledFiles(
xmlContent,
outputPath,
uniqueIdElements,
fullName,
INDENT,
);
if (prePurge) {
await fs.rm(outputPath, { recursive: true });
}

buildDisassembledFiles(
xmlContent,
outputPath,
uniqueIdElements,
fullName,
INDENT,
postPurge,
xmlPath,
);
}
}
Loading

0 comments on commit 685fc51

Please sign in to comment.