Skip to content

Commit

Permalink
feat(impact): add impact:package and impact:releaseconfig commands
Browse files Browse the repository at this point in the history
Add two new helper commands that can be used to figure out impacted packages or impacted
releaseconfigs by comparing against last know gith t  tags
  • Loading branch information
azlam-abdulsalam committed Oct 28, 2023
1 parent 018e283 commit ec884dc
Show file tree
Hide file tree
Showing 7 changed files with 436 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/sfpowerscripts-cli/messages/impact_package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"commandDescription": "Figures out impacted packages of a project, due to a change from the last known tags",
"baseCommitOrBranchFlagDescription": "The base branch on which the git tags should be used"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"commandDescription": "Figures out impacted release configurations of a project, due to a change,from the last known tags",
"releaseConfigFileFlagDescription":"Path to the directory containing release defns",
"baseCommitOrBranchFlagDescription": "The base branch on which the git tags should be used from",
"filterByFlagDescription": "Filter by a specific release config name"
}
4 changes: 4 additions & 0 deletions packages/sfpowerscripts-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,10 @@
}
}
},
"impact" : {
"description": "Figures out the impact of various components of sfpowerscripts",
"external": true
},
"analyze": {
"description": "Analyze your projects using static analysis tools such as PMD",
"external": true
Expand Down
80 changes: 80 additions & 0 deletions packages/sfpowerscripts-cli/src/commands/impact/package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { Messages } from '@salesforce/core';
import SfpowerscriptsCommand from '../../SfpowerscriptsCommand';
import { Stage } from '../../impl/Stage';
import SFPLogger, { COLOR_KEY_MESSAGE, ConsoleLogger } from '@dxatscale/sfp-logger';
import { Flags } from '@oclif/core';
import { loglevel } from '../../flags/sfdxflags';
import { ZERO_BORDER_TABLE } from '../../ui/TableConstants';
import ImpactedPackageResolver, { ImpactedPackageProps } from '../../impl/impact/ImpactedPackagesResolver';
const Table = require('cli-table');
import path from 'path';
import * as fs from 'fs-extra';


Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@dxatscale/sfpowerscripts', 'impact_package');

export default class Package extends SfpowerscriptsCommand {
public static flags = {
loglevel,
basebranch: Flags.string({
description: messages.getMessage('baseCommitOrBranchFlagDescription'),
required: true,
})
};

public static description = messages.getMessage('commandDescription');
private props: ImpactedPackageProps;

async execute(): Promise<any> {
// Read Manifest

this.props = {
currentStage: Stage.BUILD,
baseBranch: this.flags.basebranch,
diffOptions: {
useLatestGitTags: true,
skipPackageDescriptorChange: false,
},
};

const impactedPackageResolver = new ImpactedPackageResolver(this.props, new ConsoleLogger());

let packagesToBeBuiltWithReasons = await impactedPackageResolver.getImpactedPackages();
let packageDiffTable = this.createDiffPackageScheduledDisplayedAsATable(packagesToBeBuiltWithReasons);
const packagesToBeBuilt = Array.from(packagesToBeBuiltWithReasons.keys());

//Log Packages to be built
SFPLogger.log(COLOR_KEY_MESSAGE('Packages impacted...'));
SFPLogger.log(packageDiffTable.toString());


const outputPath = path.join(process.cwd(), 'impacted-package.json');
if (packagesToBeBuilt && packagesToBeBuilt.length > 0)
fs.writeFileSync(outputPath, JSON.stringify(packagesToBeBuilt, null, 2));
else fs.writeFileSync(outputPath, JSON.stringify([], null, 2));
SFPLogger.log(`Impacted packages if any written to ${outputPath}`);


return packagesToBeBuilt;
}

private createDiffPackageScheduledDisplayedAsATable(packagesToBeBuilt: Map<string, any>) {
let tableHead = ['Package', 'Reason', 'Last Known Tag'];
let table = new Table({
head: tableHead,
chars: ZERO_BORDER_TABLE,
});
for (const pkg of packagesToBeBuilt.keys()) {
let item = [
pkg,
packagesToBeBuilt.get(pkg).reason,
packagesToBeBuilt.get(pkg).tag ? packagesToBeBuilt.get(pkg).tag : '',
];
table.push(item);
}
return table;
}


}
128 changes: 128 additions & 0 deletions packages/sfpowerscripts-cli/src/commands/impact/releaseconfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { Messages } from '@salesforce/core';
import SfpowerscriptsCommand from '../../SfpowerscriptsCommand';
import { Stage } from '../../impl/Stage';
import * as fs from 'fs-extra';
import SFPLogger, { COLOR_KEY_MESSAGE, ConsoleLogger } from '@dxatscale/sfp-logger';
import { Flags } from '@oclif/core';
import { loglevel } from '../../flags/sfdxflags';
import { ZERO_BORDER_TABLE } from '../../ui/TableConstants';
import path from 'path';
import ImpactedPackageResolver, { ImpactedPackageProps } from '../../impl/impact/ImpactedPackagesResolver';
import ImpactedRelaseConfigResolver from '../../impl/impact/ImpactedReleaseConfig';
const Table = require('cli-table');


Messages.importMessagesDirectory(__dirname);
const messages = Messages.loadMessages('@dxatscale/sfpowerscripts', 'impact_release_config');

export default class ReleaseConfig extends SfpowerscriptsCommand {
public static flags = {
loglevel,
basebranch: Flags.string({
description: messages.getMessage('baseCommitOrBranchFlagDescription'),
required: true,
}),
releaseconfig: Flags.string({
description: messages.getMessage('releaseConfigFileFlagDescription'),
default: 'config',
}),
filterBy: Flags.string({
description: messages.getMessage('filterByFlagDescription'),
}),
};

public static description = messages.getMessage('commandDescription');
private props: ImpactedPackageProps;
isMultiConfigFilesEnabled: boolean;

async execute(): Promise<any> {
// Read Manifest

this.props = {
branch: this.flags.branch,
currentStage: Stage.VALIDATE,
baseBranch: this.flags.basebranch,
diffOptions: {
useLatestGitTags: true,
skipPackageDescriptorChange: false,
},
};

const impactedPackageResolver = new ImpactedPackageResolver(this.props, new ConsoleLogger());

let packagesToBeBuiltWithReasons = await impactedPackageResolver.getImpactedPackages();
let packageDiffTable = this.createDiffPackageScheduledDisplayedAsATable(packagesToBeBuiltWithReasons);
const packagesToBeBuilt = Array.from(packagesToBeBuiltWithReasons.keys());

//Log Packages to be built
SFPLogger.log(COLOR_KEY_MESSAGE('Packages impacted...'));
SFPLogger.log(packageDiffTable.toString());

const impactedReleaseConfigResolver = new ImpactedRelaseConfigResolver();

let impactedReleaseConfigs = impactedReleaseConfigResolver.getImpactedReleaseConfigs(
packagesToBeBuilt,
this.flags.releaseconfig,
this.flags.filterBy
);

let impactedReleaseConfigTable = this.createImpactedReleaseConfigsAsATable(impactedReleaseConfigs.include);
//Log Packages to be built
SFPLogger.log(COLOR_KEY_MESSAGE('Release Configs impacted...'));
SFPLogger.log(impactedReleaseConfigTable.toString());

const outputPath = path.join(process.cwd(), 'impacted-release-configs.json');
if (impactedReleaseConfigs && impactedReleaseConfigs.include.length > 0)
fs.writeFileSync(outputPath, JSON.stringify(impactedReleaseConfigs, null, 2));
else fs.writeFileSync(outputPath, JSON.stringify([], null, 2));
if (!this.flags.filterBy) SFPLogger.log(`Impacted release configs written to ${outputPath}`);
else
SFPLogger.log(
`Impacted release configs written to ${outputPath},${
impactedReleaseConfigs.include[0]?.releaseName
? `filtered impacted release config found for ${impactedReleaseConfigs.include[0]?.releaseName}`
: `no impacted release config found for ${this.flags.filterBy}`
}`
);

return impactedReleaseConfigs.include;
}

private createDiffPackageScheduledDisplayedAsATable(packagesToBeBuilt: Map<string, any>) {
let tableHead = ['Package', 'Reason', 'Last Known Tag'];
if (this.isMultiConfigFilesEnabled && this.props.currentStage == Stage.BUILD) {
tableHead.push('Scratch Org Config File');
}
let table = new Table({
head: tableHead,
chars: ZERO_BORDER_TABLE,
});
for (const pkg of packagesToBeBuilt.keys()) {
let item = [
pkg,
packagesToBeBuilt.get(pkg).reason,
packagesToBeBuilt.get(pkg).tag ? packagesToBeBuilt.get(pkg).tag : '',
];

table.push(item);
}
return table;
}

private createImpactedReleaseConfigsAsATable(impacatedReleaseConfigs: any[]) {
let tableHead = ['Release/Domain Name', 'Pools', 'ReleaseConfig Path'];
let table = new Table({
head: tableHead,
chars: ZERO_BORDER_TABLE,
});
for (const impactedReleaseConfig of impacatedReleaseConfigs) {
let item = [
impactedReleaseConfig.releaseName,
impactedReleaseConfig.domainNameUsedForPools,
impactedReleaseConfig.filePath,
];
table.push(item);
}
return table;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import PackageDiffImpl, { PackageDiffOptions } from '@dxatscale/sfpowerscripts.core/lib/package/diff/PackageDiffImpl';
import { Stage } from '../Stage';
import ProjectConfig from '@dxatscale/sfpowerscripts.core/lib/project/ProjectConfig';
import { PackageType } from '@dxatscale/sfpowerscripts.core/lib/package/SfpPackage';
import * as fs from 'fs-extra';
import { Logger } from '@dxatscale/sfp-logger';
import BuildCollections from '../parallelBuilder/BuildCollections';

export interface ImpactedPackageProps {
projectDirectory?: string;
branch?: string;
configFilePath?: string;
currentStage: Stage;
baseBranch?: string;
diffOptions?: PackageDiffOptions;
includeOnlyPackages?: string[];
}

export default class ImpactedPackageResolver {


constructor(private props: ImpactedPackageProps, private logger: Logger) {
}

async getImpactedPackages(): Promise<Map<string, any>> {
let projectConfig = ProjectConfig.getSFDXProjectConfig(this.props.projectDirectory);
let packagesToBeBuilt = this.getPackagesToBeBuilt(this.props.projectDirectory);
let packagesToBeBuiltWithReasons = await this.filterPackagesToBeBuiltByChanged(
this.props.projectDirectory,
projectConfig,
packagesToBeBuilt
);

return packagesToBeBuiltWithReasons;
}

/**
* Get the file path of the forceignore for current stage, from project config.
* Returns null if a forceignore path is not defined in the project config for the current stage.
*
* @param projectConfig
* @param currentStage
*/
private getPathToForceIgnoreForCurrentStage(projectConfig: any, currentStage: Stage): string {
let stageForceIgnorePath: string;

let ignoreFiles: { [key in Stage]: string } = projectConfig.plugins?.sfpowerscripts?.ignoreFiles;
if (ignoreFiles) {
Object.keys(ignoreFiles).forEach((key) => {
if (key.toLowerCase() == currentStage) {
stageForceIgnorePath = ignoreFiles[key];
}
});
}

if (stageForceIgnorePath) {
if (fs.existsSync(stageForceIgnorePath)) {
return stageForceIgnorePath;
} else throw new Error(`${stageForceIgnorePath} forceignore file does not exist`);
} else return null;
}

private async filterPackagesToBeBuiltByChanged(projectDirectory: string,projectConfig:any, allPackagesInRepo: any) {
let packagesToBeBuilt = new Map<string, any>();
let buildCollections = new BuildCollections(projectDirectory);
if (this.props.diffOptions)
this.props.diffOptions.pathToReplacementForceIgnore = this.getPathToForceIgnoreForCurrentStage(
projectConfig,
this.props.currentStage
);

for await (const pkg of allPackagesInRepo) {
let diffImpl: PackageDiffImpl = new PackageDiffImpl(
this.logger,
pkg,
this.props.projectDirectory,
this.props.diffOptions
);
let packageDiffCheck = await diffImpl.exec();

if (packageDiffCheck.isToBeBuilt) {
packagesToBeBuilt.set(pkg, {
reason: packageDiffCheck.reason,
tag: packageDiffCheck.tag,
});
//Add Bundles
if (buildCollections.isPackageInACollection(pkg)) {
buildCollections.listPackagesInCollection(pkg).forEach((packageInCollection) => {
if (!packagesToBeBuilt.has(packageInCollection)) {
packagesToBeBuilt.set(packageInCollection, {
reason: 'Part of a build collection',
});
}
});
}
}
}
return packagesToBeBuilt;
}

private getPackagesToBeBuilt(projectDirectory: string, includeOnlyPackages?: string[]): string[] {
let projectConfig = ProjectConfig.getSFDXProjectConfig(projectDirectory);
let sfdxpackages = [];

let packageDescriptors = projectConfig['packageDirectories'].filter((pkg) => {
if (
pkg.ignoreOnStage?.find((stage) => {
stage = stage.toLowerCase();
return stage === this.props.currentStage;
})
)
return false;
else return true;
});

//Filter Packages
if (includeOnlyPackages) {
packageDescriptors = packageDescriptors.filter((pkg) => {
if (
includeOnlyPackages.find((includedPkg) => {
return includedPkg == pkg.package;
})
)
return true;
else return false;
});
}

// Ignore aliasfied packages on stages fix #1289
packageDescriptors = packageDescriptors.filter((pkg) => {
return !(this.props.currentStage === 'prepare' && pkg.aliasfy && pkg.type !== PackageType.Data);
});

for (const pkg of packageDescriptors) {
if (pkg.package && pkg.versionNumber) sfdxpackages.push(pkg['package']);
}
return sfdxpackages;
}

}
Loading

0 comments on commit ec884dc

Please sign in to comment.