Skip to content

Commit

Permalink
feat: customLabels => customLabel files
Browse files Browse the repository at this point in the history
  • Loading branch information
mshanemc committed Aug 8, 2024
1 parent c49f7f5 commit d873517
Show file tree
Hide file tree
Showing 20 changed files with 189 additions and 99 deletions.
3 changes: 1 addition & 2 deletions src/convert/convertContext/recompositionFinalizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { extractUniqueElementValue, getXmlElement, unwrapAndOmitNS } from '../..
import { MetadataComponent } from '../../resolve/types';
import { XML_NS_KEY, XML_NS_URL } from '../../common/constants';
import { ComponentSet } from '../../collections/componentSet';
import { RecompositionStrategy } from '../../registry/types';
import { SourceComponent } from '../../resolve/sourceComponent';
import { JsToXml } from '../streams';
import { WriterFormat } from '../types';
Expand Down Expand Up @@ -127,7 +126,7 @@ const recompose =
const getStartingXml =
(cache: XmlCache) =>
async (parent: SourceComponent): Promise<JsonMap> =>
parent.type.strategies?.recomposition === RecompositionStrategy.StartEmpty
parent.type.strategies?.recomposition === 'startEmpty'
? {}
: unwrapAndOmitNS(parent.type.name)(await getXmlFromCache(cache)(parent)) ?? {};

Expand Down
6 changes: 4 additions & 2 deletions src/convert/streams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,13 @@ export class ComponentConverter extends Transform {
case 'source':
if (mergeWith) {
for (const mergeComponent of mergeWith) {
converts.push(transformer.toSourceFormat(chunk, mergeComponent));
converts.push(
transformer.toSourceFormat({ component: chunk, mergeWith: mergeComponent, mergeSet: this.mergeSet })
);
}
}
if (converts.length === 0) {
converts.push(transformer.toSourceFormat(chunk));
converts.push(transformer.toSourceFormat({ component: chunk }));
}
break;
case 'metadata':
Expand Down
5 changes: 4 additions & 1 deletion src/convert/transformers/baseMetadataTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,8 @@ export abstract class BaseMetadataTransformer implements MetadataTransformer {
}

public abstract toMetadataFormat(component: SourceComponent): Promise<WriteInfo[]>;
public abstract toSourceFormat(component: SourceComponent, mergeWith?: SourceComponent): Promise<WriteInfo[]>;
public abstract toSourceFormat(input: {
component: SourceComponent;
mergeWith?: SourceComponent;
}): Promise<WriteInfo[]>;
}
55 changes: 55 additions & 0 deletions src/convert/transformers/decomposeLabelsTransformer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2023, 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 type { CustomLabels, CustomLabel } from '@jsforce/jsforce-node/lib/api/metadata';
import { SfError } from '@salesforce/core/sfError';
import { calculateRelativePath } from '../../utils/path';
import { SourceComponent } from '../../resolve/sourceComponent';
import { ToSourceFormatInput, WriteInfo } from '../types';
import { JsToXml } from '../streams';
import { DefaultMetadataTransformer } from './defaultMetadataTransformer';

/* Use for the metadata type CustomLabels */
export class LabelsMetadataTransformer extends DefaultMetadataTransformer {
// CustomLabels file => CustomLabel
public async toSourceFormat({ component, mergeSet }: ToSourceFormatInput): Promise<WriteInfo[]> {
const labelType = this.registry.getTypeByName('CustomLabel');
const partiallyAppliedPathCalculator = calculateRelativePath('source')({
self: labelType,
});
const xml = await component.parseXml<CustomLabels>();
// split each label into a separate label file
return xml.labels.filter(customLabelHasFullName).map((l) => ({
output:
// if present in the merge set, use that xml path, otherwise use the default path
mergeSet?.getComponentFilenamesByNameAndType({ fullName: l.fullName, type: labelType.name })?.[0] ??
partiallyAppliedPathCalculator(l.fullName)(`${l.fullName}.label-meta.xml`),
source: new JsToXml({ CustomLabel: l }),
}));
}
}

/* Use for the metadata type CustomLabel */
export class LabelMetadataTransformer extends DefaultMetadataTransformer {
// eslint-disable-next-line @typescript-eslint/require-await, class-methods-use-this, @typescript-eslint/no-unused-vars
public async toMetadataFormat(component: SourceComponent): Promise<WriteInfo[]> {
// TODO:
// read all each label from the recomposition state, regardless of parents
// merge them all to a single CustomLabels file
// write the CustomLabels file
return [];
}

// toSourceFormat uses the default (merge them with the existing label)
}

