Skip to content

Commit 5daeb09

Browse files
committed
feat: enhanced support for metadata refresh and export in decomposed source format
1 parent e2ff339 commit 5daeb09

File tree

8 files changed

+251
-140
lines changed

8 files changed

+251
-140
lines changed

packages/salesforce/src/deploy/maifest.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,19 @@ export class PackageManifest {
6363
}
6464
}
6565

66+
/**
67+
* Get all component names in this package as strings in the format `type/name`
68+
* @example `ApexClass/MyClass`
69+
* @return {string[]} Array of component names in the format `type/name`
70+
*/
71+
public componentsNames() : string[] {
72+
return Iterable.reduce(
73+
this.metadataMembers.entries(),
74+
(arr, [type, members]) => arr.concat(Array.from(members).map(member => `${type}/${member}`)),
75+
new Array<string>()
76+
);
77+
}
78+
6679
/**
6780
* Merge another package into this package
6881
* @param other Other package to merge into this package

packages/salesforce/src/deploy/metadataExpander.ts

Lines changed: 30 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import { SalesforcePackageComponentFile } from './package';
33
import { outputFile } from 'fs-extra';
44
import { MetadataRegistry, MetadataType } from '../metadataRegistry';
55
import { injectable } from '@vlocode/core';
6-
import { asArray, substringBefore, XML } from '@vlocode/util';
6+
import { asArray, count, substringBefore, XML } from '@vlocode/util';
77
import * as mimeTypes from 'mime-db';
88

99
interface MetadataFile extends SalesforcePackageComponentFile {
1010
content(): Promise<Buffer>;
11-
metadata(): Promise<(object & { "@type": string }) | undefined>;
11+
metadata(): Promise<object | undefined>;
1212
}
1313

1414
interface OutputFile {
@@ -20,35 +20,33 @@ export class MetadataExpander {
2020

2121
public async expandMetadata(metadata: MetadataFile): Promise<Record<string, Buffer>> {
2222
const content = await metadata.content();
23+
const metadataObj = await metadata.metadata();
2324
const type = MetadataRegistry.getMetadataType(metadata.componentType);
2425

2526
if (type?.childXmlNames.length) {
2627
return this.expandMetadataChildren(metadata.componentName, type, XML.parse(content));
2728
}
2829

29-
const metadataObj = await metadata.metadata();
30-
if (!metadataObj) {
31-
return this.expandContentAsMeta(metadata, content);
32-
}
33-
34-
if (metadataObj['@type'] === 'StaticResource') {
35-
return this.expandStaticResource(metadata, content, metadataObj);
30+
if (type?.name === 'StaticResource') {
31+
return metadataObj ? this.expandStaticResource(metadata, content, metadataObj) : {};
3632
}
3733

38-
return this.expandAsContent(metadata, content);
34+
return this.expandContent(metadata, content);
3935
}
4036

4137
private expandStaticResource(metadata: MetadataFile, content: Buffer, meta: object): Record<string, Buffer> {
42-
const basename = this.baseName(metadata.packagePath);
38+
const basename = this.baseName(metadata.packagePath, true);
4339
const contentType = meta['contentType']?.toLowerCase();
4440
const mimeType = mimeTypes[contentType];
4541
const suffix = mimeType?.extensions?.[0] || 'resource';
42+
const metaXml = XML.stringify({ StaticResource: meta }, { indent: 4, attributePrefix: '@attributes' });
4643
return {
47-
[`${basename}.${suffix}`]: content
44+
[`${basename}.${suffix}`]: content,
45+
[`${basename}.${suffix}-meta.xml`]: Buffer.from(metaXml)
4846
};
4947
}
50-
51-
private expandContentAsMeta(metadata: MetadataFile, content: Buffer): Record<string, Buffer> {
48+
49+
private expandContent(metadata: MetadataFile, content: Buffer): Record<string, Buffer> {
5250
const basename = this.baseName(metadata.packagePath);
5351
const appendMetaXml = this.shouldAppendMetaXml(metadata.packagePath, content);
5452
const expandedName = appendMetaXml ? `${basename}-meta.xml` : basename;
@@ -57,32 +55,36 @@ export class MetadataExpander {
5755
};
5856
}
5957

60-
private expandAsContent(metadata: MetadataFile, content: Buffer): Record<string, Buffer> {
61-
const basename = this.baseName(metadata.packagePath);
62-
return {
63-
[basename]: content
64-
};
65-
}
66-
6758
private expandMetadataChildren(name: string, type: MetadataType, metadata: object): Record<string, Buffer> {
6859
const expandedMetadata: Record<string, Buffer> = {};
6960

70-
for (const childType of Object.values(type.children?.types ?? {})) {
71-
const childContent = metadata[type.name][childType.directoryName];
61+
if (!type.children) {
62+
throw new Error(`Metadata type ${type.name} does not have child types defined`);
63+
}
64+
65+
for (const [childNode, childTypeId] of Object.entries(type.children.directories ?? {})) {
66+
const childType = type.children.types[childTypeId];
67+
const childContent = metadata[type.name][childNode];
7268
for (const childItem of asArray(childContent ?? [])) {
73-
const childName = childItem.fullName;
69+
const childName = childType.uniqueIdElement ? childItem[childType.uniqueIdElement] : childItem.fullName;
70+
if (!childName) {
71+
throw new Error(`Missing unique identifier for child type ${childType.name} in metadata ${type.name}`);
72+
}
7473
const childFileName = path.posix.join(name, childType.directoryName, `${childName}.${childType.suffix}-meta.xml`);
7574
expandedMetadata[childFileName] = Buffer.from(XML.stringify({ [childType.name]: childItem}, 4));
7675
}
7776

7877
if (childContent) {
79-
delete metadata[type.name][childType.directoryName];
78+
delete metadata[type.name][childNode];
8079
}
8180
}
8281

83-
const parentFileName = path.posix.join(name, `${name}.${type.suffix}-meta.xml`);
84-
expandedMetadata[parentFileName] = Buffer.from(XML.stringify({ [type.name]: metadata[type.name]}, 4));
85-
82+
const isParentEmpty = count(Object.keys(metadata[type.name]), key => key != '$') === 0;
83+
if (!isParentEmpty) {
84+
// Do not include parent metadata if it has no children
85+
const parentFileName = path.posix.join(name, `${name}.${type.suffix}-meta.xml`);
86+
expandedMetadata[parentFileName] = Buffer.from(XML.stringify({ [type.name]: metadata[type.name]}, 4));
87+
}
8688
return expandedMetadata;
8789
}
8890

packages/salesforce/src/deploy/package.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { FileSystem } from '@vlocode/core';
66
import { PackageManifest } from './maifest';
77
import { isPromise } from 'util/types';
88
import { outputFile } from 'fs-extra';
9+
import { MetadataRegistry } from '../metadataRegistry';
910

1011
export interface SalesforcePackageComponent {
1112
componentType: string; // componentType
@@ -250,16 +251,20 @@ export class SalesforcePackage {
250251
* @returns Source file folder or undefined when not found
251252
*/
252253
public getSourceFolder(componentType: string, componentName: string) {
254+
const metadataInfo = MetadataRegistry.getMetadataType(componentType);
255+
const decomposed = metadataInfo?.strategies?.decomposition !== undefined;
253256
for (const [fsPath, sourceFileInfo] of this.sourceFileToComponent.entries()) {
254-
if (sourceFileInfo.componentType === componentType &&
255-
sourceFileInfo.componentName === componentName) {
256-
return directoryName(fsPath);
257+
if (sourceFileInfo.componentType !== componentType) {
258+
continue;
259+
}
260+
if (sourceFileInfo.componentName === componentName) {
261+
return directoryName(fsPath, decomposed ? 2 : 1);
257262
}
258263
}
259264
}
260265

261266
/**
262-
* Get the name of the source file.
267+
* Get the sources files associated with the specified component.
263268
* @param type XML Type
264269
* @param name Component name
265270
* @returns FS path from which the component was loaded or undefined when not loaded or not in the current package
@@ -531,7 +536,8 @@ export class SalesforcePackage {
531536
}
532537

533538
/**
534-
* Get a flat array with the names of the components in this package prefixed with their type.
539+
* Get a flat array with the names of the components in this package prefixed with their type separated by a slash.
540+
* @returns Array of component names in the format <type>/<name>
535541
*/
536542
public getComponentNames() {
537543
return Iterable.reduce(this.manifest.types(), (arr, type) => arr.concat(this.manifest.list(type).map(name => `${type}/${name}`)), new Array<string>());

packages/salesforce/src/deploy/packageBuilder.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import chalk from 'chalk';
33
import ZipArchive from 'jszip';
44

55
import { Logger, injectable, CachedFileSystemAdapter , FileSystem, Container, container } from '@vlocode/core';
6-
import { cache, substringAfterLast , Iterable, XML, CancellationToken, FileSystemUri, substringBeforeLast, stringEquals, clearCache } from '@vlocode/util';
6+
import { cache, substringAfterLast , Iterable, XML, CancellationToken, FileSystemUri, substringBeforeLast, stringEquals, clearCache, stringEqualsIgnoreCase } from '@vlocode/util';
77

88
import { PackageManifest } from './maifest';
99
import { SalesforcePackage, SalesforcePackageComponent, SalesforcePackageComponentFile } from './package';
@@ -216,7 +216,7 @@ export class SalesforcePackageBuilder {
216216
const isFolderMetadata = stringEquals(metadataType.folderType, xmlName, { caseInsensitive: true });
217217
if (metadataType.name != xmlName && !isFolderMetadata) {
218218
// Support for SFDX formatted source code
219-
childMetadataFiles.push([ file, xmlName, metadataType]);
219+
childMetadataFiles.push([ file, xmlName, metadataType ]);
220220
continue;
221221
}
222222

@@ -468,10 +468,11 @@ export class SalesforcePackageBuilder {
468468
}
469469

470470
const childComponentName = this.getPackageComponentName(sourceFile, parentType);
471-
const folderPerType = parentType.strategies?.decomposition === 'folderPerType';
471+
const sourceFilePath = sourceFile.split(/\\|\//g);
472+
const indexOfTypeFolder = sourceFilePath.findLastIndex(p => stringEqualsIgnoreCase(p, parentType.directoryName));
472473

473-
const parentComponentFolder = path.join(...sourceFile.split(/\\|\//g).slice(0, folderPerType ? -2 : -1));
474-
const parentComponentName = path.basename(parentComponentFolder);
474+
const parentComponentFolder = path.join(...sourceFilePath.slice(0, indexOfTypeFolder + 2));
475+
const parentComponentName = sourceFilePath[indexOfTypeFolder + 1];
475476
const parentComponentMetaFile = path.join(parentComponentFolder, `${parentComponentName}.${parentType.suffix}-meta.xml`);
476477
const parentPackagePath = await this.getPackagePath(parentComponentMetaFile, parentType);
477478

@@ -494,7 +495,17 @@ export class SalesforcePackageBuilder {
494495
} else {
495496
this.mdPackage.addManifestEntry(parentType.name, parentComponentName);
496497
}
497-
this.mdPackage.addSourceMap(sourceFile, { componentType: fragmentType.name, componentName: `${parentComponentName}.${childComponentName}`, packagePath: parentPackagePath });
498+
// Register source file as source for composed metadata
499+
this.mdPackage.addSourceMap(sourceFile, {
500+
componentType: fragmentType.name,
501+
componentName: `${parentComponentName}.${childComponentName}`,
502+
packagePath: parentPackagePath
503+
});
504+
this.mdPackage.addSourceMap(parentComponentMetaFile, {
505+
componentType: parentType.name,
506+
componentName: parentComponentName,
507+
packagePath: parentPackagePath
508+
});
498509
}
499510

500511
this.logger.verbose(`Added %s (%s.%s) as [%s]`, path.basename(sourceFile), parentComponentName, childComponentName, chalk.green(fragmentTypeName));

0 commit comments

Comments
 (0)