Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(release): enhanced support for multiple release definitions in release command #14

Merged
merged 6 commits into from
Feb 12, 2024
59 changes: 36 additions & 23 deletions packages/sfp-cli/src/commands/orchestrator/release.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ import SFPLogger, {
ConsoleLogger,
} from '@flxblio/sfp-logger';
import ReleaseDefinition from '../../impl/release/ReleaseDefinition';
import { arrayFlagSfdxStyle, loglevel, logsgroupsymbol, optionalDevHubFlag, requiredUserNameFlag } from '../../flags/sfdxflags';
import {
arrayFlagSfdxStyle,
loglevel,
logsgroupsymbol,
optionalDevHubFlag,
requiredUserNameFlag,
} from '../../flags/sfdxflags';
import { Flags } from '@oclif/core';

Messages.importMessagesDirectory(__dirname);
Expand Down Expand Up @@ -91,16 +97,16 @@ export default class Release extends SfpCommand {
allowunpromotedpackages: Flags.boolean({
description: messages.getMessage('allowUnpromotedPackagesFlagDescription'),
hidden: true,
deprecated: {
deprecated: {
message: '--allowunpromotedpackages is deprecated, All packages are allowed',
},
},
}),
changelogByDomains: Flags.boolean({
description: messages.getMessage('changelogByDomainsFlagDescription'),
hidden: true
hidden: true,
}),
devhubalias: optionalDevHubFlag,
loglevel
loglevel,
};