const customLabelHasFullName = (label: CustomLabel): label is CustomLabel & { fullName: string } => {
if (label.fullName === undefined) {
throw SfError.create({ message: 'Label does not have a fullName', data: label });
}
return true;
};
8 changes: 4 additions & 4 deletions src/convert/transformers/decomposedMetadataTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import { calculateRelativePath } from '../../utils/path';
import { ForceIgnore } from '../../resolve/forceIgnore';
import { extractUniqueElementValue, objectHasSomeRealValues } from '../../utils/decomposed';
import type { MetadataComponent } from '../../resolve/types';
import { DecompositionStrategy, type MetadataType } from '../../registry/types';
import { type MetadataType } from '../../registry/types';
import { SourceComponent } from '../../resolve/sourceComponent';
import { JsToXml } from '../streams';
import type { WriteInfo, XmlObj } from '../types';
import type { ToSourceFormatInput, WriteInfo, XmlObj } from '../types';
import { META_XML_SUFFIX, XML_NS_KEY, XML_NS_URL } from '../../common/constants';
import type { SourcePath } from '../../common/types';
import { ComponentSet } from '../../collections/componentSet';
Expand Down Expand Up @@ -60,7 +60,7 @@ export class DecomposedMetadataTransformer extends BaseMetadataTransformer {
return [];
}

