Skip to content

Commit

Permalink
fix: delta deploy strategy does not correctly fallback to default com…
Browse files Browse the repository at this point in the history
…parison when attempting XML compare on none XML file
  • Loading branch information
Codeneos committed Aug 23, 2023
1 parent f9c8cf3 commit d17cf6e
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 22 deletions.
25 changes: 10 additions & 15 deletions packages/salesforce/src/retrieveDeltaStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ export class RetrieveDeltaStrategy {
}

private isDataChanged(
packagePath: string,
localData: Buffer | string | undefined,
orgData: Buffer | string | undefined,
type: MetadataType): boolean
packagePath: string,
localData: Buffer | string | undefined,
orgData: Buffer | string | undefined,
type: MetadataType): boolean
{
if (Buffer.isBuffer(localData) && Buffer.isBuffer(orgData) && localData.compare(orgData) === 0) {
// If both are buffers first do a quick buffer comparison
Expand All @@ -118,39 +118,34 @@ export class RetrieveDeltaStrategy {
}

try {
return !this.getComparer(packagePath, type)(localData, orgData);
return !this.getComparer(packagePath, localData, type)(localData, orgData);
} catch {
return !this.compareStrategies.default(localData, orgData);
}
}

private getComparer(packagePath: string, type: MetadataType): CompareStrategy {
private getComparer(packagePath: string, data: string | Buffer, type: MetadataType): CompareStrategy {
if (/\.(cls|trigger)-meta\.xml$/i.test(packagePath)) {
return this.compareStrategies.metaXml;
}

if (/\.xml$/i.test(packagePath)) {
return this.compareStrategies.xml;
}

if (type.name === 'FlexiPage' ||
type.name === 'Layout')
{
return this.compareStrategies.xmlStrictOrder;
}

if (type.suffix && packagePath.endsWith(type.suffix)) {
// this covers most of the metadata files
return this.compareStrategies.xml;
}

if (type.name === 'StaticResource' ||
type.name === 'ContentAsset' ||
type.name === 'Document')
{
return this.compareStrategies.binary;
}

if (/\.xml$/i.test(packagePath) || XML.isXml(data)) {
return this.compareStrategies.xml;
}

return this.compareStrategies.default;
}

Expand Down
17 changes: 17 additions & 0 deletions packages/util/src/__tests__/xml.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,23 @@ describe('xml', () => {
<!-- test -->
<test><tag>test</tag></test>`)).toBe('test');
});
it('should return first tag for XML snippets without declaration', () => {
expect(XML.getRootTagName(
`<!-- Returns name of first none-comment root tag: rootTag -->
<rootTag>
<inner />
</rootTag`)).toBe('rootTag');
});
it('should return first tag for XML snippets prefixed with a comment', () => {
expect(XML.getRootTagName(
`\n\n<!-- test --> \n\n <test><tag>test</tag></test>`)).toBe('test');
});
it('should return undefined for XML snippet when requireDeclaration = true', () => {
expect(XML.getRootTagName(
`<test><tag>test</tag></test>`, {
requireDeclaration: true
})).toBe(undefined);
});
});
describe('#getNode', () => {
it('should get node based on property path', () => {
Expand Down
44 changes: 37 additions & 7 deletions packages/util/src/xml.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { XMLParser, XMLBuilder, X2jOptions, XmlBuilderOptions } from 'fast-xml-parser';
import { XMLParser, XMLBuilder, X2jOptions, XmlBuilderOptions, XMLValidator } from 'fast-xml-parser';
import { DOMParser } from '@xmldom/xmldom';
import { visitObject } from './object';

Expand Down Expand Up @@ -37,6 +37,13 @@ export interface XMLParseOptions {
* For attributes the path is prefixed with `@`, i.e. `rootTag.innerTag.@attr`
*/
valueProcessor?: (value: string, nodePath: string) => any;
/**
* When true the passed string will not be checked if it is a valid XML string.
* Set to true when you know the string is valid XML and you want to skip the validation.
* allowing for faster parsing of the XML.
* @default false
*/
skipValidation?: boolean;
}

export interface XMLStringfyOptions {
Expand All @@ -56,6 +63,12 @@ export interface TextRange {
end: TextPosition;
}

/**
* This namespaces contains normalized XML functions for parsing and stringifying XML.
* This namespace provides a normalized interface for the `fast-xml-parser` package allowing
* for a consistent interface across all Vlocode packages and making switching to a different
* XML parser easier abstracting away the differences between the different XML parsers.
*/
export namespace XML {

const options: Partial<X2jOptions & XmlBuilderOptions> = {
Expand Down Expand Up @@ -132,7 +145,7 @@ export namespace XML {
};
}

return visitObject(new XMLParser(parserOptions).parse(xml), (prop, value, target) => {
return visitObject(new XMLParser(parserOptions).parse(xml, !options.skipValidation), (prop, value, target) => {
if (typeof value === 'object') {
// Parse nil as null as per XML spec
if (value['$']?.['nil'] === true) {
Expand Down Expand Up @@ -179,15 +192,17 @@ export namespace XML {
* </rootTag
* ```
* @param xml XML string or buffer
* @param options.requireDeclaration When true the function will return undefined when the XML string does not start with a XML declaration
* @returns Name of the root tag in the XML file
*/
export function getRootTagName(xml: string | Buffer) {
export function getRootTagName(xml: string | Buffer, options?: { requireDeclaration?: boolean }) {
if (typeof xml !== 'string') {
xml = xml.toString();
}
if (xml.trimStart().startsWith('<?xml ')) {
return xml.match(/<([^-?!][\w\d]*)/im)?.[1];
if (options?.requireDeclaration && !xml.trimStart().startsWith('<?xml ')) {
return undefined;
}
return xml.match(/<([^-?!][\w\d]*)/im)?.[1];
}

/**
Expand All @@ -196,7 +211,22 @@ export namespace XML {
* @returns true when this the buffer has a valid XML declaration and root tag otherwise false
*/
export function isXml(xml: string | Buffer) {
return !!getRootTagName(xml);
if (typeof xml !== 'string') {
xml = xml.toString();
}
return XMLValidator.validate(xml) === true;
}

/**
* Validates if the specified string has a valid XML syntax structure.
* @param xml String or Buffer to validate, when a buffer is passed it iis converted to a string before parsing.
* @returns True if the string is valid XML otherwise false.
*/
export function isValid(xml: string | Buffer) {
if (typeof xml !== 'string') {
xml = xml.toString();
}
return XMLValidator.validate(xml) === true;
}

/**
Expand All @@ -205,7 +235,7 @@ export namespace XML {
* @returns normalized XML string without comments or line-breaks.
*/
export function normalize(xml: string | Buffer, options?: XMLStringfyOptions) {
return stringify(parse(xml, { trimValues: true }), 0, options);
return stringify(parse(xml, { arrayMode: true, trimValues: true }), 0, options);
}

/**
Expand Down

0 comments on commit d17cf6e

Please sign in to comment.