public async execute() {
Expand All @@ -121,15 +127,14 @@ export default class Release extends SfpCommand {
SFPLogger.log(COLOR_HEADER(`Release Definitions: ${this.flags.releasedefinition}`));
SFPLogger.log(COLOR_HEADER(`Artifact Directory: ${path.resolve('artifacts')}`));

SFPLogger.printHeaderLine('',COLOR_HEADER,LoggerLevel.INFO);
SFPLogger.printHeaderLine('', COLOR_HEADER, LoggerLevel.INFO);

let releaseDefinitions: ReleaseDefinition[] = [];
for (const pathToReleaseDefintion of this.flags.releasedefinition) {
let releaseDefinition = await ReleaseDefinitionLoader.loadReleaseDefinition(pathToReleaseDefintion);

//Support Legacy by taking the existing single workItemFilter and pushing it to the new model
if(releaseDefinition.changelog?.workItemFilter)
{
if (releaseDefinition.changelog?.workItemFilter) {
releaseDefinition.changelog.workItemFilters = new Array<string>();
releaseDefinition.changelog.workItemFilters.push(releaseDefinition.changelog?.workItemFilter);
}
Expand Down Expand Up @@ -164,21 +169,19 @@ export default class Release extends SfpCommand {
isGenerateChangelog: this.flags.generatechangelog,
devhubUserName: this.flags.devhubalias,
branch: this.flags.branchname,
directory:this.flags.directory,
directory: this.flags.directory,
};

let releaseImpl: ReleaseImpl = new ReleaseImpl(props,new ConsoleLogger());
let releaseImpl: ReleaseImpl = new ReleaseImpl(props, new ConsoleLogger());

releaseResult = await releaseImpl.exec();
if(!this.flags.dryrun)
SFPStatsSender.logCount('release.succeeded', tags);
if (!this.flags.dryrun) SFPStatsSender.logCount('release.succeeded', tags);
} catch (err) {
if (err instanceof ReleaseError) {
releaseResult = err.data;
} else SFPLogger.log(err.message);

if(!this.flags.dryrun)
SFPStatsSender.logCount('release.failed', tags);
if (!this.flags.dryrun) SFPStatsSender.logCount('release.failed', tags);

// Fail the task when an error occurs
process.exitCode = 1;
Expand All @@ -193,8 +196,7 @@ export default class Release extends SfpCommand {
}

private sendMetrics(releaseResult: ReleaseResult, tags: any, totalElapsedTime: number) {
if(!this.flags.dryrun)
{
if (!this.flags.dryrun) {
SFPStatsSender.logCount('release.scheduled', tags);

SFPStatsSender.logGauge('release.duration', totalElapsedTime, tags);
Expand Down Expand Up @@ -223,8 +225,8 @@ export default class Release extends SfpCommand {
private printReleaseSummary(releaseResult: ReleaseResult, totalElapsedTime: number): void {
if (this.flags.logsgroupsymbol?.[0])
SFPLogger.log(COLOR_HEADER(this.flags.logsgroupsymbol[0], 'Release Summary'));
SFPLogger.printHeaderLine('',COLOR_HEADER,LoggerLevel.INFO);

SFPLogger.printHeaderLine('', COLOR_HEADER, LoggerLevel.INFO);
if (releaseResult.installDependenciesResult) {
SFPLogger.log(COLOR_HEADER(`\nPackage Dependencies`));
SFPLogger.log(COLOR_SUCCESS(` ${releaseResult.installDependenciesResult.success.length} succeeded`));
Expand All @@ -233,13 +235,25 @@ export default class Release extends SfpCommand {
}

for (const succeededDeployment of releaseResult.succeededDeployments) {
SFPLogger.log(COLOR_HEADER(`\n Release Defintion: ${succeededDeployment.releaseDefinition.release}`));
SFPLogger.log(
COLOR_HEADER(
`\n Release Defintion: ${succeededDeployment.releaseDefinition.release} for Release Config: ${
succeededDeployment.releaseDefinition.releaseConfigName
? succeededDeployment.releaseDefinition.releaseConfigName
: 'N/A'
}`
)
);
SFPLogger.log(COLOR_SUCCESS(` ${succeededDeployment.result.deployed.length} succeeded`));
SFPLogger.log(COLOR_ERROR(` ${succeededDeployment.result.failed.length} failed`));
}

for (const failedDeployment of releaseResult.failedDeployments) {
SFPLogger.log(COLOR_HEADER(`\n Release Defintion: ${failedDeployment.releaseDefinition.release}`));
SFPLogger.log(COLOR_HEADER(`\n Release Defintion: ${failedDeployment.releaseDefinition.release} for for Release Config: ${
failedDeployment.releaseDefinition.releaseConfigName
? failedDeployment.releaseDefinition.releaseConfigName
: 'N/A'
}`));
SFPLogger.log(COLOR_SUCCESS(` ${failedDeployment.result.deployed.length} succeeded`));
SFPLogger.log(
COLOR_ERROR(
Expand All @@ -249,12 +263,11 @@ export default class Release extends SfpCommand {
);
}

SFPLogger.log(COLOR_TIME(`\nElapsed Time: ${new Date(totalElapsedTime).toISOString().substr(11, 8)}`));
SFPLogger.printHeaderLine('',COLOR_HEADER,LoggerLevel.INFO);
SFPLogger.log(COLOR_TIME(`\nElapsed Time: ${new Date(totalElapsedTime).toISOString().substring(11,19)}`));
SFPLogger.printHeaderLine('', COLOR_HEADER, LoggerLevel.INFO);
}

protected validateFlags() {
if (this.flags.npm && !this.flags.scope) throw new Error('--scope parameter is required for NPM');

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default class PackageDependencyResolver {
let dependency = packageDirectory.dependencies[i];
if (this.projectConfig.packageAliases[dependency.package] === undefined && !this.isSubscriberPackageVersionId(dependency.package)) {

throw new Error(`Can't find package id for dependency: ` + dependency.package);
throw new Error(`Can't find package id for dependency: ${dependency.package}, Please ensure that the package is added to sfdx-project.json in your packageAliases`);
}

let packageVersionId = this.isSubscriberPackageVersionId(dependency.package)?dependency.package:this.projectConfig.packageAliases[dependency.package]
Expand Down Expand Up @@ -211,7 +211,8 @@ export default class PackageDependencyResolver {

if (!package2VersionOnCurrentBranch) {
throw new Error(
`Failed to find validated Package2 version for dependency ${dependency.package} with version ${dependency.versionNumber} created from the current branch`
`Failed to find validated Package2 version for dependency ${dependency.package} with version ${dependency.versionNumber} created from the current branch,
You will need to create a new version of the package on the current branch to resolve this dependency.`
);
}

Expand Down
10 changes: 5 additions & 5 deletions packages/sfp-cli/src/impl/impact/ImpactedReleaseConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import path from 'path';
export default class ImpactedRelaseConfigResolver {

public getImpactedReleaseConfigs(impactedPackages, configDir,isExplicitDependencyCheckEnabled:boolean=false, filterBy?: string) {
const impactedReleaseDefs = [];
const impactedReleaseConfigs = [];

fs.readdirSync(configDir).forEach((file) => {
const filePath = path.join(configDir, file);
Expand Down Expand Up @@ -38,7 +38,7 @@ export default class ImpactedRelaseConfigResolver {
if (releaseImpactedPackages.length > 0) {
if (filterBy) {
if (releaseConfig.releaseName.includes(filterBy)) {
impactedReleaseDefs.push({
impactedReleaseConfigs.push({
releaseName: releaseConfig.releaseName,
pool: releaseConfig.pool
? releaseConfig.pool
Expand All @@ -48,7 +48,7 @@ export default class ImpactedRelaseConfigResolver {
});
}
} else {
impactedReleaseDefs.push({
impactedReleaseConfigs.push({
releaseName: releaseConfig.releaseName,
pool: releaseConfig.pool
? releaseConfig.pool
Expand All @@ -61,7 +61,7 @@ export default class ImpactedRelaseConfigResolver {
}
});

const sortedImpactedReleaseDefs = impactedReleaseDefs.sort((a, b) => {
const sortedImpactedReleaseConfigs = impactedReleaseConfigs.sort((a, b) => {
if (!a.impactedPackages.length && !b.impactedPackages.length) return 0;
if (!a.impactedPackages.length) return 1; // Move releases with no impacted packages to the end
if (!b.impactedPackages.length) return -1; // Same as above
Expand All @@ -77,7 +77,7 @@ export default class ImpactedRelaseConfigResolver {
});

const output = {
include: sortedImpactedReleaseDefs,
include: sortedImpactedReleaseConfigs,
};
return output;
}
Expand Down
45 changes: 45 additions & 0 deletions packages/sfp-cli/src/impl/release/ReleaseDefinitionSorter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Logger } from '@flxblio/sfp-logger';
import SfpPackageInquirer from '../../core/package/SfpPackageInquirer';
import ProjectConfig from '../../core/project/ProjectConfig';
import ArtifactFetcher, { Artifact } from '../../core/artifacts/ArtifactFetcher';
import SfpPackage from '../../core/package/SfpPackage';
import SfpPackageBuilder from '../../core/package/SfpPackageBuilder';
import ReleaseDefinition from './ReleaseDefinition';
import _ from 'lodash';

export default class ReleaseDefinitionSorter {

public sortReleaseDefinitions(
releaseDefinitions: ReleaseDefinition[],
leadingSfProjectConfig: any,
logger: Logger
): ReleaseDefinition[] {

let clonedReleaseDefintions:ReleaseDefinition[] = _.cloneDeep(releaseDefinitions);
const allPackagesInConfig = ProjectConfig.getAllPackagesFromProjectConfig(leadingSfProjectConfig);
const packageOccurrenceCount = new Map<string, number>();

// Count occurrences of each package across all release definitions
clonedReleaseDefintions.forEach((releaseDefinition) => {
Object.keys(releaseDefinition.artifacts).forEach((pkg) => {
if (allPackagesInConfig.includes(pkg)) {
// Only consider packages present in the project config
packageOccurrenceCount.set(pkg, (packageOccurrenceCount.get(pkg) || 0) + 1);
}
});
});
// Annotate each release definition with the index of its first unique package
clonedReleaseDefintions.forEach((releasedefnition) => {
releasedefnition['firstUniquePackageIndex'] = allPackagesInConfig.length; // Default to length (i.e., end) if no unique package is found
for (const pkg of allPackagesInConfig) {
if (releasedefnition.artifacts[pkg] && packageOccurrenceCount.get(pkg) === 1) {
// Check if the package is unique
releasedefnition['firstUniquePackageIndex'] = allPackagesInConfig.indexOf(pkg);
break; // Found the first unique package, no need to continue
}
}
});
// Sort based on the first unique package's index, placing those without a unique package at the end
return clonedReleaseDefintions.sort((a, b) => a['firstUniquePackageIndex'] - b['firstUniquePackageIndex']);
}
}
Loading
Loading