Skip to content

Commit

Permalink
feat: convert and merge decomposed component types (#184)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bryan Powell authored and lcampos committed Oct 23, 2020
1 parent a6510c5 commit a3b1bc3
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 33 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
]
},
"volta": {
"node": "12.4.0",
"node": "12.16.0",
"yarn": "1.22.4"
},
"config": {
Expand Down
13 changes: 9 additions & 4 deletions src/common/componentSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,18 @@ import { MetadataComponent } from './types';
export class ComponentSet<T extends MetadataComponent> {
private map = new Map<string, T>();

constructor(components: Iterable<T>) {
for (const component of components) {
const key = `${component.type.id}.${component.fullName}`;
this.map.set(key, component);
constructor(components?: Iterable<T>) {
if (components) {
for (const component of components) {
this.map.set(this.key(component), component);
}
}
}

public add(component: T): void {
this.map.set(this.key(component), component);
}

public get(component: MetadataComponent): T | undefined {
return this.map.get(this.key(component));
}
Expand Down
4 changes: 3 additions & 1 deletion src/convert/metadataConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ export class MetadataConverter {
if (!isSource) {
throw new LibraryError('error_merge_metadata_target_unsupported');
}
mergeSet = new ComponentSet(output.mergeWith);
mergeSet = new ComponentSet();
// since child components are composed in metadata format, we need to merge using the parent
output.mergeWith.forEach((component) => mergeSet.add(component.parent || component));
writer = new StandardWriter(output.defaultDirectory);
break;
}
Expand Down
91 changes: 65 additions & 26 deletions src/convert/transformers/decomposedMetadataTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ import {
import { JsonMap, AnyJson, JsonArray } from '@salesforce/ts-types';
import { JsToXml } from '../streams';
import { join } from 'path';
import { META_XML_SUFFIX, XML_NS_URL, XML_NS_KEY } from '../../common';
import {
ComponentSet,
MetadataType,
SourcePath,
META_XML_SUFFIX,
XML_NS_URL,
XML_NS_KEY,
} from '../../common';

interface XmlJson extends JsonMap {
[parentFullName: string]: {
Expand Down Expand Up @@ -88,52 +95,84 @@ export class DecomposedMetadataTransformer extends BaseMetadataTransformer {
return DecomposedMetadataTransformer.createWriterFormat(component, recomposedXmlObj);
}

public async toSourceFormat(component: SourceComponent): Promise<WriterFormat> {
public async toSourceFormat(
component: SourceComponent,
mergeWith?: SourceComponent
): Promise<WriterFormat> {
const writeInfos: WriteInfo[] = [];

const { type, fullName: parentFullName } = component;
const rootPackagePath = component.getPackageRelativePath(parentFullName, 'source');
const composedMetadata = (await component.parseXml())[type.name];
const rootXmlObject: XmlJson = { [type.name]: {} };
const parentXmlObject: XmlJson = { [type.name]: {} };

let childrenOnlyTags = true;
for (const [tagName, collection] of Object.entries(composedMetadata)) {
const childTypeId = type?.children?.directories[tagName];
let createParentXml = false;
const rootPackagePath = component.getPackageRelativePath(parentFullName, 'source');
const childComponentMergeSet = mergeWith
? new ComponentSet(mergeWith.getChildren())
: undefined;

const composedMetadata = await this.getComposedMetadataEntries(component);
for (const [tagKey, tagValue] of composedMetadata) {
const childTypeId = type.children?.directories[tagKey];
if (childTypeId) {
const childType = type.children.types[childTypeId];
const tagCollection = Array.isArray(collection) ? collection : [collection];

for (const entry of tagCollection) {
let output = rootPackagePath;
const strategy = this.registry.strategies[type.id].decomposition as DecompositionStrategy;
if (strategy === DecompositionStrategy.FolderPerType) {
output = join(output, childType.directoryName);
}

const name = (entry.fullName || entry.name) as string;
output = join(output, `${name}.${childType.suffix}${META_XML_SUFFIX}`);
const tagValues = Array.isArray(tagValue) ? tagValue : [tagValue];
for (const value of tagValues) {
const entryName = (value.fullName || value.name) as string;
const mergeChild = childComponentMergeSet?.get({
fullName: `${parentFullName}.${entryName}`,
type: childType,
});
const output =
mergeChild?.xml ||
join(rootPackagePath, this.getOutputPathForEntry(entryName, childType, component));

writeInfos.push({
source: new JsToXml({
[childType.name]: Object.assign({ [XML_NS_KEY]: XML_NS_URL }, entry),
[childType.name]: Object.assign({ [XML_NS_KEY]: XML_NS_URL }, value),
}),
output,
});
}
} else {
childrenOnlyTags = false;
rootXmlObject[type.name][tagName] = collection as JsonArray;
// tag entry isn't a child type, so add it to the parent xml
if (tagKey !== XML_NS_KEY) {
createParentXml = true;
}
parentXmlObject[type.name][tagKey] = tagValue as JsonArray;
}
}

if (!childrenOnlyTags) {
if (createParentXml) {
const parentOutput =
mergeWith?.xml ||
join(rootPackagePath, `${parentFullName}.${type.suffix}${META_XML_SUFFIX}`);
writeInfos.push({
source: new JsToXml(rootXmlObject),
output: join(rootPackagePath, `${parentFullName}.${type.suffix}${META_XML_SUFFIX}`),
source: new JsToXml(parentXmlObject),
output: parentOutput,
});
}

return { component, writeInfos };
}

private async getComposedMetadataEntries(component: SourceComponent): Promise<[string, any][]> {
const composedMetadata = (await component.parseXml())[component.type.name];
return Object.entries(composedMetadata);
}

private getOutputPathForEntry(
entryName: string,
entryType: MetadataType,
component: SourceComponent
): SourcePath {
const { type } = component;
const strategy = this.registry.strategies[type.id].decomposition as DecompositionStrategy;

let output = `${entryName}.${entryType.suffix}${META_XML_SUFFIX}`;

if (strategy === DecompositionStrategy.FolderPerType) {
output = join(entryType.directoryName, output);
}

return output;
}
}
1 change: 0 additions & 1 deletion src/metadata-registry/treeContainers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ export class VirtualTreeContainer extends BaseTreeContainer {
if (this.isDirectory(fsPath)) {
return Array.from(this.tree.get(fsPath)).map((p) => basename(p));
}

throw new LibraryError('error_expected_directory_path', fsPath);
}

Expand Down
6 changes: 6 additions & 0 deletions test/common/componentSet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,10 @@ describe('ComponentSet', () => {
it('should correctly test component membership', () => {
expect(set.has(dupeComponent)).to.be.true;
});

it('should add a component', () => {
const set = new ComponentSet();
set.add(dupeComponent);
expect(Array.from(set.values())).to.deep.equal([dupeComponent]);
});
});
76 changes: 76 additions & 0 deletions test/convert/transformers/decomposedMetadataTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { createSandbox } from 'sinon';
import { join } from 'path';
import { JsToXml } from '../../../src/convert/streams';
import { DECOMPOSED_TOP_LEVEL_COMPONENT } from '../../mock/registry/decomposedTopLevelConstants';
import { SourceComponent } from '../../../src';
import { XML_NS_URL, XML_NS_KEY } from '../../../src/common';

const env = createSandbox();
Expand Down Expand Up @@ -275,5 +276,80 @@ describe('DecomposedMetadataTransformer', () => {
],
});
});

it('should merge output with merge component only containing children', async () => {
const mergeComponentChild = component.getChildren()[0];
const componentToConvert = SourceComponent.createVirtualComponent(
{
name: 'a',
type: mockRegistry.types.reginaking,
},
[]
);
env.stub(componentToConvert, 'parseXml').resolves({
ReginaKing: {
[XML_NS_KEY]: XML_NS_URL,
[mergeComponentChild.type.directoryName]: {
fullName: mergeComponentChild.name,
test: 'testVal',
},
},
});

const transformer = new DecomposedMetadataTransformer(mockRegistry);
const result = await transformer.toSourceFormat(componentToConvert, component);

expect(result).to.deep.equal({
component: componentToConvert,
writeInfos: [
{
source: new JsToXml({
[mergeComponentChild.type.name]: {
[XML_NS_KEY]: XML_NS_URL,
fullName: mergeComponentChild.name,
test: 'testVal',
},
}),
output: mergeComponentChild.xml,
},
],
});
});

it('should merge output with parent merge component', async () => {
const componentToConvert = SourceComponent.createVirtualComponent(
{
name: 'a',
type: mockRegistry.types.reginaking,
},
[]
);
env.stub(componentToConvert, 'parseXml').resolves({
ReginaKing: {
[XML_NS_KEY]: XML_NS_URL,
fullName: component.fullName,
foo: 'bar',
},
});

const transformer = new DecomposedMetadataTransformer(mockRegistry);
const result = await transformer.toSourceFormat(componentToConvert, component);

expect(result).to.deep.equal({
component: componentToConvert,
writeInfos: [
{
source: new JsToXml({
[component.type.name]: {
[XML_NS_KEY]: XML_NS_URL,
fullName: component.fullName,
foo: 'bar',
},
}),
output: component.xml,
},
],
});
});
});
});
8 changes: 8 additions & 0 deletions test/metadata-registry/treeContainers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,14 @@ describe('Tree Containers', () => {
it('should return directory entries for readDirectory', () => {
expect(tree.readDirectory('.')).to.deep.equal(virtualFS[0].children);
});

it('should throw an error if path is not a directory', () => {
assert.throws(
() => tree.readDirectory('test.txt'),
LibraryError,
nls.localize('error_expected_directory_path', 'test.txt')
);
});
});

describe('readFile', () => {
Expand Down

0 comments on commit a3b1bc3

Please sign in to comment.