public async toSourceFormat(component: SourceComponent, mergeWith?: SourceComponent): Promise<WriteInfo[]> {
public async toSourceFormat({ component, mergeWith }: ToSourceFormatInput): Promise<WriteInfo[]> {
const forceIgnore = component.getForceIgnore();

// if the whole parent is ignored, we won't worry about decomposing things
Expand Down Expand Up @@ -265,7 +265,7 @@ const getDefaultOutput = (component: MetadataComponent): SourcePath => {
// there could be a '.' inside the child name (ex: PermissionSet.FieldPermissions.field uses Obj__c.Field__c)
const childName = tail.length ? tail.join('.') : undefined;
const output = join(
parent?.type.strategies?.decomposition === DecompositionStrategy.FolderPerType ? type.directoryName : '',
parent?.type.strategies?.decomposition === 'folderPerType' ? type.directoryName : '',
`${childName ?? baseName}.${ensureString(component.type.suffix)}${META_XML_SUFFIX}`
);
return join(calculateRelativePath('source')({ self: parent?.type ?? type })(fullName)(baseName), output);
Expand Down
8 changes: 7 additions & 1 deletion src/convert/transformers/defaultMetadataTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@ export class DefaultMetadataTransformer extends BaseMetadataTransformer {
}

// eslint-disable-next-line @typescript-eslint/require-await, class-methods-use-this
public async toSourceFormat(component: SourceComponent, mergeWith?: SourceComponent): Promise<WriteInfo[]> {
public async toSourceFormat({
component,
mergeWith,
}: {
component: SourceComponent;
mergeWith?: SourceComponent;
}): Promise<WriteInfo[]> {
return getWriteInfos(component, 'source', mergeWith);
}
}
Expand Down
14 changes: 9 additions & 5 deletions src/convert/transformers/metadataTransformerFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import { MetadataTransformer } from '../types';
import { SourceComponent } from '../../resolve/sourceComponent';
import { ConvertContext } from '../convertContext/convertContext';
import { RegistryAccess } from '../../registry/registryAccess';
import { TransformerStrategy } from '../../registry/types';
import { DefaultMetadataTransformer } from './defaultMetadataTransformer';
import { DecomposedMetadataTransformer } from './decomposedMetadataTransformer';
import { StaticResourceMetadataTransformer } from './staticResourceMetadataTransformer';
import { NonDecomposedMetadataTransformer } from './nonDecomposedMetadataTransformer';
import { LabelMetadataTransformer, LabelsMetadataTransformer } from './decomposeLabelsTransformer';

Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/source-deploy-retrieve', 'sdr');
Expand All @@ -32,15 +32,19 @@ export class MetadataTransformerFactory {
const type = component.parent ? component.parent.type : component.type;
const transformerId = type.strategies?.transformer;
switch (transformerId) {
case TransformerStrategy.Standard:
case 'standard':
case undefined:
return new DefaultMetadataTransformer(this.registry, this.context);
case TransformerStrategy.Decomposed:
case 'decomposed':
return new DecomposedMetadataTransformer(this.registry, this.context);
case TransformerStrategy.StaticResource:
case 'staticResource':
return new StaticResourceMetadataTransformer(this.registry, this.context);
case TransformerStrategy.NonDecomposed:
case 'nonDecomposed':
return new NonDecomposedMetadataTransformer(this.registry, this.context);
case 'decomposedLabels':
return component.type.name === 'CustomLabels'
? new LabelsMetadataTransformer(this.registry, this.context)
: new LabelMetadataTransformer(this.registry, this.context);
default:
throw messages.createError('error_missing_transformer', [type.name, transformerId]);
}
Expand Down
5 changes: 2 additions & 3 deletions src/convert/transformers/nonDecomposedMetadataTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@
import { get, getString, JsonMap } from '@salesforce/ts-types';
import { ensureArray } from '@salesforce/kit';
import { Messages } from '@salesforce/core';
import { WriteInfo } from '../types';
import { SourceComponent } from '../../resolve/sourceComponent';
import { ToSourceFormatInput, WriteInfo } from '../types';
import { DecomposedMetadataTransformer } from './decomposedMetadataTransformer';
Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@salesforce/source-deploy-retrieve', 'sdr');
Expand All @@ -22,7 +21,7 @@ const messages = Messages.loadMessages('@salesforce/source-deploy-retrieve', 'sd
export class NonDecomposedMetadataTransformer extends DecomposedMetadataTransformer {
// streams uses mergeWith for all types. Removing it would break the interface
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public async toSourceFormat(component: SourceComponent, mergeWith?: SourceComponent): Promise<WriteInfo[]> {
public async toSourceFormat({ component, mergeWith }: ToSourceFormatInput): Promise<WriteInfo[]> {
// this will only include the incoming (retrieved) labels, not the local file
const parentXml = await component.parseXml();
const xmlPathToChildren = `${component.type.name}.${component.type.directoryName}`;
Expand Down
4 changes: 2 additions & 2 deletions src/convert/transformers/staticResourceMetadataTransformer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { createWriteStream } from 'graceful-fs';
import { Logger, Messages, SfError } from '@salesforce/core';
import { isEmpty } from '@salesforce/kit';
import { baseName } from '../../utils/path';
import { WriteInfo } from '../types';
import { ToSourceFormatInput, WriteInfo } from '../types';
import { SourceComponent } from '../../resolve/sourceComponent';
import { SourcePath } from '../../common/types';
import { ensureFileExists } from '../../utils/fileSystemHandler';
Expand Down Expand Up @@ -97,7 +97,7 @@ export class StaticResourceMetadataTransformer extends BaseMetadataTransformer {
];
}

public async toSourceFormat(component: SourceComponent, mergeWith?: SourceComponent): Promise<WriteInfo[]> {
public async toSourceFormat({ component, mergeWith }: ToSourceFormatInput): Promise<WriteInfo[]> {
const { xml, content } = component;

if (!content) {
Expand Down
9 changes: 8 additions & 1 deletion src/convert/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/
import { Readable } from 'node:stream';
import { JsonMap } from '@salesforce/ts-types';
import { ComponentSet } from '../collections/componentSet';
import { XML_NS_KEY, XML_NS_URL } from '../common/constants';
import { FileResponseSuccess } from '../client/types';
import { SourcePath } from '../common/types';
Expand Down Expand Up @@ -74,13 +75,19 @@ export type MergeConfig = {
forceIgnoredPaths?: Set<string>;
};

export type ToSourceFormatInput = {
component: SourceComponent;
mergeWith?: SourceComponent;
mergeSet?: ComponentSet;
};
export type ToSourceFormat = (input: ToSourceFormatInput) => Promise<WriteInfo[]>;
/**
* Transforms metadata component files into different SFDX file formats
*/
export type MetadataTransformer = {
defaultDirectory?: string;
toSourceFormat: ToSourceFormat;
toMetadataFormat(component: SourceComponent): Promise<WriteInfo[]>;
toSourceFormat(component: SourceComponent, mergeWith?: SourceComponent): Promise<WriteInfo[]>;
};

// --------------
Expand Down
15 changes: 5 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,10 @@ export {
FromSourceOptions,
FromManifestOptions,
} from './collections';
export {
RegistryAccess,
registry,
getCurrentApiVersion,
MetadataRegistry,
MetadataType,
DecompositionStrategy,
RecompositionStrategy,
TransformerStrategy,
} from './registry';

export { RegistryAccess, registry, getCurrentApiVersion, MetadataRegistry, MetadataType } from './registry';

// TODO: don't export these strategies
export { DecompositionStrategy, TransformerStrategy, RecompositionStrategy } from './registry/types';

export { presetMap } from './registry/presets/presetMap';
8 changes: 1 addition & 7 deletions src/registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,4 @@ export { registry } from './registry';
export { standardValueSet } from './standardvalueset';
export { RegistryAccess } from './registryAccess';
export { getCurrentApiVersion } from './coverage';
export {
MetadataRegistry,
MetadataType,
DecompositionStrategy,
RecompositionStrategy,
TransformerStrategy,
} from './types';
export { MetadataRegistry, MetadataType } from './types';
28 changes: 28 additions & 0 deletions src/registry/presets/decomposeCustomLabelsBeta2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"suffixes": {
"label": "customlabel",
"labels": "customlabels"
},
"types": {
"customlabel": {
"directoryName": "labels",
"id": "customlabel",
"name": "CustomLabel",
"strategies": {
"adapter": "default",
"transformer": "label"
},
"suffix": "label"
},
"customlabels": {
"directoryName": "labels",
"id": "customlabels",
"name": "CustomLabels",
"strategies": {
"adapter": "default",
"transformer": "labels"
},
"suffix": "labels"
}
}
}
6 changes: 5 additions & 1 deletion src/registry/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export type MetadataType = {
*/
strategies?: {
adapter: 'mixedContent' | 'matchingContentFile' | 'decomposed' | 'digitalExperience' | 'bundle' | 'default';
transformer?: 'decomposed' | 'staticResource' | 'nonDecomposed' | 'standard';
transformer?: 'decomposed' | 'staticResource' | 'nonDecomposed' | 'standard' | 'decomposedLabels';
decomposition?: 'topLevel' | 'folderPerType';
recomposition?: 'startEmpty';
};
Expand Down Expand Up @@ -168,6 +168,7 @@ type DirectoryIndex = {
};

/**
* @deprecated. See the strategies union type on the registry types for the valid names
* Strategy names for handling component decomposition.
*/
export const enum DecompositionStrategy {
Expand All @@ -182,6 +183,7 @@ export const enum DecompositionStrategy {
}

/**
* @deprecated. See the strategies union type on the registry types for the valid names
* Strategy names for handling component recomposition.
*/
export const enum RecompositionStrategy {
Expand All @@ -192,13 +194,15 @@ export const enum RecompositionStrategy {
}

/**
* @deprecated. See the strategies union type on the registry types for the valid names
* Strategy names for the type of transformation to use for metadata types.
*/
export const enum TransformerStrategy {
Standard = 'standard',
Decomposed = 'decomposed',
StaticResource = 'staticResource',
NonDecomposed = 'nonDecomposed',
DecomposedLabels = 'decomposedLabels',
}

type Channel = {
Expand Down
11 changes: 6 additions & 5 deletions test/convert/streams.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Logger, SfError, Messages } from '@salesforce/core';
import { expect, assert } from 'chai';
import { createSandbox, SinonStub } from 'sinon';
import JSZip from 'jszip';
import { ToSourceFormatInput } from '../../src/convert/types';
import * as streams from '../../src/convert/streams';
import * as fsUtil from '../../src/utils/fileSystemHandler';
import { ComponentSet, MetadataResolver, RegistryAccess, SourceComponent, WriteInfo, WriterFormat } from '../../src';
Expand All @@ -35,7 +36,7 @@ class TestTransformer extends BaseMetadataTransformer {
}
// partial implementation only for tests
// eslint-disable-next-line class-methods-use-this, @typescript-eslint/require-await
public async toSourceFormat(component: SourceComponent, mergeWith?: SourceComponent): Promise<WriteInfo[]> {
public async toSourceFormat({ mergeWith }: ToSourceFormatInput): Promise<WriteInfo[]> {
const output = mergeWith ? mergeWith.content ?? mergeWith.xml : '/type/file.s';
assert(output);
return [{ output, source: new Readable() }];
Expand Down Expand Up @@ -106,7 +107,7 @@ describe('Streams', () => {
expect(err).to.be.undefined;
expect(data).to.deep.equal({
component,
writeInfos: await transformer.toSourceFormat(component),
writeInfos: await transformer.toSourceFormat({ component }),
});
done();
} catch (e) {
Expand All @@ -133,7 +134,7 @@ describe('Streams', () => {
expect(err).to.be.undefined;
expect(data).to.deep.equal({
component: newComponent,
writeInfos: await transformer.toSourceFormat(newComponent, component),
writeInfos: await transformer.toSourceFormat({ component: newComponent, mergeWith: component }),
});
done();
} catch (e) {
Expand Down Expand Up @@ -162,8 +163,8 @@ describe('Streams', () => {
expect(err).to.be.undefined;
expect(data).to.deep.equal({
component: newComponent,
writeInfos: (await transformer.toSourceFormat(newComponent, component)).concat(
await transformer.toSourceFormat(newComponent, secondMergeComponent)
writeInfos: (await transformer.toSourceFormat({ component: newComponent, mergeWith: component })).concat(
await transformer.toSourceFormat({ component: newComponent, mergeWith: secondMergeComponent })
),
});
done();
Expand Down
Loading

0 comments on commit d873517

Please sign in to comment.