Skip to content

Support split CustomLabels #273

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/client/metadataApiRetrieve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ export class MetadataApiRetrieve extends MetadataTransfer<
protected async checkStatus(id: string): Promise<MetadataApiRetrieveStatus> {
const connection = await this.getConnection();
// Recasting to use the project's RetrieveResult type
return (connection.metadata.checkRetrieveStatus(id) as unknown) as Promise<
MetadataApiRetrieveStatus
>;
const status = await connection.metadata.checkRetrieveStatus(id);
status.fileProperties = normalizeToArray(status.fileProperties);
return status as MetadataApiRetrieveStatus;
}

protected async post(result: MetadataApiRetrieveStatus): Promise<RetrieveResult> {
Expand Down
2 changes: 1 addition & 1 deletion src/collections/componentSet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ export class ComponentSet extends LazyCollection<MetadataComponent> {
indentBy: new Array(indentation + 1).join(' '),
ignoreAttributes: false,
});
const toParse = this.getObject() as any;
const toParse = this.getObject();
toParse.Package[XML_NS_KEY] = XML_NS_URL;
return XML_DECL.concat(j2x.parse(toParse));
}
Expand Down
3 changes: 2 additions & 1 deletion src/collections/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { ComponentLike } from '../common';
import { ComponentLike, XML_NS_KEY } from '../common';
import { RegistryAccess, TreeContainer } from '../metadata-registry';
import { ComponentSet } from './componentSet';

Expand All @@ -17,6 +17,7 @@ export interface PackageManifestObject {
Package: {
types: PackageTypeMembers[];
version: string;
[XML_NS_KEY]?: string;
};
}

Expand Down
4 changes: 4 additions & 0 deletions src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export type MetadataType = {
adapter: string;
transformer?: string;
decomposition?: string;
elementParser?: {
xmlPath: string;
nameAttr: string;
};
};
};

Expand Down
3 changes: 3 additions & 0 deletions src/convert/transformers/metadataTransformerFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { DecomposedMetadataTransformer } from './decomposedMetadataTransformer';
import { ConvertContext } from '../convertContext';
import { StaticResourceMetadataTransformer } from './staticResourceMetadataTransformer';
import { RegistryAccess, TransformerStrategy } from '../../metadata-registry';
import { NonDecomposedMetadataTransformer } from './nonDecomposedMetadataTransformer';

