@dxatscale/sfprofiles
Version:
Salesforce Profile management
139 lines (122 loc) • 6.33 kB
text/typescript
import { Sfpowerkit } from '@utils/sfpowerkit';
import * as path from 'path';
import * as _ from 'lodash';
import ProfileActions, { ProfileSourceFile } from './profileActions';
import FileUtils from '@utils/fileutils';
import * as fs from 'fs-extra';
import { Worker } from 'worker_threads';
import SFPLogger, { LoggerLevel } from '@dxatscale/sfp-logger';
import MetadataFiles from '@impl/metadata/metadataFiles';
export default class ProfileReconcile extends ProfileActions {
public async reconcile(srcFolders: string[], profileList: string[], destFolder: string): Promise<string[]> {
//Get supported permissions from the org
try {
this.createDestinationFolder(destFolder);
SFPLogger.log(`ProfileList ${JSON.stringify(profileList)}`, LoggerLevel.DEBUG);
if (_.isNil(srcFolders) || srcFolders.length === 0) {
srcFolders = await Sfpowerkit.getProjectDirectories();
}
SFPLogger.log(`Project Directories ${JSON.stringify(srcFolders)}`, LoggerLevel.TRACE);
let localProfiles = await this.loadProfileFromPackageDirectories(srcFolders);
//Find Profiles to Reconcile
let profilesToReconcile: ProfileSourceFile[] = this.findProfilesToReconcile(profileList, localProfiles);
SFPLogger.log(`Profiles Found in Project Directory ${profilesToReconcile.length}`, LoggerLevel.INFO);
let reconciledProfiles = [];
//Reconcile one first, then do the rest later to use cache for subsequent one
if (profilesToReconcile.length > 1) {
reconciledProfiles = await this.runWorkers([profilesToReconcile[0]], destFolder);
profilesToReconcile.shift();
}
reconciledProfiles = reconciledProfiles.concat(await this.runWorkers(profilesToReconcile, destFolder));
return reconciledProfiles;
} catch (err) {
console.error(err);
throw err;
}
}
private runWorkers(profilesToReconcile: ProfileSourceFile[], destFolder) {
let workerCount = 0;
let finishedWorkerCount = 0;
let chunk = 10; // One worker to process 10 profiles
let i: number;
let profileCount = profilesToReconcile.length;
let result: string[] = [];
let workerPromise = new Promise<string[]>((resolve, reject) => {
try {
for (i = 0; i < profileCount; i += chunk) {
workerCount++;
let temparray: ProfileSourceFile[] = profilesToReconcile.slice(i, i + chunk);
SFPLogger.log(
`Initiated Profile reconcile thread :${workerCount} with a chunk of ${temparray.length} profiles`,
LoggerLevel.INFO
);
SFPLogger.log(`Profiles queued in thread :${workerCount} :`, LoggerLevel.INFO);
let reconcileWorkerFile: string;
//Switch to typescript while run locally using sfdx link, for debugging, else switch to js
if (fs.existsSync(path.resolve(__dirname, `./reconcileWorker.js`))) {
reconcileWorkerFile = `./reconcileWorker.js`;
} else {
reconcileWorkerFile = `./reconcileWorker.ts`;
}
SFPLogger.log(`reconcileWorkerFile: ${reconcileWorkerFile}`, LoggerLevel.TRACE);
const workerData = {
profileChunk: temparray,
destFolder: destFolder,
targetOrg: this.org?.getUsername(), //Org can be null during source only reconcile
loglevel: SFPLogger.logLevel,
isJsonFormatEnabled: Sfpowerkit.isJsonFormatEnabled,
isSourceOnly: MetadataFiles.sourceOnly,
path: reconcileWorkerFile,
};
const worker = new Worker(path.resolve(__dirname, './worker.js'), {
workerData
});
worker.on('message', (data) => {
// eslint-disable-next-line @typescript-eslint/no-array-constructor
SFPLogger.log(`Message received: ${data}`, LoggerLevel.TRACE);
let completedProfiles: string[] = new Array();
completedProfiles.push(...data);
for (const profile of completedProfiles) {
SFPLogger.log(`Reconciled Profile ${profile}`, LoggerLevel.INFO);
}
result.push(...data);
});
worker.on('error', (err) => {
SFPLogger.log(`Error while running worker ${err}`, LoggerLevel.ERROR);
reject(err);
});
worker.on('exit', (code) => {
finishedWorkerCount++;
SFPLogger.log(`Worker stopped with exit code ${code}`, LoggerLevel.TRACE);
if (code !== 0)
//reject(new Error(`Worker stopped with exit code ${code}`));
SFPLogger.log(`Worker stopped with exit code ${code}`, LoggerLevel.ERROR);
if (workerCount === finishedWorkerCount) {
resolve(result);
}
});
}
} catch (err) {
console.error(err);
throw err;
}
});
return workerPromise;
}
private findProfilesToReconcile(profileList: string[], localProfiles: ProfileSourceFile[]) {
let profilesToReconcile;
if (profileList.length > 0) {
profilesToReconcile = localProfiles.filter((elem) => {
if (profileList.includes(elem.name)) return true;
});
} else {
profilesToReconcile = localProfiles;
}
return profilesToReconcile;
}
private createDestinationFolder(destFolder: string) {
if (!_.isNil(destFolder)) {
FileUtils.mkDirByPathSync(destFolder);
}
}
}