From 4f33fd4b876dd88ebbda17a5c2a13ee617284713 Mon Sep 17 00:00:00 2001 From: azlam-abdulsalam Date: Thu, 8 Feb 2024 14:40:49 +1100 Subject: [PATCH] feat(release): introduce additional properties to release config and definition Introduce additional properties such as releaseConfigName, metadata into release configuration. Refactor the implementation across to make the variables and lass clear --- packages/sfp-cli/messages/release.json | 3 +- .../messages/releasedefinition_generate.json | 5 +- ...schema.json => release-config.schema.json} | 16 +- ...n.schema.json => release-defn.schema.json} | 14 +- packages/sfp-cli/src/BuildBase.ts | 6 +- .../sfp-cli/src/commands/artifacts/fetch.ts | 4 +- .../src/commands/orchestrator/release.ts | 12 +- .../commands/releasedefinition/generate.ts | 5 + packages/sfp-cli/src/commands/repo/patch.ts | 15 +- .../impl/artifacts/FetchAnArtifactFromNPM.ts | 27 ++-- .../sfp-cli/src/impl/artifacts/FetchImpl.ts | 4 +- .../sfp-cli/src/impl/deploy/DeployImpl.ts | 6 +- .../sfp-cli/src/impl/prepare/PrepareImpl.ts | 11 +- .../sfp-cli/src/impl/release/ReleaseConfig.ts | 137 +++--------------- .../src/impl/release/ReleaseConfigLoader.ts | 115 +++++++++++++++ .../src/impl/release/ReleaseDefinition.ts | 100 +++---------- .../release/ReleaseDefinitionGenerator.ts | 81 ++++++----- .../ReleaseDefinitionGeneratorConfigSchema.ts | 21 --- .../impl/release/ReleaseDefinitionLoader.ts | 79 ++++++++++ .../impl/release/ReleaseDefinitionSchema.ts | 21 --- .../sfp-cli/src/impl/release/ReleaseImpl.ts | 18 +-- .../sfp-cli/src/impl/validate/ValidateImpl.ts | 6 +- .../impl/release/ReleaseDefinition.test.ts | 18 +-- 23 files changed, 387 insertions(+), 337 deletions(-) rename packages/sfp-cli/resources/schemas/{releasedefinitiongenerator.schema.json => release-config.schema.json} (82%) rename packages/sfp-cli/resources/schemas/{releasedefinition.schema.json => release-defn.schema.json} (75%) create mode 100644 packages/sfp-cli/src/impl/release/ReleaseConfigLoader.ts delete mode 100644 packages/sfp-cli/src/impl/release/ReleaseDefinitionGeneratorConfigSchema.ts create mode 100644 packages/sfp-cli/src/impl/release/ReleaseDefinitionLoader.ts delete mode 100644 packages/sfp-cli/src/impl/release/ReleaseDefinitionSchema.ts diff --git a/packages/sfp-cli/messages/release.json b/packages/sfp-cli/messages/release.json index dfe21fdc9..890fd0a22 100644 --- a/packages/sfp-cli/messages/release.json +++ b/packages/sfp-cli/messages/release.json @@ -15,5 +15,6 @@ "allowUnpromotedPackagesFlagDescription": "Allow un-promoted packages to be installed in production", "devhubAliasFlagDescription": "Provide the alias of the devhub previously authenticated, default value is HubOrg", "directoryFlagDescription": "Relative path to directory to which the changelog should be generated, if the directory doesnt exist, it will be created", - "branchNameFlagDescription": "Repository branch in which the changelog files are located" + "branchNameFlagDescription": "Repository branch in which the changelog files are located", + "changelogByDomainsFlagDescription":"Create changelog files by domains or name mentioned in release config" } diff --git a/packages/sfp-cli/messages/releasedefinition_generate.json b/packages/sfp-cli/messages/releasedefinition_generate.json index c8f314871..f3072d865 100644 --- a/packages/sfp-cli/messages/releasedefinition_generate.json +++ b/packages/sfp-cli/messages/releasedefinition_generate.json @@ -1,10 +1,11 @@ { "commandDescription": "Generates release definition based on the artifacts installed from a commit reference", - "configFileFlagDescription":"Path to the config file which determines how the release definition should be generated", + "configFileFlagDescription":"Path to the release config file which determines how the release definition should be generated", "releaseNameFlagDescription": "Set a release name on the release definition file created", "commitFlagDescription": "Utilize the tags on the source branch to generate release definiton", "directoryFlagDescription": "Relative path to directory to which the release definition file should be generated, if the directory doesnt exist, it will be created", "branchNameFlagDescription": "Repository branch in which the release definition files are to be written", "noPushFlagDescription":"Do not push the changelog to a repository to the provided branch", - "forcePushFlagDescription": "Force push changes to the repository branch" + "forcePushFlagDescription": "Force push changes to the repository branch", + "metadataFlagDescription": "Additional metadata in json format that needs to be added to the release definition file" } diff --git a/packages/sfp-cli/resources/schemas/releasedefinitiongenerator.schema.json b/packages/sfp-cli/resources/schemas/release-config.schema.json similarity index 82% rename from packages/sfp-cli/resources/schemas/releasedefinitiongenerator.schema.json rename to packages/sfp-cli/resources/schemas/release-config.schema.json index 580d0fefb..64d485174 100644 --- a/packages/sfp-cli/resources/schemas/releasedefinitiongenerator.schema.json +++ b/packages/sfp-cli/resources/schemas/release-config.schema.json @@ -1,14 +1,22 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/flxbl-io/sfp/blob/develop/packages/sfp-cli/resources/releasedefinitiongenerator.schema.json", - "title": "release definition generator", - "description": "The definition for generating a release defintion using generator command", + "$id": "https://github.com/flxbl-io/json-schemas/blob/main/release-config.schema.json", + "title": "Configuration of a release, used as an input to the release definition generator or by sfops for understanding how the repo is structure", + "description": "Configuration of a release", "type": "object", "additionalProperties": true, "dependencies": { "baselineOrg": ["skipIfAlreadyInstalled"] }, "properties": { + "releaseName": { + "type": "string", + "title": "Name of the domain this release configuration belongs to" + }, + "pool": { + "type": "string", + "title": "The pool of review sandbox or scratch org to be used for validating the packages that are part of this release configuration" + }, "excludeArtifacts": { "type": "array", "title": "Exclude the below artifacts while creating a release definition", @@ -57,7 +65,7 @@ }, "releasedefinitionProperties":{ "type": "object", - "title": "Properties that need to be set in the generated definition file", + "title": "Properties that need to be set in the generated release definition", "additionalProperties": true, "properties": { "skipIfAlreadyInstalled": { diff --git a/packages/sfp-cli/resources/schemas/releasedefinition.schema.json b/packages/sfp-cli/resources/schemas/release-defn.schema.json similarity index 75% rename from packages/sfp-cli/resources/schemas/releasedefinition.schema.json rename to packages/sfp-cli/resources/schemas/release-defn.schema.json index d81dcd6bb..33a25f335 100644 --- a/packages/sfp-cli/resources/schemas/releasedefinition.schema.json +++ b/packages/sfp-cli/resources/schemas/release-defn.schema.json @@ -1,8 +1,8 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "$id": "https://github.com/flxbl-io/sfp/blob/develop/packages/sfp-cli/resources/releasedefinition.schema.json", - "title": "release definition", - "description": "The definition for a release using sfp orchestrator", + "$id": "https://github.com/flxbl-io/json-schemas/blob/main/release-defn.schema.json", + "title": "A definition of a release, the artifacts to be deployed along with its version,", + "description": "This yaml file is either manually defined or auto generated by sfp release definition generator", "type": "object", "required": ["release", "artifacts"], "additionalProperties": false, @@ -13,6 +13,14 @@ "release": { "type": "string" }, + "releaseConfigName": { + "type": "string", + "title": "The name of the release config that generated this release definition,autopopulated when generated by release definition generator" + }, + "metadata": { + "type": "object", + "title": "Additional data to be written to the release definition" + }, "skipIfAlreadyInstalled": { "type": "boolean" }, diff --git a/packages/sfp-cli/src/BuildBase.ts b/packages/sfp-cli/src/BuildBase.ts index c8608200d..e2c6f3439 100644 --- a/packages/sfp-cli/src/BuildBase.ts +++ b/packages/sfp-cli/src/BuildBase.ts @@ -22,7 +22,7 @@ import SFPLogger, { } from '@flxblio/sfp-logger'; import getFormattedTime from './core/utils/GetFormattedTime'; import SfpPackage from './core/package/SfpPackage'; -import ReleaseConfig from './impl/release/ReleaseConfig'; +import ReleaseConfigLoader from './impl/release/ReleaseConfigLoader'; import { Flags } from '@oclif/core'; import { loglevel, orgApiVersionFlagSfdxStyle, targetdevhubusername } from './flags/sfdxflags'; @@ -234,8 +234,8 @@ export default abstract class BuildBase extends SfpCommand { private includeOnlyPackagesAsPerReleaseConfig(releaseConfigFilePath:string,buildProps: BuildProps,logger?:Logger): BuildProps { if (releaseConfigFilePath) { - let releaseConfig:ReleaseConfig = new ReleaseConfig(logger, releaseConfigFilePath); - buildProps.includeOnlyPackages = releaseConfig.getPackagesAsPerReleaseConfig(); + let releaseConfigLoader:ReleaseConfigLoader = new ReleaseConfigLoader(logger, releaseConfigFilePath); + buildProps.includeOnlyPackages = releaseConfigLoader.getPackagesAsPerReleaseConfig(); printIncludeOnlyPackages(buildProps.includeOnlyPackages); } return buildProps; diff --git a/packages/sfp-cli/src/commands/artifacts/fetch.ts b/packages/sfp-cli/src/commands/artifacts/fetch.ts index 279aea5a0..50f67c830 100644 --- a/packages/sfp-cli/src/commands/artifacts/fetch.ts +++ b/packages/sfp-cli/src/commands/artifacts/fetch.ts @@ -1,7 +1,7 @@ import SfpCommand from '../../SfpCommand'; import { LoggerLevel, Messages } from '@salesforce/core'; import FetchImpl, { ArtifactVersion } from '../../impl/artifacts/FetchImpl'; -import ReleaseDefinition from '../../impl/release/ReleaseDefinition'; +import ReleaseDefinitionLoader from '../../impl/release/ReleaseDefinitionLoader'; import FetchArtifactsError from '../../impl/artifacts/FetchArtifactsError'; import { ConsoleLogger } from '@flxblio/sfp-logger'; import { Flags } from '@oclif/core'; @@ -58,7 +58,7 @@ export default class Fetch extends SfpCommand { public async execute() { this.validateFlags(); - let releaseDefinition = (await ReleaseDefinition.loadReleaseDefinition(this.flags.releasedefinition)).releaseDefinition; + let releaseDefinition = await ReleaseDefinitionLoader.loadReleaseDefinition(this.flags.releasedefinition); let result: { success: ArtifactVersion[]; failed: ArtifactVersion[]; diff --git a/packages/sfp-cli/src/commands/orchestrator/release.ts b/packages/sfp-cli/src/commands/orchestrator/release.ts index 203dff01f..4c66e9ab3 100644 --- a/packages/sfp-cli/src/commands/orchestrator/release.ts +++ b/packages/sfp-cli/src/commands/orchestrator/release.ts @@ -2,7 +2,7 @@ import SfpCommand from '../../SfpCommand'; import { LoggerLevel, Messages } from '@salesforce/core'; import SFPStatsSender from '../../core/stats/SFPStatsSender'; import ReleaseImpl, { ReleaseProps, ReleaseResult } from '../../impl/release/ReleaseImpl'; -import ReleaseDefinition from '../../impl/release/ReleaseDefinition'; +import ReleaseDefinitionLoader from '../../impl/release/ReleaseDefinitionLoader'; import ReleaseError from '../../errors/ReleaseError'; import path = require('path'); import SFPLogger, { @@ -14,7 +14,7 @@ import SFPLogger, { COLOR_KEY_MESSAGE, ConsoleLogger, } from '@flxblio/sfp-logger'; -import ReleaseDefinitionSchema from '../../impl/release/ReleaseDefinitionSchema'; +import ReleaseDefinition from '../../impl/release/ReleaseDefinition'; import { arrayFlagSfdxStyle, loglevel, logsgroupsymbol, optionalDevHubFlag, requiredUserNameFlag } from '../../flags/sfdxflags'; import { Flags } from '@oclif/core'; @@ -95,6 +95,10 @@ export default class Release extends SfpCommand { message: '--allowunpromotedpackages is deprecated, All packages are allowed', }, }), + changelogByDomains: Flags.boolean({ + description: messages.getMessage('changelogByDomainsFlagDescription'), + hidden: true + }), devhubalias: optionalDevHubFlag, loglevel }; @@ -119,9 +123,9 @@ export default class Release extends SfpCommand { SFPLogger.printHeaderLine('',COLOR_HEADER,LoggerLevel.INFO); - let releaseDefinitions: ReleaseDefinitionSchema[] = []; + let releaseDefinitions: ReleaseDefinition[] = []; for (const pathToReleaseDefintion of this.flags.releasedefinition) { - let releaseDefinition = (await ReleaseDefinition.loadReleaseDefinition(pathToReleaseDefintion)).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) diff --git a/packages/sfp-cli/src/commands/releasedefinition/generate.ts b/packages/sfp-cli/src/commands/releasedefinition/generate.ts index 6cb37317a..5d67be13e 100644 --- a/packages/sfp-cli/src/commands/releasedefinition/generate.ts +++ b/packages/sfp-cli/src/commands/releasedefinition/generate.ts @@ -50,6 +50,10 @@ export default class Generate extends SfpCommand { description: messages.getMessage('forcePushFlagDescription'), dependsOn: ['push'], }), + metadata: Flags.string({ + char: 'm', + description: messages.getMessage('metadataFlagDescription'), + }), loglevel }; @@ -61,6 +65,7 @@ export default class Generate extends SfpCommand { this.flags.configfile, this.flags.releasename, this.flags.branchname, + this.flags.metadata, this.flags.directory, this.flags.nopush, this.flags.forcepush diff --git a/packages/sfp-cli/src/commands/repo/patch.ts b/packages/sfp-cli/src/commands/repo/patch.ts index 65e77aba4..a440066a1 100644 --- a/packages/sfp-cli/src/commands/repo/patch.ts +++ b/packages/sfp-cli/src/commands/repo/patch.ts @@ -1,10 +1,10 @@ import { Messages } from '@salesforce/core'; import SfpCommand from '../../SfpCommand'; -import ReleaseDefinition from '../../impl/release/ReleaseDefinition'; +import ReleaseDefinitionLoader from '../../impl/release/ReleaseDefinitionLoader'; import ProjectConfig from '../../core/project/ProjectConfig'; import GroupConsoleLogs from '../../ui/GroupConsoleLogs'; import FetchImpl from '../../impl/artifacts/FetchImpl'; -import ReleaseDefinitionSchema from '../../impl/release/ReleaseDefinitionSchema'; +import ReleaseDefinition from '../../impl/release/ReleaseDefinition'; import path = require('path'); import ArtifactFetcher, { Artifact } from '../../core/artifacts/ArtifactFetcher'; import SfpPackage, { PackageType } from '../../core/package/SfpPackage'; @@ -127,7 +127,7 @@ export default class Patch extends SfpCommand { private async fetchArtifacts( - releaseDefintions: ReleaseDefinitionSchema[], + releaseDefintions: ReleaseDefinition[], fetchArtifactScript: string, scope: string, npmrcPath: string, @@ -140,17 +140,16 @@ export default class Patch extends SfpCommand { groupSection.end(); } - private async loadReleaseDefintions(releaseDefinitionPaths: []): Promise { - let releaseDefinitions: ReleaseDefinitionSchema[] = []; + private async loadReleaseDefintions(releaseDefinitionPaths: []): Promise { + let releaseDefinitions: ReleaseDefinition[] = []; for (const pathToReleaseDefintion of releaseDefinitionPaths) { - let releaseDefinition = (await ReleaseDefinition.loadReleaseDefinition(pathToReleaseDefintion)) - .releaseDefinition; + let releaseDefinition = await ReleaseDefinitionLoader.loadReleaseDefinition(pathToReleaseDefintion); releaseDefinitions.push(releaseDefinition); } return releaseDefinitions; } - private async overwriteModules(releaseDefinitions: ReleaseDefinitionSchema[], git: Git, logger: Logger) { + private async overwriteModules(releaseDefinitions: ReleaseDefinition[], git: Git, logger: Logger) { let temporaryWorkingDirectory = git.getRepositoryPath(); let revisedProjectConfig = ProjectConfig.getSFDXProjectConfig(temporaryWorkingDirectory); for (const releaseDefinition of releaseDefinitions) { diff --git a/packages/sfp-cli/src/impl/artifacts/FetchAnArtifactFromNPM.ts b/packages/sfp-cli/src/impl/artifacts/FetchAnArtifactFromNPM.ts index f22432972..8d2f5fc87 100644 --- a/packages/sfp-cli/src/impl/artifacts/FetchAnArtifactFromNPM.ts +++ b/packages/sfp-cli/src/impl/artifacts/FetchAnArtifactFromNPM.ts @@ -2,14 +2,12 @@ import * as fs from 'fs-extra'; import child_process = require('child_process'); import path = require('path'); import FetchAnArtifact from './FetchAnArtifact'; -import SFPLogger, { COLOR_WARNING } from '@flxblio/sfp-logger'; +import SFPLogger, { COLOR_WARNING, LoggerLevel } from '@flxblio/sfp-logger'; export class FetchAnArtifactFromNPM implements FetchAnArtifact { constructor(private scope: string, private npmrcPath: string) { - //Check whether the user has already passed in @, and remove it - this.scope= this.scope.replace(/@/g, '').toLowerCase(); - + this.scope = this.scope.replace(/@/g, '').toLowerCase(); if (this.npmrcPath) { try { @@ -45,20 +43,27 @@ export class FetchAnArtifactFromNPM implements FetchAnArtifact { cmd += `@${version}`; - console.log(`Fetching ${packageName} using ${cmd}`); + SFPLogger.log(`Fetching ${packageName} using ${cmd}`,LoggerLevel.INFO); child_process.execSync(cmd, { cwd: artifactDirectory, stdio: 'pipe', }); } catch (error) { + SFPLogger.log( + COLOR_WARNING( + `Artifact for ${packageName} not found in the NPM Registry provided, This might result in deployment failures, Try running with trace log level for more information` + ), + LoggerLevel.INFO + ); + + SFPLogger.log( + error, + LoggerLevel.TRACE + ); + + if (!isToContinueOnMissingArtifact) throw error; - else - SFPLogger.log( - COLOR_WARNING( - `Artifact for ${packageName} missing in NPM Registry provided, This might result in deployment failures` - ) - ); } } } diff --git a/packages/sfp-cli/src/impl/artifacts/FetchImpl.ts b/packages/sfp-cli/src/impl/artifacts/FetchImpl.ts index 33df9a75c..2e77eed9d 100644 --- a/packages/sfp-cli/src/impl/artifacts/FetchImpl.ts +++ b/packages/sfp-cli/src/impl/artifacts/FetchImpl.ts @@ -1,7 +1,7 @@ import * as fs from 'fs-extra'; import Git from '../../core/git/Git'; import GitTags from '../../core/git/GitTags'; -import ReleaseDefinitionSchema from '../release/ReleaseDefinitionSchema'; +import ReleaseDefinition from '../release/ReleaseDefinition'; import FetchArtifactsError from './FetchArtifactsError'; import * as rimraf from 'rimraf'; import FetchArtifactSelector from './FetchArtifactSelector'; @@ -22,7 +22,7 @@ export default class FetchImpl { } public async fetchArtifacts( - releaseDefinitions: ReleaseDefinitionSchema[] + releaseDefinitions: ReleaseDefinition[] ): Promise<{ success: ArtifactVersion[]; failed: ArtifactVersion[]; diff --git a/packages/sfp-cli/src/impl/deploy/DeployImpl.ts b/packages/sfp-cli/src/impl/deploy/DeployImpl.ts index b164ac397..f5340c0ce 100644 --- a/packages/sfp-cli/src/impl/deploy/DeployImpl.ts +++ b/packages/sfp-cli/src/impl/deploy/DeployImpl.ts @@ -23,7 +23,7 @@ import * as _ from 'lodash'; import GroupConsoleLogs from '../../ui/GroupConsoleLogs'; import { ZERO_BORDER_TABLE } from '../../ui/TableConstants'; import convertBuildNumDotDelimToHyphen from '../../core/utils/VersionNumberConverter'; -import ReleaseConfig from '../release/ReleaseConfig'; +import ReleaseConfigLoader from '../release/ReleaseConfigLoader'; import fs from 'fs-extra'; import { Align, getMarkdownTable } from 'markdown-table-ts'; import FileOutputHandler from '../../outputs/FileOutputHandler'; @@ -327,8 +327,8 @@ export default class DeployImpl { else { SFPLogger.log(COLOR_KEY_MESSAGE(`Filtering packages to be deployed based on release config ${COLOR_KEY_VALUE(releaseConfigPath)}`),LoggerLevel.INFO,logger); - let releaseConfig:ReleaseConfig = new ReleaseConfig(logger,releaseConfigPath); - let packages = releaseConfig.getPackagesAsPerReleaseConfig(); + let releaseConfigLoader:ReleaseConfigLoader = new ReleaseConfigLoader(logger,releaseConfigPath); + let packages = releaseConfigLoader.getPackagesAsPerReleaseConfig(); //Filter artifacts based on packages let filteredSfPPackages:SfpPackage[] = []; diff --git a/packages/sfp-cli/src/impl/prepare/PrepareImpl.ts b/packages/sfp-cli/src/impl/prepare/PrepareImpl.ts index f438d7055..9eb11ed76 100644 --- a/packages/sfp-cli/src/impl/prepare/PrepareImpl.ts +++ b/packages/sfp-cli/src/impl/prepare/PrepareImpl.ts @@ -23,10 +23,10 @@ import SFPStatsSender from '../../core/stats/SFPStatsSender'; import ExternalPackage2DependencyResolver from '../../core/package/dependencies/ExternalPackage2DependencyResolver'; import ExternalDependencyDisplayer from '../../core/display/ExternalDependencyDisplayer'; import ReleaseDefinitionGenerator from '../release/ReleaseDefinitionGenerator'; -import ReleaseDefinitionSchema from '../release/ReleaseDefinitionSchema'; +import ReleaseDefinition from '../release/ReleaseDefinition'; import { ZERO_BORDER_TABLE } from '../../ui/TableConstants'; import GroupConsoleLogs from '../../ui/GroupConsoleLogs'; -import ReleaseConfig from '../release/ReleaseConfig'; +import ReleaseConfigLoader from '../release/ReleaseConfigLoader'; import { COLOR_KEY_VALUE } from '@flxblio/sfp-logger'; const Table = require('cli-table'); @@ -114,11 +114,12 @@ export default class PrepareImpl { 'prepare', 'test', undefined, + undefined, true, false, true ); - let releaseDefinition = (await releaseDefinitionGenerator.exec()) as ReleaseDefinitionSchema; + let releaseDefinition = (await releaseDefinitionGenerator.exec()) as ReleaseDefinition; return Object.keys(releaseDefinition.artifacts); } } @@ -288,8 +289,8 @@ export default class PrepareImpl { function includeOnlyPackagesAsPerReleaseConfig(releaseConfigFilePath:string,buildProps: BuildProps,logger?:Logger): BuildProps { if (releaseConfigFilePath) { - let releaseConfig:ReleaseConfig = new ReleaseConfig(logger, releaseConfigFilePath); - buildProps.includeOnlyPackages = releaseConfig.getPackagesAsPerReleaseConfig(); + let releaseConfigLoader:ReleaseConfigLoader = new ReleaseConfigLoader(logger, releaseConfigFilePath); + buildProps.includeOnlyPackages = releaseConfigLoader.getPackagesAsPerReleaseConfig(); printIncludeOnlyPackages(buildProps.includeOnlyPackages); } return buildProps; diff --git a/packages/sfp-cli/src/impl/release/ReleaseConfig.ts b/packages/sfp-cli/src/impl/release/ReleaseConfig.ts index dfa6f13cf..06f4c3fbb 100644 --- a/packages/sfp-cli/src/impl/release/ReleaseConfig.ts +++ b/packages/sfp-cli/src/impl/release/ReleaseConfig.ts @@ -1,115 +1,24 @@ -import * as fs from 'fs-extra'; -import ProjectConfig from '../../core/project/ProjectConfig'; -import Ajv, { _ } from 'ajv'; -import ReleaseDefinitionGeneratorConfigSchema from './ReleaseDefinitionGeneratorConfigSchema'; -import lodash = require('lodash'); -const yaml = require('js-yaml'); -import { Logger } from '@flxblio/sfp-logger'; -const path = require('path'); - -export default class ReleaseConfig { - private _releaseDefinitionGeneratorSchema: ReleaseDefinitionGeneratorConfigSchema; - - get releaseDefinitionGeneratorConfigSchema() { - // Return clone of releaseDefinition for immutability - return lodash.cloneDeep(this._releaseDefinitionGeneratorSchema); - } - - public constructor(private logger: Logger, pathToReleaseDefinition: string, private isExplicitDependencyCheckEnabled:boolean=false) { - this._releaseDefinitionGeneratorSchema = yaml.load(fs.readFileSync(pathToReleaseDefinition, 'utf8')); - this.validateReleaseDefinitionGeneratorConfig(this._releaseDefinitionGeneratorSchema); - - // Easy to handle here than with schema - if ( - this._releaseDefinitionGeneratorSchema.includeOnlyArtifacts && - this.releaseDefinitionGeneratorConfigSchema.excludeArtifacts - ) { - throw new Error('Error: Invalid schema: either use includeArtifacts or excludeArtifacts'); - } - // Easy to handle here than with schema - if ( - this._releaseDefinitionGeneratorSchema.includeOnlyPackageDependencies && - this.releaseDefinitionGeneratorConfigSchema.excludePackageDependencies - ) { - throw new Error( - 'Error: Invalid schema: either use includePackageDependencies or excludePackageDependencies' - ); - } - - // Workaround for jsonschema not supporting validation based on dependency value - if ( - this._releaseDefinitionGeneratorSchema.releasedefinitionProperties?.baselineOrg && - !this._releaseDefinitionGeneratorSchema.releasedefinitionProperties?.skipIfAlreadyInstalled - ) - throw new Error("Release option 'skipIfAlreadyInstalled' must be true for 'baselineOrg'"); - } - - public getPackagesAsPerReleaseConfig(directory?: string): string[] { - let packages: string[] = []; - let projectConfig = ProjectConfig.getSFDXProjectConfig(directory); - //Read sfdx project json - let sfdxPackages = ProjectConfig.getAllPackagesFromProjectConfig(projectConfig); - for (const sfdxPackage of sfdxPackages) { - if (this.getArtifactPredicate(sfdxPackage)) { - packages.push(sfdxPackage); - } - } - - if(packages.length>0) - { - for (const sfdxPackage of sfdxPackages) { - if (this.getPackageDependencyPredicate(sfdxPackage)) { - packages.push(sfdxPackage); - } - } - } - - - return packages; - } - - private validateReleaseDefinitionGeneratorConfig( - releaseDefinitionGeneratorSchema: ReleaseDefinitionGeneratorConfigSchema - ): void { - let schema = fs.readJSONSync( - path.join(__dirname, '..', '..', '..', 'resources', 'schemas', 'releasedefinitiongenerator.schema.json'), - { encoding: 'UTF-8' } - ); - - let validator = new Ajv({ allErrors: true }).compile(schema); - let validationResult = validator(releaseDefinitionGeneratorSchema); - - if (!validationResult) { - let errorMsg: string = - `Release definition generation config does not meet schema requirements, ` + - `found ${validator.errors.length} validation errors:\n`; - - validator.errors.forEach((error, errorNum) => { - errorMsg += `\n${errorNum + 1}: ${error.instancePath}: ${error.message} ${JSON.stringify( - error.params, - null, - 4 - )}`; - }); - - throw new Error(errorMsg); - } - } - - private getArtifactPredicate(artifact: string): boolean { - if (this.releaseDefinitionGeneratorConfigSchema.includeOnlyArtifacts) { - return this.releaseDefinitionGeneratorConfigSchema.includeOnlyArtifacts?.includes(artifact); - } else if (this.releaseDefinitionGeneratorConfigSchema.excludeArtifacts) { - return !this.releaseDefinitionGeneratorConfigSchema.excludeArtifacts?.includes(artifact); - } else if(this.isExplicitDependencyCheckEnabled && this.releaseDefinitionGeneratorConfigSchema.dependencyOn) { - return this.releaseDefinitionGeneratorConfigSchema.dependencyOn?.includes(artifact); - } - else return true; - } - - private getPackageDependencyPredicate(artifact: string): boolean { - if(this.isExplicitDependencyCheckEnabled && this.releaseDefinitionGeneratorConfigSchema.dependencyOn) { - return this.releaseDefinitionGeneratorConfigSchema.dependencyOn?.includes(artifact); - } - } +export default interface ReleaseConfig { + releaseName?: string; + pool?:string; + includeOnlyArtifacts?: string[]; + excludeArtifacts?: string[]; + excludeArtifactsWithTag?: string[]; + excludeAllPackageDependencies?:boolean; + excludePackageDependencies?: string[]; + includeOnlyPackageDependencies?: string[]; + dependencyOn?: string[]; + releasedefinitionProperties?: { + skipIfAlreadyInstalled: boolean; + skipArtifactUpdate:boolean; + baselineOrg?: string; + promotePackagesBeforeDeploymentToOrg?: string; + changelog?: { + repoUrl?: string; + workItemFilters?: string[]; + workItemUrl?: string; + limit?: number; + showAllArtifacts?: boolean; + }; + }; } diff --git a/packages/sfp-cli/src/impl/release/ReleaseConfigLoader.ts b/packages/sfp-cli/src/impl/release/ReleaseConfigLoader.ts new file mode 100644 index 000000000..3805f4da4 --- /dev/null +++ b/packages/sfp-cli/src/impl/release/ReleaseConfigLoader.ts @@ -0,0 +1,115 @@ +import * as fs from 'fs-extra'; +import ProjectConfig from '../../core/project/ProjectConfig'; +import Ajv, { _ } from 'ajv'; +import ReleaseConfig from './ReleaseConfig'; +import lodash = require('lodash'); +const yaml = require('js-yaml'); +import { Logger } from '@flxblio/sfp-logger'; +const path = require('path'); + +export default class ReleaseConfigLoader { + private _releaseConfig: ReleaseConfig; + + get releaseConfig() { + // Return clone of releaseDefinition for immutability + return lodash.cloneDeep(this._releaseConfig); + } + + public constructor(private logger: Logger, pathToReleaseDefinition: string, private isExplicitDependencyCheckEnabled:boolean=false) { + this._releaseConfig = yaml.load(fs.readFileSync(pathToReleaseDefinition, 'utf8')); + this.validateReleaseDefinitionGeneratorConfig(this._releaseConfig); + + // Easy to handle here than with schema + if ( + this._releaseConfig.includeOnlyArtifacts && + this.releaseConfig.excludeArtifacts + ) { + throw new Error('Error: Invalid schema: either use includeArtifacts or excludeArtifacts'); + } + // Easy to handle here than with schema + if ( + this._releaseConfig.includeOnlyPackageDependencies && + this.releaseConfig.excludePackageDependencies + ) { + throw new Error( + 'Error: Invalid schema: either use includePackageDependencies or excludePackageDependencies' + ); + } + + // Workaround for jsonschema not supporting validation based on dependency value + if ( + this._releaseConfig.releasedefinitionProperties?.baselineOrg && + !this._releaseConfig.releasedefinitionProperties?.skipIfAlreadyInstalled + ) + throw new Error("Release option 'skipIfAlreadyInstalled' must be true for 'baselineOrg'"); + } + + public getPackagesAsPerReleaseConfig(directory?: string): string[] { + let packages: string[] = []; + let projectConfig = ProjectConfig.getSFDXProjectConfig(directory); + //Read sfdx project json + let sfdxPackages = ProjectConfig.getAllPackagesFromProjectConfig(projectConfig); + for (const sfdxPackage of sfdxPackages) { + if (this.getArtifactPredicate(sfdxPackage)) { + packages.push(sfdxPackage); + } + } + + if(packages.length>0) + { + for (const sfdxPackage of sfdxPackages) { + if (this.getPackageDependencyPredicate(sfdxPackage)) { + packages.push(sfdxPackage); + } + } + } + + + return packages; + } + + private validateReleaseDefinitionGeneratorConfig( + releaseDefinitionGeneratorSchema: ReleaseConfig + ): void { + let schema = fs.readJSONSync( + path.join(__dirname, '..', '..', '..', 'resources', 'schemas', 'release-config.schema.json'), + { encoding: 'UTF-8' } + ); + + let validator = new Ajv({ allErrors: true }).compile(schema); + let validationResult = validator(releaseDefinitionGeneratorSchema); + + if (!validationResult) { + let errorMsg: string = + `Release definition generation config does not meet schema requirements, ` + + `found ${validator.errors.length} validation errors:\n`; + + validator.errors.forEach((error, errorNum) => { + errorMsg += `\n${errorNum + 1}: ${error.instancePath}: ${error.message} ${JSON.stringify( + error.params, + null, + 4 + )}`; + }); + + throw new Error(errorMsg); + } + } + + private getArtifactPredicate(artifact: string): boolean { + if (this.releaseConfig.includeOnlyArtifacts) { + return this.releaseConfig.includeOnlyArtifacts?.includes(artifact); + } else if (this.releaseConfig.excludeArtifacts) { + return !this.releaseConfig.excludeArtifacts?.includes(artifact); + } else if(this.isExplicitDependencyCheckEnabled && this.releaseConfig.dependencyOn) { + return this.releaseConfig.dependencyOn?.includes(artifact); + } + else return true; + } + + private getPackageDependencyPredicate(artifact: string): boolean { + if(this.isExplicitDependencyCheckEnabled && this.releaseConfig.dependencyOn) { + return this.releaseConfig.dependencyOn?.includes(artifact); + } + } +} diff --git a/packages/sfp-cli/src/impl/release/ReleaseDefinition.ts b/packages/sfp-cli/src/impl/release/ReleaseDefinition.ts index accfb708b..a2983888f 100644 --- a/packages/sfp-cli/src/impl/release/ReleaseDefinition.ts +++ b/packages/sfp-cli/src/impl/release/ReleaseDefinition.ts @@ -1,79 +1,23 @@ -import ReleaseDefinitionSchema from './ReleaseDefinitionSchema'; -import Ajv from 'ajv'; -const yaml = require('js-yaml'); -import lodash = require('lodash'); -import get18DigitSalesforceId from '../../utils/Get18DigitSalesforceId'; -import Git from '../../core/git/Git'; -import { ConsoleLogger } from '@flxblio/sfp-logger'; -const fs = require('fs-extra'); -const path = require('path'); - -export default class ReleaseDefinition { - get releaseDefinition() { - // Return clone of releaseDefinition for immutability - return lodash.cloneDeep(this._releaseDefinitionSchema); - } - private constructor(private _releaseDefinitionSchema: ReleaseDefinitionSchema) { - this.validateReleaseDefinition(this._releaseDefinitionSchema); - - // Workaround for jsonschema not supporting validation based on dependency value - if (this._releaseDefinitionSchema.baselineOrg && !this._releaseDefinitionSchema.skipIfAlreadyInstalled) - throw new Error("Release option 'skipIfAlreadyInstalled' must be true for 'baselineOrg'"); - - if (this._releaseDefinitionSchema.packageDependencies) { - this.convertPackageDependenciesIdTo18Digits(this._releaseDefinitionSchema.packageDependencies); - } - } - - public static async loadReleaseDefinition(pathToReleaseDefinition: string) { - //Check whether path contains gitRef - let releaseDefinitionSchema: ReleaseDefinitionSchema; - try { - if (pathToReleaseDefinition.includes(':')) { - let git = await Git.initiateRepo(); - await git.fetch(); - let releaseFile = await git.show([pathToReleaseDefinition]); - releaseDefinitionSchema = yaml.load(releaseFile); - } else { - releaseDefinitionSchema = yaml.load(fs.readFileSync(pathToReleaseDefinition, 'UTF8')); - } - } catch (error) { - throw new Error(`Unable to read the release definition file due to ${JSON.stringify(error)}`); - } - - let releaseDefinition = new ReleaseDefinition(releaseDefinitionSchema); - return releaseDefinition; - } - - private convertPackageDependenciesIdTo18Digits(packageDependencies: { [p: string]: string }) { - for (let pkg in packageDependencies) { - packageDependencies[pkg] = get18DigitSalesforceId(packageDependencies[pkg]); - } - } - - private validateReleaseDefinition(releaseDefinition: ReleaseDefinitionSchema): void { - let schema = fs.readJSONSync( - path.join(__dirname, '..', '..', '..', 'resources', 'schemas', 'releasedefinition.schema.json'), - { encoding: 'UTF-8' } - ); - - let validator = new Ajv({ allErrors: true }).compile(schema); - let validationResult = validator(releaseDefinition); - - if (!validationResult) { - let errorMsg: string = - `Release definition does not meet schema requirements, ` + - `found ${validator.errors.length} validation errors:\n`; - - validator.errors.forEach((error, errorNum) => { - errorMsg += `\n${errorNum + 1}: ${error.instancePath}: ${error.message} ${JSON.stringify( - error.params, - null, - 4 - )}`; - }); - - throw new Error(errorMsg); - } - } +export default interface ReleaseDefinition { + release: string; + releaseConfigName?:string; + metadata?: any; + skipIfAlreadyInstalled: boolean; + skipArtifactUpdate:boolean; + baselineOrg?: string; + artifacts: { + [p: string]: string; + }; + packageDependencies?: { + [p: string]: string; + }; + promotePackagesBeforeDeploymentToOrg?: string; + changelog?: { + repoUrl?: string; + workItemFilter?:string; + workItemFilters?: string[]; + workItemUrl?: string; + limit?: number; + showAllArtifacts?: boolean; + }; } diff --git a/packages/sfp-cli/src/impl/release/ReleaseDefinitionGenerator.ts b/packages/sfp-cli/src/impl/release/ReleaseDefinitionGenerator.ts index 1b283240b..f64b63f3e 100644 --- a/packages/sfp-cli/src/impl/release/ReleaseDefinitionGenerator.ts +++ b/packages/sfp-cli/src/impl/release/ReleaseDefinitionGenerator.ts @@ -1,10 +1,10 @@ import { GitError } from 'simple-git'; import * as fs from 'fs-extra'; -import ReleaseDefinitionSchema from './ReleaseDefinitionSchema'; +import ReleaseDefinition from './ReleaseDefinition'; import ProjectConfig from '../../core/project/ProjectConfig'; import Ajv, { _ } from 'ajv'; import SFPLogger, { COLOR_HEADER, COLOR_KEY_MESSAGE, Logger } from '@flxblio/sfp-logger'; -import ReleaseDefinitionGeneratorConfigSchema from './ReleaseDefinitionGeneratorConfigSchema'; +import ReleaseConfig from './ReleaseConfig'; import lodash = require('lodash'); import { LoggerLevel } from '@flxblio/sfp-logger'; import Git from '../../core/git/Git'; @@ -14,11 +14,11 @@ const yaml = require('js-yaml'); const path = require('path'); export default class ReleaseDefinitionGenerator { - private _releaseDefinitionGeneratorSchema: ReleaseDefinitionGeneratorConfigSchema; + private _releaseConfiguration: ReleaseConfig; - get releaseDefinitionGeneratorConfigSchema() { + get releaseConfiguration() { // Return clone of releaseDefinition for immutability - return lodash.cloneDeep(this._releaseDefinitionGeneratorSchema); + return lodash.cloneDeep(this._releaseConfiguration); } public constructor( @@ -27,25 +27,36 @@ export default class ReleaseDefinitionGenerator { pathToReleaseDefinition: string, private releaseName: string, private branch: string, + private metadata: any, private directory?: string, private noPush: boolean = false, private forcePush: boolean = false, private inMemoryMode:boolean = false ) { - this._releaseDefinitionGeneratorSchema = yaml.load(fs.readFileSync(pathToReleaseDefinition, 'utf8')); - this.validateReleaseDefinitionGeneratorConfig(this._releaseDefinitionGeneratorSchema); + this._releaseConfiguration = yaml.load(fs.readFileSync(pathToReleaseDefinition, 'utf8')); + this.validateReleaseDefinitionGeneratorConfig(this._releaseConfiguration); + + + //Attempt to parse metadata flag into JSON + if (this.metadata) { + try { + this.metadata = JSON.parse(this.metadata); + } catch (error) { + throw new Error(`Invalid JSON for metadata flag: ${error}`); + } + } // Easy to handle here than with schema if ( - this._releaseDefinitionGeneratorSchema.includeOnlyArtifacts && - this.releaseDefinitionGeneratorConfigSchema.excludeArtifacts + this._releaseConfiguration.includeOnlyArtifacts && + this.releaseConfiguration.excludeArtifacts ) { throw new Error('Error: Invalid schema: either use includeArtifacts or excludeArtifacts'); } // Easy to handle here than with schema if ( - this._releaseDefinitionGeneratorSchema.includeOnlyPackageDependencies && - this.releaseDefinitionGeneratorConfigSchema.excludePackageDependencies + this._releaseConfiguration.includeOnlyPackageDependencies && + this.releaseConfiguration.excludePackageDependencies ) { throw new Error( 'Error: Invalid schema: either use includePackageDependencies or excludePackageDependencies' @@ -54,13 +65,13 @@ export default class ReleaseDefinitionGenerator { // Workaround for jsonschema not supporting validation based on dependency value if ( - this._releaseDefinitionGeneratorSchema.releasedefinitionProperties?.baselineOrg && - !this._releaseDefinitionGeneratorSchema.releasedefinitionProperties?.skipIfAlreadyInstalled + this._releaseConfiguration.releasedefinitionProperties?.baselineOrg && + !this._releaseConfiguration.releasedefinitionProperties?.skipIfAlreadyInstalled ) throw new Error("Release option 'skipIfAlreadyInstalled' must be true for 'baselineOrg'"); } - async exec(): Promise { @@ -92,7 +103,7 @@ export default class ReleaseDefinitionGenerator { ); } - private async execHandler(): Promise { @@ -146,7 +157,7 @@ export default class ReleaseDefinitionGenerator { } } - if (!this.releaseDefinitionGeneratorConfigSchema.excludeAllPackageDependencies) { + if (!this.releaseConfiguration.excludeAllPackageDependencies) { let allExternalPackages = ProjectConfig.getAllExternalPackages(projectConfig); for (const externalPackage of allExternalPackages) { if ( @@ -161,7 +172,7 @@ export default class ReleaseDefinitionGenerator { return { artifacts, packageDependencies }; } - private async generateReleaseDefintion(artifacts: any, packageDependencies: any, git: Git): Promise { @@ -179,8 +190,10 @@ export default class ReleaseDefinitionGenerator { return obj; }, {}); - let releaseDefinition: ReleaseDefinitionSchema = { + let releaseDefinition: ReleaseDefinition = { release: this.releaseName, + releaseConfigName : this.releaseConfiguration?.releaseName, + metadata: this.metadata, skipIfAlreadyInstalled: true, skipArtifactUpdate:false, artifacts: artifacts, @@ -190,18 +203,18 @@ export default class ReleaseDefinitionGenerator { if (Object.keys(packageDependencies).length > 0) releaseDefinition.packageDependencies = packageDependencies; //add promotePackagesBeforeDeploymentToOrg - releaseDefinition.promotePackagesBeforeDeploymentToOrg = this.releaseDefinitionGeneratorConfigSchema.releasedefinitionProperties?.promotePackagesBeforeDeploymentToOrg; + releaseDefinition.promotePackagesBeforeDeploymentToOrg = this.releaseConfiguration.releasedefinitionProperties?.promotePackagesBeforeDeploymentToOrg; //override skip if already installed - if(this.releaseDefinitionGeneratorConfigSchema.releasedefinitionProperties?.skipIfAlreadyInstalled) - releaseDefinition.skipIfAlreadyInstalled = this.releaseDefinitionGeneratorConfigSchema.releasedefinitionProperties?.skipIfAlreadyInstalled; + if(this.releaseConfiguration.releasedefinitionProperties?.skipIfAlreadyInstalled) + releaseDefinition.skipIfAlreadyInstalled = this.releaseConfiguration.releasedefinitionProperties?.skipIfAlreadyInstalled; //override skip artifact update - if(this.releaseDefinitionGeneratorConfigSchema.releasedefinitionProperties?.skipArtifactUpdate) - releaseDefinition.skipArtifactUpdate = this.releaseDefinitionGeneratorConfigSchema.releasedefinitionProperties?.skipArtifactUpdate; + if(this.releaseConfiguration.releasedefinitionProperties?.skipArtifactUpdate) + releaseDefinition.skipArtifactUpdate = this.releaseConfiguration.releasedefinitionProperties?.skipArtifactUpdate; //Add changelog info - releaseDefinition.changelog = this.releaseDefinitionGeneratorConfigSchema.releasedefinitionProperties?.changelog; + releaseDefinition.changelog = this.releaseConfiguration.releasedefinitionProperties?.changelog; if(this.inMemoryMode) return releaseDefinition; @@ -240,10 +253,10 @@ export default class ReleaseDefinitionGenerator { } private validateReleaseDefinitionGeneratorConfig( - releaseDefinitionGeneratorSchema: ReleaseDefinitionGeneratorConfigSchema + releaseDefinitionGeneratorSchema: ReleaseConfig ): void { let schema = fs.readJSONSync( - path.join(__dirname, '..', '..', '..', 'resources', 'schemas', 'releasedefinitiongenerator.schema.json'), + path.join(__dirname, '..', '..', '..', 'resources', 'schemas', 'release-config.schema.json'), { encoding: 'UTF-8' } ); @@ -268,18 +281,18 @@ export default class ReleaseDefinitionGenerator { } private getArtifactPredicate(artifact: string): boolean { - if (this.releaseDefinitionGeneratorConfigSchema.includeOnlyArtifacts) { - return this.releaseDefinitionGeneratorConfigSchema.includeOnlyArtifacts?.includes(artifact); - } else if (this.releaseDefinitionGeneratorConfigSchema.excludeArtifacts) { - return !this.releaseDefinitionGeneratorConfigSchema.excludeArtifacts?.includes(artifact); + if (this.releaseConfiguration.includeOnlyArtifacts) { + return this.releaseConfiguration.includeOnlyArtifacts?.includes(artifact); + } else if (this.releaseConfiguration.excludeArtifacts) { + return !this.releaseConfiguration.excludeArtifacts?.includes(artifact); } else return true; } private getDependencyPredicate(artifact: string): boolean { - if (this.releaseDefinitionGeneratorConfigSchema.includeOnlyPackageDependencies) { - return this.releaseDefinitionGeneratorConfigSchema.includeOnlyPackageDependencies?.includes(artifact); - } else if (this.releaseDefinitionGeneratorConfigSchema.excludePackageDependencies) { - return !this.releaseDefinitionGeneratorConfigSchema.excludePackageDependencies?.includes(artifact); + if (this.releaseConfiguration.includeOnlyPackageDependencies) { + return this.releaseConfiguration.includeOnlyPackageDependencies?.includes(artifact); + } else if (this.releaseConfiguration.excludePackageDependencies) { + return !this.releaseConfiguration.excludePackageDependencies?.includes(artifact); } else return true; } } \ No newline at end of file diff --git a/packages/sfp-cli/src/impl/release/ReleaseDefinitionGeneratorConfigSchema.ts b/packages/sfp-cli/src/impl/release/ReleaseDefinitionGeneratorConfigSchema.ts deleted file mode 100644 index 05d3d8ab9..000000000 --- a/packages/sfp-cli/src/impl/release/ReleaseDefinitionGeneratorConfigSchema.ts +++ /dev/null @@ -1,21 +0,0 @@ -export default interface ReleaseDefinitionGeneratorSchema { - includeOnlyArtifacts?: string[]; - excludeArtifacts?: string[]; - excludeArtifactsWithTag?: string[]; - excludeAllPackageDependencies?:boolean; - excludePackageDependencies?: string[]; - includeOnlyPackageDependencies?: string[]; - releasedefinitionProperties?: { - skipIfAlreadyInstalled: boolean; - skipArtifactUpdate:boolean; - baselineOrg?: string; - promotePackagesBeforeDeploymentToOrg?: string; - changelog?: { - repoUrl?: string; - workItemFilters?: string[]; - workItemUrl?: string; - limit?: number; - showAllArtifacts?: boolean; - }; - }; -} diff --git a/packages/sfp-cli/src/impl/release/ReleaseDefinitionLoader.ts b/packages/sfp-cli/src/impl/release/ReleaseDefinitionLoader.ts new file mode 100644 index 000000000..3c11cd4b8 --- /dev/null +++ b/packages/sfp-cli/src/impl/release/ReleaseDefinitionLoader.ts @@ -0,0 +1,79 @@ +import ReleaseDefinition from './ReleaseDefinition'; +import Ajv from 'ajv'; +const yaml = require('js-yaml'); +import lodash = require('lodash'); +import get18DigitSalesforceId from '../../utils/Get18DigitSalesforceId'; +import Git from '../../core/git/Git'; +import { ConsoleLogger } from '@flxblio/sfp-logger'; +const fs = require('fs-extra'); +const path = require('path'); + +export default class ReleaseDefinitionLoader { + get releaseDefinition() { + // Return clone of releaseDefinition for immutability + return lodash.cloneDeep(this._releaseDefinition); + } + private constructor(private _releaseDefinition: ReleaseDefinition) { + this.validateReleaseDefinition(this._releaseDefinition); + + // Workaround for jsonschema not supporting validation based on dependency value + if (this._releaseDefinition.baselineOrg && !this._releaseDefinition.skipIfAlreadyInstalled) + throw new Error("Release option 'skipIfAlreadyInstalled' must be true for 'baselineOrg'"); + + if (this._releaseDefinition.packageDependencies) { + this.convertPackageDependenciesIdTo18Digits(this._releaseDefinition.packageDependencies); + } + } + + public static async loadReleaseDefinition(pathToReleaseDefinition: string): Promise { + //Check whether path contains gitRef + let releaseDefinition: ReleaseDefinition; + try { + if (pathToReleaseDefinition.includes(':')) { + let git = await Git.initiateRepo(); + await git.fetch(); + let releaseFile = await git.show([pathToReleaseDefinition]); + releaseDefinition = yaml.load(releaseFile); + } else { + releaseDefinition = yaml.load(fs.readFileSync(pathToReleaseDefinition, 'UTF8')); + } + } catch (error) { + throw new Error(`Unable to read the release definition file due to ${JSON.stringify(error)}`); + } + + let releaseDefinitionLoader = new ReleaseDefinitionLoader(releaseDefinition); + return releaseDefinitionLoader.releaseDefinition; + } + + private convertPackageDependenciesIdTo18Digits(packageDependencies: { [p: string]: string }) { + for (let pkg in packageDependencies) { + packageDependencies[pkg] = get18DigitSalesforceId(packageDependencies[pkg]); + } + } + + private validateReleaseDefinition(releaseDefinition: ReleaseDefinition): void { + let schema = fs.readJSONSync( + path.join(__dirname, '..', '..', '..', 'resources', 'schemas', 'release-defn.schema.json'), + { encoding: 'UTF-8' } + ); + + let validator = new Ajv({ allErrors: true }).compile(schema); + let validationResult = validator(releaseDefinition); + + if (!validationResult) { + let errorMsg: string = + `Release definition does not meet schema requirements, ` + + `found ${validator.errors.length} validation errors:\n`; + + validator.errors.forEach((error, errorNum) => { + errorMsg += `\n${errorNum + 1}: ${error.instancePath}: ${error.message} ${JSON.stringify( + error.params, + null, + 4 + )}`; + }); + + throw new Error(errorMsg); + } + } +} diff --git a/packages/sfp-cli/src/impl/release/ReleaseDefinitionSchema.ts b/packages/sfp-cli/src/impl/release/ReleaseDefinitionSchema.ts deleted file mode 100644 index b8f221291..000000000 --- a/packages/sfp-cli/src/impl/release/ReleaseDefinitionSchema.ts +++ /dev/null @@ -1,21 +0,0 @@ -export default interface ReleaseDefinitionSchema { - release: string; - skipIfAlreadyInstalled: boolean; - skipArtifactUpdate:boolean; - baselineOrg?: string; - artifacts: { - [p: string]: string; - }; - packageDependencies?: { - [p: string]: string; - }; - promotePackagesBeforeDeploymentToOrg?: string; - changelog?: { - repoUrl?: string; - workItemFilter?:string; - workItemFilters?: string[]; - workItemUrl?: string; - limit?: number; - showAllArtifacts?: boolean; - }; -} diff --git a/packages/sfp-cli/src/impl/release/ReleaseImpl.ts b/packages/sfp-cli/src/impl/release/ReleaseImpl.ts index 50cc5965c..463980bf8 100644 --- a/packages/sfp-cli/src/impl/release/ReleaseImpl.ts +++ b/packages/sfp-cli/src/impl/release/ReleaseImpl.ts @@ -1,4 +1,4 @@ -import ReleaseDefinitionSchema from './ReleaseDefinitionSchema'; +import ReleaseDefinition from './ReleaseDefinition'; import DeployImpl, { DeployProps, DeploymentMode, DeploymentResult } from '../deploy/DeployImpl'; import SFPLogger, { COLOR_HEADER, COLOR_KEY_MESSAGE, ConsoleLogger, Logger, LoggerLevel } from '@flxblio/sfp-logger'; import { Stage } from '../Stage'; @@ -15,7 +15,7 @@ import FetchImpl from '../artifacts/FetchImpl'; import GroupConsoleLogs from '../../ui/GroupConsoleLogs'; export interface ReleaseProps { - releaseDefinitions: ReleaseDefinitionSchema[]; + releaseDefinitions: ReleaseDefinition[]; targetOrg: string; fetchArtifactScript: string; isNpm: boolean; @@ -195,9 +195,9 @@ export default class ReleaseImpl { } private async deployArtifacts( - releaseDefinitions: ReleaseDefinitionSchema[] - ): Promise<{ releaseDefinition: ReleaseDefinitionSchema; result: DeploymentResult }[]> { - let deploymentResults: { releaseDefinition: ReleaseDefinitionSchema; result: DeploymentResult }[] = []; + releaseDefinitions: ReleaseDefinition[] + ): Promise<{ releaseDefinition: ReleaseDefinition; result: DeploymentResult }[]> { + let deploymentResults: { releaseDefinition: ReleaseDefinition; result: DeploymentResult }[] = []; for (const releaseDefinition of releaseDefinitions) { let groupSection = new GroupConsoleLogs(`Release ${releaseDefinition.release}`).begin(); SFPLogger.log(EOL); @@ -240,7 +240,7 @@ export default class ReleaseImpl { } private async installPackageDependencies( - releaseDefinitions: ReleaseDefinitionSchema[], + releaseDefinitions: ReleaseDefinition[], targetOrg: string, keys: string, waitTime: number @@ -319,7 +319,7 @@ export default class ReleaseImpl { return output; } - private displayReleaseInfo(releaseDefinition: ReleaseDefinitionSchema, props: ReleaseProps) { + private displayReleaseInfo(releaseDefinition: ReleaseDefinition, props: ReleaseProps) { SFPLogger.printHeaderLine('',COLOR_HEADER,LoggerLevel.INFO); SFPLogger.log(COLOR_KEY_MESSAGE(`Release: ${releaseDefinition.release}`)); @@ -352,7 +352,7 @@ interface InstallDependenciesResult { } export interface ReleaseResult { - succeededDeployments: { releaseDefinition: ReleaseDefinitionSchema; result: DeploymentResult }[]; - failedDeployments: { releaseDefinition: ReleaseDefinitionSchema; result: DeploymentResult }[]; + succeededDeployments: { releaseDefinition: ReleaseDefinition; result: DeploymentResult }[]; + failedDeployments: { releaseDefinition: ReleaseDefinition; result: DeploymentResult }[]; installDependenciesResult: InstallDependenciesResult; } diff --git a/packages/sfp-cli/src/impl/validate/ValidateImpl.ts b/packages/sfp-cli/src/impl/validate/ValidateImpl.ts index 7d52ed3f4..b4df61c6c 100644 --- a/packages/sfp-cli/src/impl/validate/ValidateImpl.ts +++ b/packages/sfp-cli/src/impl/validate/ValidateImpl.ts @@ -48,7 +48,7 @@ import ExternalPackage2DependencyResolver from "../../core/package/dependencies/ import ExternalDependencyDisplayer from "../../core/display/ExternalDependencyDisplayer"; import { PreDeployHook } from "../deploy/PreDeployHook"; import GroupConsoleLogs from "../../ui/GroupConsoleLogs"; -import ReleaseConfig from "../release/ReleaseConfig"; +import ReleaseConfigLoader from "../release/ReleaseConfigLoader"; import { mapInstalledArtifactstoPkgAndCommits } from "../../utils/FetchArtifactsFromOrg"; import { ApexTestValidator } from "./ApexTestValidator"; import OrgInfoDisplayer from "../../ui/OrgInfoDisplayer"; @@ -509,12 +509,12 @@ export default class ValidateImpl implements PostDeployHook, PreDeployHook { props.validationMode === ValidationMode.THOROUGH_LIMITED_BY_RELEASE_CONFIG ) { - let releaseConfig: ReleaseConfig = new ReleaseConfig( + let releaseConfigLoader: ReleaseConfigLoader = new ReleaseConfigLoader( logger, props.releaseConfigPath, true ); - return releaseConfig.getPackagesAsPerReleaseConfig(); + return releaseConfigLoader.getPackagesAsPerReleaseConfig(); } } diff --git a/packages/sfp-cli/tests/impl/release/ReleaseDefinition.test.ts b/packages/sfp-cli/tests/impl/release/ReleaseDefinition.test.ts index a4820edba..55655cc83 100644 --- a/packages/sfp-cli/tests/impl/release/ReleaseDefinition.test.ts +++ b/packages/sfp-cli/tests/impl/release/ReleaseDefinition.test.ts @@ -1,6 +1,6 @@ import { jest, expect } from '@jest/globals'; const fs = require('fs-extra'); -import ReleaseDefinition from '../../../src/impl/release/ReleaseDefinition'; +import ReleaseDefinitionLoader from '../../../src/impl/release/ReleaseDefinitionLoader'; describe('Given a release definition, validateReleaseDefinition', () => { let releaseDefinitionYaml: string; @@ -18,7 +18,7 @@ describe('Given a release definition, validateReleaseDefinition', () => { `; expect(async () => { - await ReleaseDefinition.loadReleaseDefinition('path'); + await ReleaseDefinitionLoader.loadReleaseDefinition('path'); }).rejects.toThrowError(); }); @@ -29,7 +29,7 @@ describe('Given a release definition, validateReleaseDefinition', () => { `; expect(async () => { - await ReleaseDefinition.loadReleaseDefinition('path'); + await ReleaseDefinitionLoader.loadReleaseDefinition('path'); }).rejects.toThrowError(); }); @@ -43,7 +43,7 @@ describe('Given a release definition, validateReleaseDefinition', () => { `; expect(async () => { - await ReleaseDefinition.loadReleaseDefinition('path'); + await ReleaseDefinitionLoader.loadReleaseDefinition('path'); }).toBeDefined(); }); @@ -57,7 +57,7 @@ describe('Given a release definition, validateReleaseDefinition', () => { `; expect(async () => { - await ReleaseDefinition.loadReleaseDefinition('path'); + await ReleaseDefinitionLoader.loadReleaseDefinition('path'); }).rejects.toThrowError(); }); @@ -71,7 +71,7 @@ describe('Given a release definition, validateReleaseDefinition', () => { `; expect(async () => { - await ReleaseDefinition.loadReleaseDefinition('path'); + await ReleaseDefinitionLoader.loadReleaseDefinition('path'); }).toBeDefined(); }); @@ -85,7 +85,7 @@ describe('Given a release definition, validateReleaseDefinition', () => { `; expect(async () => { - await ReleaseDefinition.loadReleaseDefinition('path'); + await ReleaseDefinitionLoader.loadReleaseDefinition('path'); }).rejects.toThrowError(); }); @@ -103,7 +103,7 @@ describe('Given a release definition, validateReleaseDefinition', () => { `; expect(async () => { - await ReleaseDefinition.loadReleaseDefinition('path'); + await ReleaseDefinitionLoader.loadReleaseDefinition('path'); }).toBeDefined(); }); @@ -119,7 +119,7 @@ describe('Given a release definition, validateReleaseDefinition', () => { `; expect(async () => { - await ReleaseDefinition.loadReleaseDefinition('path'); + await ReleaseDefinitionLoader.loadReleaseDefinition('path'); }).rejects.toThrow(); }); });