export class MetadataTransformerFactory {
private registry: RegistryAccess;
Expand All @@ -34,6 +35,8 @@ export class MetadataTransformerFactory {
return new DecomposedMetadataTransformer(this.registry, this.context);
case TransformerStrategy.StaticResource:
return new StaticResourceMetadataTransformer(this.registry, this.context);
case TransformerStrategy.NonDecomposed:
return new NonDecomposedMetadataTransformer(this.registry, this.context);
default:
throw new RegistryError('error_missing_transformer', [type.name, transformerId]);
}
Expand Down
56 changes: 56 additions & 0 deletions src/convert/transformers/nonDecomposedMetadataTransformer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { JsToXml } from '../streams';
import { NonDecomposedIndex } from '../../utils/nonDecomposedIndex';
import { normalizeToArray } from '../../utils';
import { set } from '@salesforce/kit';
import { SourceComponent } from '../../metadata-registry';
import { WriteInfo } from '../types';
import deepmerge = require('deepmerge');
import { DecomposedMetadataTransformer } from './decomposedMetadataTransformer';
import { get, JsonMap } from '@salesforce/ts-types';

/**
* Metadata Transformer for metadata types with children types that are NOT decomposed into separate files.
*
* Example Types:
* - CustomLabels
*/
export class NonDecomposedMetadataTransformer extends DecomposedMetadataTransformer {
public async toSourceFormat(
component: SourceComponent,
mergeWith?: SourceComponent
): Promise<WriteInfo[]> {
const allLabelsObj = await component.parseXml();
const index = await NonDecomposedIndex.getInstance().resolve(mergeWith);

const writeInfos: WriteInfo[] = [];
for (const [fsPath, elements] of [...index.entries()]) {
const nonDecomposedObj = deepmerge({}, allLabelsObj);
const filteredElements = this.filterElements(component, nonDecomposedObj, elements);
if (filteredElements.length) {
set(nonDecomposedObj, component.type.strategies.elementParser.xmlPath, filteredElements);
writeInfos.push({ source: new JsToXml(nonDecomposedObj), output: fsPath });
}
}
return writeInfos;
}

/**
* Only return elements that belong to the component
*/
private filterElements(
component: SourceComponent,
nonDecomposedObj: JsonMap,
elements: string[]
): JsonMap[] {
const { xmlPath, nameAttr } = component.type.strategies.elementParser;
const unfiltered = normalizeToArray(get(nonDecomposedObj, xmlPath, []) as JsonMap[]);
return unfiltered.filter((element) => elements.includes(element[nameAttr] as string));
}
}
1 change: 1 addition & 0 deletions src/metadata-registry/adapters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ export { BundleSourceAdapter } from './bundleSourceAdapter';
export { MixedContentSourceAdapter } from './mixedContentSourceAdapter';
export { DecomposedSourceAdapter } from './decomposedSourceAdapter';
export { DefaultSourceAdapter } from './defaultSourceAdapter';
export { NonDecomposedSourceAdapter } from './nonDecomposedSourceAdapter';
25 changes: 25 additions & 0 deletions src/metadata-registry/adapters/nonDecomposedSourceAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { DefaultSourceAdapter } from './defaultSourceAdapter';
import { SourcePath } from '../../common';
import { SourceComponent } from '../sourceComponent';
import { NonDecomposedIndex } from '../../utils/nonDecomposedIndex';

/**
* Handles CustomLabels and CustomLabel metadata types
*/
export class NonDecomposedSourceAdapter extends DefaultSourceAdapter {
protected metadataWithContent = false;

protected populate(trigger: SourcePath, component: SourceComponent): SourceComponent {
const index = NonDecomposedIndex.getInstance();
if (trigger.endsWith('-meta.xml')) {
index.register(trigger, component);
}
return component;
}
}
9 changes: 8 additions & 1 deletion src/metadata-registry/adapters/sourceAdapterFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ import { RegistryError } from '../../errors';
import { ForceIgnore } from '../forceIgnore';
import { MetadataType } from '../../common';
import { RegistryAccess } from '../registryAccess';
import { NonDecomposedSourceAdapter } from './nonDecomposedSourceAdapter';

enum AdapterId {
Bundle = 'bundle',
Decomposed = 'decomposed',
Default = 'default',
MatchingContentFile = 'matchingContentFile',
MixedContent = 'mixedContent',
NonDecomposed = 'nonDecomposed',
}

export class SourceAdapterFactory {
Expand All @@ -42,10 +45,14 @@ export class SourceAdapterFactory {
return new MatchingContentSourceAdapter(type, this.registry, forceIgnore, this.tree);
case AdapterId.MixedContent:
return new MixedContentSourceAdapter(type, this.registry, forceIgnore, this.tree);
case AdapterId.Default:
return new DefaultSourceAdapter(type, this.registry, forceIgnore, this.tree);
case AdapterId.NonDecomposed:
return new NonDecomposedSourceAdapter(type, this.registry, forceIgnore, this.tree);
case undefined:
return new DefaultSourceAdapter(type, this.registry, forceIgnore, this.tree);
default:
throw new RegistryError('error_missing_adapter', [type.name, adapterId]);
throw new RegistryError('error_missing_adapter', [adapterId, type.name]);
}
}
}
10 changes: 9 additions & 1 deletion src/metadata-registry/data/registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@
"directories": {
"customLabels": "customlabel"
}
},
"strategies": {
"adapter": "nonDecomposed",
"transformer": "nonDecomposed",
"elementParser": {
"xmlPath": "CustomLabels.labels",
"nameAttr": "fullName"
}
}
},
"navigationmenu": {
Expand Down Expand Up @@ -2158,4 +2166,4 @@
"botversion": "bot"
},
"apiVersion": "51.0"
}
}
7 changes: 3 additions & 4 deletions src/metadata-registry/sourceComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { baseName } from '../utils';
import { NodeFSTreeContainer, VirtualTreeContainer } from './treeContainers';
import { DEFAULT_PACKAGE_ROOT_SFDX, MetadataType, SourcePath, MetadataComponent } from '../common';
import { JsonMap } from '@salesforce/ts-types';
import { LibraryError } from '../errors';
import { SfdxFileFormat } from '../convert';
import { trimUntil } from '../utils/path';

