Skip to content

Commit

Permalink
feat: add support for removing components from metadata package
Browse files Browse the repository at this point in the history
  • Loading branch information
Codeneos committed Jun 21, 2024
1 parent ad3b757 commit 789eeee
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 19 deletions.
65 changes: 47 additions & 18 deletions packages/salesforce/src/deploymentPackage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,25 @@ export class SalesforcePackage {
post: new PackageManifest()
};

/**
* Defines the structure of the metadata package, where the key is the package path in the package
* zip file and the value contains the info about the component as well as the data.
*/
private readonly packageData = new Map<string, SalesforcePackageEntry>();

/**
* Maps source files with their FS path to package contents
* Maps source files by file system path to the package metadata entries.
* * key - source file path
* * value - package metadata entry
*/
private readonly sourceFileMap = new Map<string, SalesforcePackageSourceMap>();
private readonly sourceFileToComponent = new Map<string, SalesforcePackageSourceMap>();

/**
* Map each component to package files
* Map package components to one or more source files from which they were loaded.
* * key - component type and name separated by a dot (e.g. `apexclass.myclass`) to lower case
* * value - array of source file paths
*/
private readonly packageComponents = new Map<string, string[]>();
private readonly componentToSource = new Map<string, string[]>();

/**
* Get printable name of the package components for use in the UI.
Expand Down Expand Up @@ -145,12 +153,12 @@ export class SalesforcePackage {
}

public addSourceMap(fsPath: string, entry: SalesforcePackageSourceMap) {
this.sourceFileMap.set(fsPath, {
this.sourceFileToComponent.set(fsPath, {
packagePath: entry.packagePath,
componentName: entry.componentName,
componentType: entry.componentType
});
arrayMapPush(this.packageComponents, `${entry.componentType}.${entry.componentName}`.toLowerCase(), fsPath);
arrayMapPush(this.componentToSource, `${entry.componentType}.${entry.componentName}`.toLowerCase(), fsPath);
}

/**
Expand Down Expand Up @@ -213,15 +221,24 @@ export class SalesforcePackage {
}

/**
* Remove metadata file from the package and manifest
* @param entry
* Removes all package data from the package for the specified component.
*
* @param entry - The component to remove.
*/
public remove(entry: { xmlName: string; componentName: string; packagePath: string } & SalesforcePackageFileData) {
this.manifest.remove(entry.componentType, entry.componentName);
this.packageData.delete(entry.packagePath);
public remove(entry: SalesforcePackageComponent) {
this.manifest.remove(entry.componentType, entry.componentType);

// Clean up source file mappings
const packageKey = `${entry.componentType}.${entry.componentName}`.toLowerCase();
const sourceFiles = this.componentToSource.get(packageKey);
for (const fsPath of sourceFiles ?? []) {
this.sourceFileToComponent.delete(fsPath);
}
this.componentToSource.delete(packageKey);

if (entry.fsPath) {
this.sourceFileMap.delete(entry.fsPath);
// Clean up package data
for (const path of this.getPackagePaths(entry)) {
this.packageData.delete(path);
}
}

Expand All @@ -232,7 +249,7 @@ export class SalesforcePackage {
* @returns Source file folder or undefined when not found
*/
public getSourceFolder(componentType: string, componentName: string) {
for (const [fsPath, sourceFileInfo] of this.sourceFileMap.entries()) {
for (const [fsPath, sourceFileInfo] of this.sourceFileToComponent.entries()) {
if (sourceFileInfo.componentType === componentType &&
sourceFileInfo.componentName === componentName) {
return directoryName(fsPath);
Expand All @@ -247,7 +264,7 @@ export class SalesforcePackage {
* @returns FS path from which the component was loaded or undefined when not loaded or not in the current package
*/
public getComponentSourceFiles(type: string, name: string) {
return this.packageComponents.get(`${type}.${name}`.toLowerCase());
return this.componentToSource.get(`${type}.${name}`.toLowerCase());
}

/**
Expand All @@ -267,7 +284,7 @@ export class SalesforcePackage {
filter: value => !!value.fsPath,
map: value => value.fsPath!
}),
this.sourceFileMap.keys()
this.sourceFileToComponent.keys()
));
}

Expand All @@ -294,7 +311,7 @@ export class SalesforcePackage {
* @param sourceFile Source file path
*/
public getSourceFileInfo(sourceFile: string) {
return this.sourceFileMap.get(sourceFile);
return this.sourceFileToComponent.get(sourceFile);
}

/**
Expand Down Expand Up @@ -402,6 +419,18 @@ export class SalesforcePackage {
return packagedClasses;
}

/**
* Get all package paths for the specified component. Use this method to get all files in the package for a specific component.
* Use get {@see getPackageData} to get the actual data for the package path at the specified path.
* @param component Component to get package paths for
*/
public getPackagePaths(component: SalesforcePackageComponent): Array<string> {
return [...Iterable.transform(this.packageData.entries(), {
filter: ([, entry]) => entry.componentType === component.componentType && entry.componentName === component.componentName,
map: ([path]) => path
})];
}

/**
* Get the currently packaged data for the specified path in the package.
* @param packagePath Package path
Expand Down Expand Up @@ -600,7 +629,7 @@ export class SalesforcePackage {
const fsPaths = this.getComponentSourceFiles(type, name);
if (fsPaths?.length) {
const metaFile = fsPaths.length == 1 ? fsPaths[0] : fsPaths.find(f => f.toLowerCase().endsWith('.xml'));
const metaFileSourceMap = metaFile && this.sourceFileMap.get(metaFile);
const metaFileSourceMap = metaFile && this.sourceFileToComponent.get(metaFile);
if (metaFileSourceMap) {
const packageData = this.getPackageData(metaFileSourceMap.packagePath);
if (packageData?.data && XML.isXml(packageData.data)) {
Expand Down
40 changes: 40 additions & 0 deletions packages/salesforce/src/deploymentPackageBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,38 @@ export class SalesforcePackageBuilder {
return this;
}

/**
* Removes unchanged components from the package by executing the
* provided delta strategy which determines which components have changed.
*
* @template S - The type of the DeltaPackageStrategy.
* @template T - The type of the object.
* @param strategy - The DeltaPackageStrategy instance or constructor.
* @param options - The options for getting changed components.
* @returns An array of removed SalesforcePackageComponent objects.
*/
public async removeUnchanged<S extends DeltaPackageStrategy<T>, T extends object>(
strategy?: S | (new(...args: any[]) => S),
options?: Parameters<S['getChangedComponents']>[1]
) : Promise<Array<SalesforcePackageComponent>> {
const mdPackage = this.getPackage();
const deltaStrategy: DeltaPackageStrategy<T> = typeof strategy === 'function' || !strategy
? Container.get(this).create(strategy ?? RetrieveDeltaStrategy as any) : strategy;

const changedComponents = await deltaStrategy.getChangedComponents(mdPackage, options);
const changedComponentSet = new Set(changedComponents.map(c => `${c.componentType}/${c.componentName}`));
const removedComponents = new Array<SalesforcePackageComponent>();

for (const component of this.mdPackage.components()) {
if (!changedComponentSet.has(`${component.componentType}/${component.componentName}`)) {
removedComponents.push(component);
this.mdPackage.remove(component);
}
}

return removedComponents;
}

private sortXmlFragments(fragments: Array<[string, string, MetadataType]>) : Array<[string, string, MetadataType]> {
return fragments.sort(([, fragmentTypeA, parentTypeA], [, fragmentTypeB, parentTypeB]) => {
const metaTypeCompare = parentTypeA.name.localeCompare(parentTypeB.name);
Expand Down Expand Up @@ -471,6 +503,14 @@ export class SalesforcePackageBuilder {
return deltaPackage;
}

/**
* Retrieves the list of components currently in the package.
* @returns An array of SalesforcePackageComponent objects.
*/
public getPackageComponents(): Array<SalesforcePackageComponent> {
return this.mdPackage.components();
}

/**
* Gets SalesforcePackage underlying the builder.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/salesforce/src/retrieveDeltaStrategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface CompareStrategy {
* Returns a list of components that have changed which can be used to create a new deployment package.
*/
@injectable({ lifecycle: LifecyclePolicy.transient })
export class RetrieveDeltaStrategy {
export class RetrieveDeltaStrategy {

private readonly compareStrategies : Record<string, CompareStrategy> = {
'xmlStrictOrder': (a, b) => this.isXmlEqual(a, b, { strictOrder: true }),
Expand Down

0 comments on commit 789eeee

Please sign in to comment.