Skip to content

Commit

Permalink
feat: add replacement token support to deployment package builder
Browse files Browse the repository at this point in the history
  • Loading branch information
Codeneos committed Aug 16, 2024
1 parent 595ccb9 commit 467c586
Show file tree
Hide file tree
Showing 10 changed files with 118 additions and 18 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'jest';

import { SalesforcePackageBuilder, SalesforcePackageType } from '../deploymentPackageBuilder';
import { SalesforcePackageBuilder, SalesforcePackageType } from '../deploy/packageBuilder';
import { Logger, MemoryFileSystem, container } from '@vlocode/core';
import { XML } from '@vlocode/util';

Expand Down
4 changes: 3 additions & 1 deletion packages/salesforce/src/deploy/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from './deploymentProgress';
export * from './packageXml';
export * from './maifest';
export * from './retrieveResultPackage';
export * from './package';
export * from './packageBuilder';
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,10 @@ export class PackageManifest {
const manifest = new PackageManifest();
this.metadataMembers.forEach((members, type) => {
if (typeof metadataTypes === 'string') {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
metadataTypes === type && members.forEach(member => manifest.add(type, member));
} else if (Array.isArray(metadataTypes)) {
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
metadataTypes.includes(type) && members.forEach(member => manifest.add(type, member));
} else {
members.forEach(member => metadataTypes(type, member) && manifest.add(type, member));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import * as path from 'path';
import ZipArchive from 'jszip';
import { Iterable, XML , directoryName, arrayMapPush, asArray, groupBy, stringEqualsIgnoreCase } from '@vlocode/util';
import { FileSystem } from '@vlocode/core';
import { PackageManifest } from './deploy/packageXml';
import { PackageManifest } from './maifest';
import { isPromise } from 'util/types';

export interface SalesforcePackageComponent {
Expand Down Expand Up @@ -144,7 +144,7 @@ export class SalesforcePackage {
* @param component Component specification for which to get the files
*/
public *getComponentFiles(component: SalesforcePackageComponent) : Generator<SalesforcePackageEntry> {
for (const [path, entry] of this.packageData.entries()) {
for (const entry of this.packageData.values()) {
if (entry.componentName === component.componentName &&
entry.componentType === component.componentType) {
yield entry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ import ZipArchive from 'jszip';
import { Logger, injectable, CachedFileSystemAdapter , FileSystem, Container, container } from '@vlocode/core';
import { cache, substringAfterLast , Iterable, XML, CancellationToken, FileSystemUri, substringBeforeLast, stringEquals } from '@vlocode/util';

import { PackageManifest } from './deploy/packageXml';
import { SalesforcePackage, SalesforcePackageComponent } from './deploymentPackage';
import { MetadataRegistry, MetadataType } from './metadataRegistry';
import { RetrieveDeltaStrategy } from './retrieveDeltaStrategy';
import { PackageManifest } from './maifest';
import { SalesforcePackage, SalesforcePackageComponent, SalesforcePackageComponentFile } from './package';
import { MetadataRegistry, MetadataType } from '../metadataRegistry';
import { RetrieveDeltaStrategy } from '../retrieveDeltaStrategy';
import { Minimatch } from 'minimatch';

export enum SalesforcePackageType {
/**
Expand Down Expand Up @@ -49,6 +50,67 @@ interface MetadataObject {
data: Record<string, unknown>;
}

interface ReplacementDetail {
/**
* Token to replace in the file content, all text matching this token will be replaced with the replacement value.
*/
token: string;
/**
* Replacement value or function to generate replacement value
*/
replacement: string | ((file: string, data: string | Buffer) => string);
/**
* Match file pattern to apply the replacement to.
*/
filePatterns?: string | string[];
/**
* Match metadata type to apply the replacement to.
*/
metadataTypes?: string | string[];
}

class TokenReplacement {
private filePatterns?: Minimatch[];
private metadataTypes?: string[];

constructor(public detail: ReplacementDetail) {
const filePatterns = detail.filePatterns
? (Array.isArray(detail.filePatterns) ? detail.filePatterns : [ detail.filePatterns ])
: undefined;
const metadataTypes = detail.metadataTypes
? (Array.isArray(detail.metadataTypes) ? detail.metadataTypes : [ detail.metadataTypes ])
: undefined;
this.filePatterns = filePatterns?.map(pattern => new Minimatch(pattern, { nocomment: true, nocase: true }));
this.metadataTypes = metadataTypes?.map(pattern => pattern.toLocaleLowerCase());
}

public async isMatch(entry: SalesforcePackageComponentFile) {
if (this.filePatterns) {
if (!this.filePatterns.some(pattern => pattern.match(entry.packagePath))) {
return false;
}
}
if (this.metadataTypes) {
const metadataType = entry.componentType.toLocaleLowerCase();
if (!this.metadataTypes.includes(metadataType)) {
return false;
}
}
return true;
}

public async apply(data: Buffer | string, file: string) {
const replacementValue = typeof this.detail.replacement === 'function'
? this.detail.replacement(file, data)
: this.detail.replacement;

if (typeof data === 'string') {
return data.replace(this.detail.token, replacementValue);
}
return Buffer.from(data.toString().replace(this.detail.token, replacementValue));
}
}

@injectable()
export class SalesforcePackageBuilder {

Expand All @@ -61,6 +123,8 @@ export class SalesforcePackageBuilder {
private readonly fs: FileSystem;
private readonly parsedFiles = new Set<string>();
private readonly composedData = new Map<string, MetadataObject>();
private readonly replacements = new Array<TokenReplacement>();

@injectable.property private readonly metadataRegistry: MetadataRegistry;
@injectable.property private readonly logger: Logger;

Expand All @@ -73,6 +137,22 @@ export class SalesforcePackageBuilder {
this.mdPackage = new SalesforcePackage(this.apiVersion);
}

/**
* Add a replacement token to the package builder. When building the package the token will be replaced with the replacement value.
* Replacements are applied to all files in the package and can target specific files or metadata types or be applied globally.
* @usage
* ```typescript
* builder.addReplacement({
* token: `$CURRENT_USER`
* metadataTypes:
* });
* @remark Avoid applying replacements globally as they can have unintended side effects.
* @param replacement
*/
public addReplacement(replacement: ReplacementDetail) {
this.replacements.push(new TokenReplacement(replacement));
}

/**
* Adds one or more files to the package.
* @param files Array of files to add
Expand Down Expand Up @@ -279,19 +359,28 @@ export class SalesforcePackageBuilder {
if (this.type === SalesforcePackageType.destruct) {
this.mdPackage.addDestructiveChange(componentType, componentName);
} else {
const packagePath = await this.getPackagePath(file, metadataType);
this.mdPackage.add({
const packageEntry = {
componentType,
componentName,
packagePath,
packagePath: await this.getPackagePath(file, metadataType),
data: await this.fs.readFile(file),
fsPath: file
});
};
await this.applyReplacements(packageEntry);
this.mdPackage.add(packageEntry);
}

this.logger.verbose(`Added %s (%s) as [%s]`, path.basename(file), componentName, chalk.green(metadataType.name));
}

private async applyReplacements(entry: SalesforcePackageComponentFile & { data: Buffer | string }) {
for (const replacement of this.replacements) {
if (await replacement.isMatch(entry)) {
entry.data = await replacement.apply(entry.data, entry.packagePath);
}
}
}

/**
* Add compressed static resource bundle to the package; compresses all the resources in the folder into a zip file but does not add
* the meta-data file for the resource folder.
Expand Down
4 changes: 2 additions & 2 deletions packages/salesforce/src/deploy/retrieveResultPackage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import * as fs from 'fs-extra';
import ZipArchive from 'jszip';
import { directoryName, fileName as baseName , groupBy } from '@vlocode/util';
import { FileProperties, RetrieveResult } from '../connection';
import { SalesforcePackageComponentFile } from '../deploymentPackage';
import { PackageManifest } from './packageXml';
import { PackageManifest } from './maifest';
import { SalesforcePackageComponentFile } from './package';

/**
* Extends typings on the JSZipObject with internal _data object
Expand Down
2 changes: 1 addition & 1 deletion packages/salesforce/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
export * from './schema';
export * from './types';
export * from './deploymentPackage';
export * from './deploymentPackageBuilder';
export * from './deploy/packageBuilder';
export * from './developerLog';
export * from './developerLogs';
export * from './metadataRegistry';
Expand Down
2 changes: 1 addition & 1 deletion packages/salesforce/src/metadataRegistry.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import registryData from './registry/metadataRegistry.json';
import { MetadataType as RegistryMetadataType } from './registry/types';
import { singletonMixin } from '@vlocode/util';
import { injectable, LifecyclePolicy, Logger } from '@vlocode/core';
import { injectable, Logger } from '@vlocode/core';
import urlFormats from './metadataUrls.json';

export interface MetadataUrlFormat {
Expand Down
2 changes: 1 addition & 1 deletion packages/salesforce/src/salesforceService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Container, container, FileSystem, injectable, LifecyclePolicy, Logger }
import { cache, evalTemplate, mapAsyncParallel, XML, substringAfter, fileName, Timer, FileSystemUri, CancellationToken, asArray, groupBy, isSalesforceId, spreadAsync, filterUndefined } from '@vlocode/util';

import { HttpMethod, HttpRequestInfo, SalesforceConnectionProvider } from './connection';
import { SalesforcePackageBuilder, SalesforcePackageType } from './deploymentPackageBuilder';
import { SalesforcePackageBuilder, SalesforcePackageType } from './deploy/packageBuilder';
import { QueryService, QueryResult } from './queryService';
import { RecordBatch } from './recordBatch';
import { SalesforceDeployService } from './salesforceDeployService';
Expand Down
9 changes: 8 additions & 1 deletion packages/util/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,11 @@ export type Await<T> = T extends {
then(onfulfilled?: (value: infer U) => unknown): unknown;
} ? U : T;

export type AwaitReturnType<T extends (...args: any) => any> = Await<ReturnType<T>>;
export type AwaitReturnType<T extends (...args: any) => any> = Await<ReturnType<T>>;

type ExtractPropertyNames<T> = { [K in keyof T]: T[K] extends (...args: any[]) => any ? never : K }[keyof T];

/**
* Extracts all properties from a type that are not functions
*/
export type ExtractProperties<T> = Pick<T, ExtractPropertyNames<T>>;

0 comments on commit 467c586

Please sign in to comment.