Expand Down Expand Up @@ -78,12 +77,12 @@ export class SourceComponent implements MetadataComponent {
: [];
}

public async parseXml(): Promise<JsonMap> {
public async parseXml<T = JsonMap>(): Promise<T> {
if (this.xml) {
const contents = await this.tree.readFile(this.xml);
return parse(contents.toString(), { ignoreAttributes: false });
return parse(contents.toString(), { ignoreAttributes: false }) as T;
}
return {};
return {} as T;
}

public getPackageRelativePath(fsPath: SourcePath, format: SfdxFileFormat): SourcePath {
Expand Down
1 change: 1 addition & 0 deletions src/metadata-registry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const enum TransformerStrategy {
Standard = 'standard',
Decomposed = 'decomposed',
StaticResource = 'staticResource',
NonDecomposed = 'nonDecomposed',
}

/**
Expand Down
73 changes: 73 additions & 0 deletions src/utils/nonDecomposedIndex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright (c) 2021, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/

import { get, JsonMap } from '@salesforce/ts-types';
import { SourceComponent } from '../metadata-registry/sourceComponent';
import { normalizeToArray } from './collections';

export type CustomLabelsObj = {
CustomLabels: {
labels: CustomLabel | CustomLabel[];
};
};

export type CustomLabel = JsonMap & { fullName: string };

type Index = Map<string, string[]>;

export class NonDecomposedIndex {
private static instance: NonDecomposedIndex;
private components: Map<string, SourceComponent> = new Map();
private index: Index = new Map();

// eslint-disable-next-line @typescript-eslint/no-empty-function
public constructor() {}

public static getInstance(): NonDecomposedIndex {
if (!NonDecomposedIndex.instance) {
NonDecomposedIndex.instance = new NonDecomposedIndex();
}
return NonDecomposedIndex.instance;
}

/**
* Register a SourceComponent to be resolved later.
*/
public register(fsPath: string, component: SourceComponent): void {
this.components.set(fsPath, component);
}

/**
* Read and parse the xml either:
* - for all registered SourceComponents
* - or, the provided SourceComponents
*/
public async resolve(...components: SourceComponent[]): Promise<Index> {
const filtered = components.filter((c) => !!c);
const componentsToResolve = filtered.length
? (filtered.map((c) => [c.xml, c]) as [string, SourceComponent][])
: [...this.components.entries()];

for (const [fsPath, component] of componentsToResolve) {
const contents = await component.parseXml();
const { xmlPath, nameAttr } = component.type.strategies.elementParser;
const elements = normalizeToArray(get(contents, xmlPath, []) as JsonMap[]);
const elementNames = elements.map((e) => e[nameAttr]) as string[];
this.addToIndex(fsPath, elementNames);
}
return this.index;
}

private addToIndex(fsPath: string, elements: string[]): void {
if (this.index.has(fsPath)) {
const existing = this.index.get(fsPath);
this.index.set(fsPath, [...existing, ...elements]);
} else {
this.index.set(fsPath, elements);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('SourceAdapterFactory', () => {
assert.throws(
() => factory.getAdapter(type),
RegistryError,
nls.localize('error_missing_adapter', [type.name, type.strategies.adapter])
nls.localize('error_missing_adapter', [type.strategies.adapter, type.name])
);
});
});