From d1f4f8e707c647fe606dbf305b036ef7e51d9366 Mon Sep 17 00:00:00 2001 From: Rocko1204 Date: Thu, 10 Nov 2022 21:58:33 +0100 Subject: [PATCH] feat/new command package-validate - add validation job for source packages --- package.json | 2 +- src/commands/rocko/package/validate.ts | 124 ++++++++++++++++++++++++- src/interfaces/package.ts | 7 ++ 3 files changed, 128 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index bc38984..9efbbfc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@rocko1204/rocko-sfdx", "description": "Salesforce DX (SFDX) commands to support salesforce developments & deployments", - "version": "1.1.1", + "version": "1.2.1", "author": "Ronny Rokitta", "bugs": "https://github.com/Rocko1204/rocko-sfdx/issues", "dependencies": { diff --git a/src/commands/rocko/package/validate.ts b/src/commands/rocko/package/validate.ts index 9c1c050..45c1327 100644 --- a/src/commands/rocko/package/validate.ts +++ b/src/commands/rocko/package/validate.ts @@ -11,6 +11,7 @@ import {AnyJson} from '@salesforce/ts-types'; import { ComponentSet, MetadataApiDeploy, MetadataResolver,DeployDetails } from '@salesforce/source-deploy-retrieve'; import * as Table from 'cli-table3'; import * as path from 'path'; +import * as fs from 'fs'; import {LOGOBANNER} from '../../../utils'; const util = require('util'); const exec = util.promisify(require('child_process').exec); @@ -26,7 +27,7 @@ import { COLOR_ERROR } from '../../../utils'; import { QueryResult } from 'jsforce'; -import {PackageDirLarge, UnlockedPackageInfo, PackageDirLargeWithDep, CodeCoverageWarnings, DeployError, ApexTestQueueResult, ApexTestclassCheck} from "../../../interfaces/package"; +import {PackageDirLarge, UnlockedPackageInfo, PackageDirLargeWithDep, CodeCoverageWarnings, DeployError, ApexTestQueueResult, ApexTestclassCheck,SourcePackageComps} from "../../../interfaces/package"; import {ApexClass, ApexTestQueueItem, ApexCodeCoverageAggregate, ApexTestResult} from "../../../interfaces/sobject"; @@ -108,10 +109,21 @@ export default class PackageCheck extends SfdxCommand { Logger(`OMG only a source package 😒. Start validation`, COLOR_INFO); Logger(`👆. Important. Any apex class needs a code coverage 75 %`, COLOR_INFO); Logger(`👆. Important. Please use a sandbox org to validate`, COLOR_INFO); - Logger(`⚠️⚠️⚠️👆. This check is in progress. Please wait for the next version update ⚠️⚠️⚠️`, COLOR_WARNING); + + //execute preDeployment Scripts + if (value.preDeploymentScript && this.flags.deploymentscripts) { + Logger(`☝ Found pre deployment script for package ${key}`,COLOR_INFO); + await this.runDeploymentSteps(value.preDeploymentScript, 'preDeployment', key); + } + await this.validateSourcePackage(path.normalize(value.path), key); + //execute postDeployment Scripts + if (value.postDeploymentScript && this.flags.deploymentscripts) { + Logger(`☝ Found post deployment script for package ${key}`,COLOR_INFO); + await this.runDeploymentSteps(value.postDeploymentScript, 'postDeployment', key); + } } } - Logger(`Yippiee. 🤙 Validation finsihed without errors. Great 🤜🤛`,COLOR_HEADER); + Logger(`Yippiee. 🤙 Validaton finsihed without errors. Great 🤜🤛`,COLOR_HEADER); return {} } @@ -432,7 +444,7 @@ First the dependecies packages. And then this package.` if (jobId) { recordResult.push(jobId); } else { - throw new SfError(`Post Request to Queue runs on error`); + new SfError(`Post Request to Queue runs on error`); } } catch (e) { throw new SfError(`Insert to queue runs on error`); @@ -549,4 +561,108 @@ First the dependecies packages. And then this package.` throw new SfError(`Please fix this issues from the table and try again.`); } } + private async validateSourcePackage(path: string, pck: string) { + Logger(`💪 Start Deployment and Tests for source package.`,COLOR_HEADER); + let username = this.org.getConnection().getUsername(); + const sourceComps = await this.getApexClassesForSource(path); + const testLevel = sourceComps.apexTestclassNames.length > 0 ? 'RunSpecifiedTests' : 'NoTestRun'; + Logger(`Validate source package: ${pck}`,COLOR_HEADER); + Logger(`${COLOR_NOTIFY('Path:')} ${COLOR_INFO(path)}`); + Logger(`${COLOR_NOTIFY('Metadata Size:')} ${COLOR_INFO(sourceComps.comps.length)}`); + Logger(`${COLOR_NOTIFY('TestLevel:')} ${COLOR_INFO(testLevel)}`); + Logger(`${COLOR_NOTIFY('Username:')} ${COLOR_INFO(username)}`); + Logger( + `${COLOR_NOTIFY('ApexClasses:')} ${ + sourceComps.apexClassNames.length > 0 + ? COLOR_INFO(sourceComps.apexClassNames.join()) + : COLOR_INFO('no Apex Classes in source package') + }` + ); + Logger( + `${COLOR_NOTIFY('ApexTestClasses:')} ${ + sourceComps.apexTestclassNames.length > 0 + ? COLOR_INFO(sourceComps.apexTestclassNames.join()) + : COLOR_INFO('no Apex Test Classes in source package') + }` + ); + + if (sourceComps.apexClassNames.length > 0 && sourceComps.apexTestclassNames.length === 0) { + throw new SfError( + `Found apex class(es) for package ${pck} but no testclass(es). Please create a new testclass.` + ); + } + + const deploy: MetadataApiDeploy = await ComponentSet.fromSource(path).deploy({ + usernameOrConnection: username, + apiOptions: { checkOnly: true, testLevel: testLevel, runTests: sourceComps.apexTestclassNames }, + }); + // Attach a listener to check the deploy status on each poll + let counter = 0; + deploy.onUpdate((response) => { + if (counter === 5) { + const { + status, + numberComponentsDeployed, + numberComponentsTotal, + numberTestsTotal, + numberTestsCompleted, + stateDetail, + } = response; + const progress = `${numberComponentsDeployed}/${numberComponentsTotal}`; + const testProgress = `${numberTestsCompleted}/${numberTestsTotal}`; + let message = ''; + if (numberComponentsDeployed < sourceComps.comps.length) { + message = `⌛ Deploy Package: ${pck} Status: ${status} Progress: ${progress}`; + } else if (numberComponentsDeployed === numberComponentsTotal && numberTestsTotal > 0) { + message = `⌛ Test Package: ${pck} Status: ${status} Progress: ${testProgress} ${stateDetail ?? ''}`; + } else if (numberTestsTotal === 0 && sourceComps.apexTestclassNames.length > 0) { + message = `⌛ Waiting for testclass execution`; + } + Logger(message,COLOR_TRACE); + counter = 0; + } else { + counter++; + } + }); + + // Wait for polling to finish and get the DeployResult object + const res = await deploy.pollStatus(); + if (!res.response.success) { + await this.createOutput(res.response.details); + } else { + Logger(`✔ Deployment and tests for source package ${pck} successfully 👌`,COLOR_INFO); + } + } + private async getApexClassesForSource(path: string): Promise { + const sourcePckComps: SourcePackageComps = { comps: [], apexClassNames: [], apexTestclassNames: [] }; + const resolver: MetadataResolver = new MetadataResolver(); + + for (const component of resolver.getComponentsFromPath(path)) { + sourcePckComps.comps.push(component.name); + if (component.type.id === 'apexclass') { + const apexCheckResult: ApexTestclassCheck = await this.checkIsSourceTestClass(component.content); + if (apexCheckResult.isTest) { + sourcePckComps.apexTestclassNames.push(component.name); + } else { + sourcePckComps.apexClassNames.push(component.name); + } + } + } + + return sourcePckComps; + } + //check if apex class is a testclass from code identifier @isTest + private async checkIsSourceTestClass(comp: string): Promise { + let checkResult: ApexTestclassCheck = { isTest: false }; + try { + const data = await fs.promises.readFile(comp, 'utf8'); + if (data.search('@isTest') > -1 || data.search('@IsTest') > -1) { + checkResult.isTest = true; + } + } catch (err) { + Logger(err,COLOR_TRACE); + return checkResult; + } + return checkResult; + } } diff --git a/src/interfaces/package.ts b/src/interfaces/package.ts index 191eacf..6bc11d6 100644 --- a/src/interfaces/package.ts +++ b/src/interfaces/package.ts @@ -46,3 +46,10 @@ export type ApexTestclassCheck = { Id?: string; isTest?: boolean; } + +export type SourcePackageComps = { + comps?: string[]; + apexClassNames?: string[]; + apexTestclassNames?: string[]; +} +