diff --git a/packages/sfpowerscripts-cli/CHANGELOG.md b/packages/sfpowerscripts-cli/CHANGELOG.md index 375e5c6f2..6c0a4531b 100644 --- a/packages/sfpowerscripts-cli/CHANGELOG.md +++ b/packages/sfpowerscripts-cli/CHANGELOG.md @@ -3,9 +3,9 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. -# [20.30.0](https://github.com/dxatscale/sfpowerscripts/compare/@dxatscale/sfpowerscripts@20.29.0...@dxatscale/sfpowerscripts@20.30.0) (2023-03-22) +# [20.30.0](https://github.com/flxblio/sfp/compare/@flxblio/sfp@20.29.0...@flxblio/sfp@20.30.0) (2023-03-22) ### Features -* **publish:** Add new flag to delete Git tags by age and limit ([#1275](https://github.com/dxatscale/sfpowerscripts/issues/1275)) ([aae62d6](https://github.com/dxatscale/sfpowerscripts/commit/aae62d6d3e7eb390dddcf2ca46b99b44ca4cc933)) +* **publish:** Add new flag to delete Git tags by age and limit ([#1275](https://github.com/flxblio/sfp/issues/1275)) ([aae62d6](https://github.com/flxblio/sfp/commit/aae62d6d3e7eb390dddcf2ca46b99b44ca4cc933)) diff --git a/packages/sfpowerscripts-cli/README.md b/packages/sfpowerscripts-cli/README.md index fabe4ea56..0abca1322 100644 --- a/packages/sfpowerscripts-cli/README.md +++ b/packages/sfpowerscripts-cli/README.md @@ -1,19 +1,19 @@
- +
-![Version](https://img.shields.io/npm/v/@dxatscale/sfpowerscripts.svg) -[![GitHub stars](https://img.shields.io/github/stars/dxatscale/sfpowerscripts)](https://gitHub.com/dxatscale/sfpowerscripts/stargazers/) -[![GitHub contributors](https://img.shields.io/github/contributors/dxatscale/sfpowerscripts.svg)](https://github.com/forcedotcom/dxatscale/sfpowerscripts/graphs/contributors/) -[![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/dxatscale/sfpowerscripts/blob/master/LICENSE) +![Version](https://img.shields.io/npm/v/@flxblio/sfp.svg) +[![GitHub stars](https://img.shields.io/github/stars/flxblio/sfp)](https://github.com/flxblio/sfp/stargazers/) +[![GitHub contributors](https://img.shields.io/github/contributors/flxblio/sfp.svg)](https://github.com/flxblio/sfp/graphs/contributors/) +[![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/flxblio/sfp/blob/master/LICENSE) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![DeepScan grade](https://deepscan.io/api/teams/10234/projects/12959/branches/208838/badge/grade.svg)](https://deepscan.io/dashboard#view=project&tid=10234&pid=12959&bid=208838) -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fdxatscale%2Fsfpowerscripts.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fdxatscale%2Fsfpowerscripts?ref=badge_shield) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5614/badge)](https://bestpractices.coreinfrastructure.org/projects/5614) +[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fflxblio%2Fsfp.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fflxblio%2Fsfp?ref=badge_shield) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5614/badge)](https://bestpractices.coreinfrastructure.org/projects/5614) -[![Join slack](https://i.imgur.com/FZZmA3g.png)](https://launchpass.com/dxatscale) +[![Join slack](https://i.imgur.com/FZZmA3g.png)](https://launchpass.com/flxblio) -A build system for package based development in Salesforce, delivered as a node cli that can be implemented in any CI/CD system of choice.Read more about the cli and details here - https://docs.dxatscale.io +A build system for package based development in Salesforce, delivered as a node cli that can be implemented in any CI/CD system of choice.Read more about the cli and details here - https://docs.flxblio.io #### Features @@ -26,10 +26,10 @@ A build system for package based development in Salesforce, delivered as a node - Integrate with any CI/CD system of choice - All commands are enabled with statsD, for collecting metrics about your pipeline. -There are lot more features to explore. Read more at https://docs.dxatscale.io +There are lot more features to explore. Read more at https://docs.flxblio.io -The project is delivered as a CLI that can be deployed in any CI/CD system, The module is available in [NPM](https://www.npmjs.com/package/@dxatscale/sfpowerscripts) or can be -used by using the [docker image](https://github.com/dxatscale/sfpowerscripts/pkgs/container/sfpowerscripts) +The project is delivered as a CLI that can be deployed in any CI/CD system, The module is available in [NPM](https://www.npmjs.com/package/@flxblio/sfp) or can be +used by using the [docker image](https://github.com/flxblio/sfp/pkgs/container/sfp) @@ -45,29 +45,29 @@ used by using the [docker image](https://github.com/dxatscale/sfpowerscripts/pkg #### CI/CD Reference Implementation -Getting started guides for popular CI/CD platforms along with reference pipelines are available [here](https://docs.dxatscale.io/reference-implementation/github) +Getting started guides for popular CI/CD platforms along with reference pipelines are available [here](https://docs.flxblio.io/implementing-your-ci-cd/github) -#### Installing sfpowerscripts locally +#### Installing sfp locally -sfpowerscripts can be installed on your local device using npm +sfp can be installed on your local device using npm ``` -npm i -g @dxatscale/sfpowerscripts +npm i -g @flxblio/sfp ``` #### Docker -Docker images for sfpowerscripts are available at [GitHub Container Registry](https://github.com/dxatscale/sfpowerscripts/pkgs/container/sfpowerscripts). +Docker images for sfp are available at [GitHub Container Registry](https://github.com/flxblio/sfp/pkgs/container/sfp). -We recommend using the sfpowerscripts docker image to avoid breakages in your CI/CD pipelines due to updates in sfpowerscripts or any of its dependencies such as the SFDX CLI. +We recommend using the sfp docker image to avoid breakages in your CI/CD pipelines due to updates in sfp or any of its dependencies such as the SFDX CLI. #### Build Instructions -To build sfpowerscripts execute the following on the terminal: +To build sfp execute the following on the terminal: ``` npm i -g lerna #Install Lerna Globally -cd+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196 +197 +198 +199 +200 +201 +202 +203 +204 +205 +206 +207 +208 +209 +210 +211 +212 +213 +214 +215 +216 +217 +218 +219 +220 +221 +222 +223 +224 +225 +226 +227 +228 +229 +230 +231 +232 +233 +234 +235 +236 +237 +238 +239 +240 +241 +242 +243 +244 +245 +246 +247 | 1x +1x +1x +1x +1x + + +1x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import SFPLogger, { Logger, LoggerLevel } from '@flxblio/sfp-logger'; +import simplegit, { SimpleGit } from 'simple-git'; +import fs = require('fs-extra'); +import GitIdentity from './GitIdentity'; +const tmp = require('tmp'); + +//Git Abstraction +export default class Git { + private _git: SimpleGit; + private repositoryLocation: string; + private tempRepoLocation: any; + private _isATemporaryRepo: boolean = false; + + private constructor(private projectDir?: string, private logger?: Logger) { + if (this.projectDir) { + this._git = simplegit(this.projectDir); + this.repositoryLocation = this.projectDir; + } else { + this._git = simplegit(); + this.repositoryLocation = process.cwd(); + } + } + + async fetch() { + return this._git.fetch('origin'); + } + + async getHeadCommit(): Promise<string> { + return this._git.revparse(['HEAD']); + } + + async show(options: string[]): Promise<string> { + return this._git.show(options); + } + + async tag(options: string[]): Promise<string[]> { + let tagResult = await this._git.tag(options); + + let temp: string[] = tagResult.split('\n'); + temp.pop(); + + return temp; + } + + async diff(options: string[]): Promise<string[]> { + let diffResult = await this._git.diff(options); + + let temp: string[] = diffResult.split('\n'); + temp.pop(); + + return temp; + } + + async log(options: string[]): Promise<string[]> { + let gitLogResult = await this._git.log(options); + + return gitLogResult['all'][0]['hash'].split('\n'); + } + + public async getRemoteOriginUrl(overrideOriginURL?: string): Promise<string> { + let remoteOriginURL; + if (!overrideOriginURL) { + remoteOriginURL = (await this._git.getConfig('remote.origin.url')).value; + if (!remoteOriginURL) { + remoteOriginURL = (await this._git.getConfig('remote.origin.url')).value; + } + SFPLogger.log(`Fetched Remote URL ${remoteOriginURL}`, LoggerLevel.DEBUG); + } else remoteOriginURL = overrideOriginURL; + + Iif (!remoteOriginURL) throw new Error('Remote origin must be set in repository'); + + return remoteOriginURL; + } + + public async commitFile(pathToFiles: string[], message = `[skip ci] Autogenerated commit by sfp`) { + try { + await new GitIdentity(this._git).setUsernameAndEmail(); + await this._git.add(pathToFiles); + await this._git.commit(message); + SFPLogger.log(`Committed File ${pathToFiles}`); + } catch (error) { + SFPLogger.log( + `Unable to commit file, probably due to no change or something else,Please try manually`, + LoggerLevel.ERROR + ); + throw error; + } + } + + async pushTags(tags?: string[]) { + if (!tags) await this._git.pushTags(); + else { + for (let tag of tags) { + await this._git.push('origin', tag); + } + } + } + + async deleteTags(tags?: string[]) { + Iif (tags) await this._git.push('origin', '--delete', tags); + } + + async addAnnotatedTag(tagName: string, annotation: string, commitId?: string) { + try { + await new GitIdentity(this._git).setUsernameAndEmail(); + if (!commitId) { + await this._git.addAnnotatedTag(tagName, annotation); + } else { + const commands = ['tag', tagName, commitId, '-m', annotation]; + await this._git.raw(commands); + } + } catch (error) { + SFPLogger.log( + `Unable to commit file, probably due to no change or something else,Please try manually`, + LoggerLevel.ERROR + ); + throw error; + } + } + + public async isBranchExists(branch: string): Promise<boolean> { + const listOfBranches = await this._git.branch(['-la']); + + return listOfBranches.all.find((elem) => elem.endsWith(branch)) ? true : false; + } + + static async initiateRepoAtTempLocation(logger: Logger, commitRef?: string, branch?: string): Promise<Git> { + let locationOfCopiedDirectory = tmp.dirSync({ unsafeCleanup: true }); + + SFPLogger.log(`Copying the repository to ${locationOfCopiedDirectory.name}`, LoggerLevel.INFO, logger); + let repoDir = locationOfCopiedDirectory.name; + + // Copy source directory to temp dir + fs.copySync(process.cwd(), repoDir); + + //Initiate git on new repo on using the abstracted object + let git = new Git(repoDir, logger); + git._isATemporaryRepo = true; + git.tempRepoLocation = locationOfCopiedDirectory; + + await git.addSafeConfig(repoDir); + await git.getRemoteOriginUrl(); + await git.fetch(); + if (branch) { + await git.createBranch(branch); + } + if (commitRef) { + await git.checkout(commitRef, true); + } + + SFPLogger.log( + `Successfully created temporary repository at ${repoDir} with commit ${commitRef ? commitRef : 'HEAD'}`, + LoggerLevel.INFO, + logger + ); + return git; + } + + static async initiateRepo(logger?: Logger, projectDir?: string) { + let git = new Git(projectDir, logger); + if (projectDir) await git.addSafeConfig(projectDir); + else { + await git.addSafeConfig(process.cwd()); + } + await git.getRemoteOriginUrl(); + return git; + } + + public getRepositoryPath() { + return this.repositoryLocation; + } + + async deleteTempoRepoIfAny() { + Iif (this.tempRepoLocation) this.tempRepoLocation.removeCallback(); + } + + async addSafeConfig(repoDir: string) { + try + { + //add workaround for safe directory (https://github.com/actions/runner/issues/2033) + await this._git.addConfig('safe.directory', repoDir, false, 'global'); + }catch(error) + { + //ignore error + SFPLogger.log(`Unable to set safe.directory`,LoggerLevel.TRACE) + } + } + + async pushToRemote(branch: string, isForce: boolean) { + Iif (!branch) branch = (await this._git.branch()).current; + SFPLogger.log(`Pushing ${branch}`, LoggerLevel.INFO, this.logger); + if (process.env.sfp_OVERRIDE_ORIGIN_URL) { + await this._git.removeRemote('origin'); + await this._git.addRemote('origin', process.env.sfp_OVERRIDE_ORIGIN_URL); + } + + if (isForce) { + await this._git.push('origin', branch, [`--force`]); + } else { + await this._git.push('origin', branch); + } + } + + isATemporaryRepo(): boolean { + return this._isATemporaryRepo; + } + + async getCurrentCommitId() { + return this._git.revparse(['HEAD']); + } + + async checkout(commitRef: string, isForce?: boolean) { + if (isForce) { + return this._git.checkout(commitRef, [`--force`]); + } else return this._git.checkout(commitRef, {}); + } + + async checkoutPath(commitRef: string, path: string, isForce?: boolean) { + if (isForce) { + return this._git.checkout(commitRef, [path, `--force`]); + } else return this._git.checkout(commitRef, [path]); + } + + async stageChangedFiles(path: string): Promise<boolean> { + try { + await this._git.add(path); + return true; + } catch (error) { + SFPLogger.log(`Nothing to add, ignoring`, LoggerLevel.INFO, this.logger); + return false; + } + } + async createBranch(branch: string) { + if (await this.isBranchExists(branch)) { + await this._git.checkout(branch, ['-f']); + try { + // For ease-of-use when running locally and local branch exists + await this._git.merge([`refs/remotes/origin/${branch}`]); + } catch (error) { + SFPLogger.log(`Unable to find remote`, LoggerLevel.TRACE, this.logger); + } + } else { + await this._git.checkout(['-b', branch]); + } + } +} + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 | + +1x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + | import { SimpleGit } from 'simple-git/promise'; + +export default class GitIdentity { + constructor(private git: SimpleGit) {} + + async setUsernameAndEmail(): Promise<void> { + await this.setUsername(); + await this.setEmail(); + } + + private async setUsername(): Promise<void> { + let username: string; + + if (process.env.sfp_GIT_USERNAME) { + username = process.env.sfp_GIT_USERNAME; + } else { + username = 'sfp'; + } + + await this.git.addConfig('user.name', username); + } + + private async setEmail(): Promise<void> { + let email: string; + + if (process.env.sfp_GIT_EMAIL) { + email = process.env.sfp_GIT_EMAIL; + } else { + email = 'sfp@flxblio.io'; + } + + await this.git.addConfig('user.email', email); + } +} + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
---|---|---|---|---|---|---|---|---|---|
Git.ts | +
+
+ |
+ 6% | +6/100 | +0% | +0/15 | +0% | +0/26 | +6.38% | +6/94 | +
GitIdentity.ts | +
+
+ |
+ 10% | +1/10 | +100% | +0/0 | +0% | +0/4 | +10% | +1/10 | +
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 | +1x + +1x + + + + +4x +4x +4x +4x + +4x + + +2x + + + + + + +2x + + +1x +1x +1x + +1x + + + + + + + + + + + + + + + + + +2x + + + + + + + + +3x + + +2x +2x + + + + + + + + +1x + + + +1x +1x + + +1x + + + + + +1x +1x + + +2x + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +6x +6x + + + + + + + | import { ReleaseChangelog, Release, ReleaseId } from './ReleaseChangelog'; +import lodash = require('lodash'); + +export default class OrgsUpdater { + private latestReleaseId: ReleaseId; + private idOfReleaseWithMatchingHashId: ReleaseId; + + constructor( + private releaseChangelog: ReleaseChangelog, + private latestRelease: Release, + private org: string, + private releaseWithMatchingHashId: Release + ) { + this.latestReleaseId = this.convertReleaseToId(this.latestRelease); + + if (this.releaseWithMatchingHashId) { + this.idOfReleaseWithMatchingHashId = this.convertReleaseToId(this.releaseWithMatchingHashId); + } + } + + update(): void { + if (!this.idOfReleaseWithMatchingHashId) { + if (this.releaseChangelog.orgs) { + let org = this.releaseChangelog.orgs.find((org) => org.name === this.org); + + if (org) { + org.releases.push(this.latestReleaseId); + org.latestRelease = org.releases[org.releases.length - 1]; + org.retryCount = 0; + } else { + this.releaseChangelog.orgs.push({ + name: this.org, + releases: [this.latestReleaseId], + latestRelease: this.latestReleaseId, + retryCount: 0, + }); + } + } else { + // for backwards-compatibility with pre-existing changelogs + this.releaseChangelog.orgs = [ + { + name: this.org, + releases: [this.latestReleaseId], + latestRelease: this.latestReleaseId, + retryCount: 0, + }, + ]; + } + console.log( + `Updating ${this.org} org with`, + this.latestRelease.names[this.latestRelease.names.length - 1] + + '-' + + this.latestRelease.buildNumber + + `(0)` + ); + } else { + // Update orgs + let org = this.releaseChangelog.orgs.find((org) => org.name === this.org); + + if (org) { + let indexOfReleaseToOrg = org.releases.findIndex( + (orgRelease) => orgRelease.hashId === this.idOfReleaseWithMatchingHashId.hashId + ); + if (org.latestRelease.hashId !== this.idOfReleaseWithMatchingHashId.hashId) { + if (indexOfReleaseToOrg >= 0) { + // Update release names in releases to org + org.releases[indexOfReleaseToOrg] = this.idOfReleaseWithMatchingHashId; + org.releases[indexOfReleaseToOrg].date = new Date().toUTCString(); + } else { + // Add releaseId in releases to org + org.releases.push(this.idOfReleaseWithMatchingHashId); + } + + // Update latest release + org.latestRelease = this.idOfReleaseWithMatchingHashId; + org.retryCount = 0; + } else { + if (lodash.isEqual(org.releases[indexOfReleaseToOrg], this.idOfReleaseWithMatchingHashId)) { + org.retryCount++; + } else { + org.retryCount = 0; + } + + // Update releases names in releases to org & latestRelease + org.releases[indexOfReleaseToOrg] = this.idOfReleaseWithMatchingHashId; + org.latestRelease = this.idOfReleaseWithMatchingHashId; + } + + console.log( + `Updating ${this.org} org with`, + org.latestRelease.names[org.latestRelease.names.length - 1] + + '-' + + org.latestRelease.buildNumber + + `(${org.retryCount})` + ); + } else { + // new org + this.releaseChangelog.orgs.push({ + name: this.org, + releases: [this.idOfReleaseWithMatchingHashId], + latestRelease: this.idOfReleaseWithMatchingHashId, + retryCount: 0, + }); + console.log( + `Updating ${this.org} org with`, + `${this.idOfReleaseWithMatchingHashId.names[this.idOfReleaseWithMatchingHashId.names.length - 1]}-${ + this.idOfReleaseWithMatchingHashId.buildNumber + }(0)` + ); + } + } + } + + /** + * Convert Release to Release Id + * @param release + * @returns + */ + private convertReleaseToId(release: Release): ReleaseId { + let releaseNames = [...release.names]; // Shallow copy + return { + names: releaseNames, + buildNumber: release.buildNumber, + hashId: release.hashId, + }; + } +} + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 | 1x + + + +1x +1x + + + + + + + +2x +2x + + + +24x +24x + + + +3x +3x + +1x + + + + + + + + + + +3x + + + + | import SFPLogger, { Logger, LoggerLevel } from '@flxblio/sfp-logger'; +import { Release } from './ReleaseChangelog'; + + +export default class WorkItemUpdater { + constructor(private latestRelease: Release, private workItemFilters: string[],private logger?:Logger) {} + + /** + * Generate work items in latest release + */ + update(): void { + for (const workItemFilter of this.workItemFilters) { + + let workItemFilterRegex: RegExp = RegExp(workItemFilter, 'gi'); + SFPLogger.log(`Matching...${workItemFilterRegex}`,LoggerLevel.INFO,this.logger); + + for (let artifact of this.latestRelease['artifacts']) { + for (let commit of artifact['commits']) { + let commitMessage: String = commit['message'] + '\n' + commit['body']; + let workItems: RegExpMatchArray = commitMessage.match(workItemFilterRegex); + if (workItems) { + for (let item of workItems) { + if (this.latestRelease['workItems'][item] == null) { + this.latestRelease['workItems'][item] = new Set<string>(); + this.latestRelease['workItems'][item].add(commit['commitId'].slice(0, 8)); + } else { + this.latestRelease['workItems'][item].add(commit['commitId'].slice(0, 8)); + } + } + } + } + } + } + + // Convert each work item Set to Array + // Enables JSON stringification of work item + for (let key in this.latestRelease['workItems']) { + this.latestRelease.workItems[key] = Array.from(this.latestRelease.workItems[key]); + } + } +} + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
---|---|---|---|---|---|---|---|---|---|
OrgsUpdater.ts | +
+
+ |
+ 82.35% | +28/34 | +100% | +0/0 | +100% | +6/6 | +81.25% | +26/32 | +
WorkItemUpdater.ts | +
+
+ |
+ 100% | +13/13 | +100% | +0/0 | +100% | +2/2 | +100% | +11/11 | +
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 | 1x + + + +8x + + + +3x + + + +14x +1x + + + +12x +11x +9x + +8x +8x + + + + + + + +4x +4x +4x + +4x +19x +19x +18x +18x +18x + +15x + + + + +3x + + + | export default class UndirectedGraph { + private _adjacencyList: { [p: string]: string[] }; + + constructor() { + this._adjacencyList = {}; + } + + get adjacencyList() { + return this._adjacencyList; + } + + addVertex(name: string) { + if (!this._adjacencyList[name]) this._adjacencyList[name] = []; + else throw new Error(`Vertex with name '${name}' already exists`); + } + + addEdge(vertexA: string, vertexB: string): void { + if (vertexA === vertexB) throw new Error('Cannot add an edge to a single vertex'); + if (!this._adjacencyList[vertexA]) throw new Error(`Vertex with name ${vertexA} does not exist`); + if (!this._adjacencyList[vertexB]) throw new Error(`Vertex with name ${vertexB} does not exist`); + + if (!this._adjacencyList[vertexA].includes(vertexB)) this._adjacencyList[vertexA].push(vertexB); + if (!this._adjacencyList[vertexB].includes(vertexA)) this._adjacencyList[vertexB].push(vertexA); + } + + /** + * Returns vertices in graph, using depth-first search from the starting vertex + * @param start + */ + dfs(start: string): string[] { + const vertices: string[] = []; + const visited: { [p: string]: boolean } = {}; + const adjacencyList = this._adjacencyList; + + (function dfsHandler(vertex) { + Iif (!vertex) return null; + if (!adjacencyList[vertex]) throw new Error(`Vertex '${vertex}' does not exist`); + visited[vertex] = true; + vertices.push(vertex); + adjacencyList[vertex].forEach((neighbor) => { + if (!visited[neighbor]) { + return dfsHandler(neighbor); + } + }); + })(start); + + return vertices; + } +} + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
---|---|---|---|---|---|---|---|---|---|
UndirectedGraph.ts | +
+
+ |
+ 96.55% | +28/29 | +88.88% | +8/9 | +100% | +7/7 | +100% | +21/21 | +
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 | +1x +1x +1x +1x +1x + +1x +1x + +1x + + + + +5x +5x + + +1x +1x + + + + + + + + + + + + + + + + +5x + + + + + +5x + + + + + + + + + + +5x + + + + +5x +5x + + + +4x + + +4x +6x + + + + + + +4x + + + + | 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); + } + } +} + |
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
---|---|---|---|---|---|---|---|---|---|
ReleaseDefinition.ts | +
+
+ |
+ 70% | +21/30 | +100% | +3/3 | +66.66% | +4/6 | +70% | +21/30 | +
+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +
+ +File | ++ | Statements | ++ | Branches | ++ | Functions | ++ | Lines | ++ |
---|---|---|---|---|---|---|---|---|---|
src | +
+
+ |
+ 100% | +26/26 | +100% | +3/3 | +100% | +7/7 | +100% | +26/26 | +
src/core/apex | +
+
+ |
+ 33.33% | +8/24 | +100% | +0/0 | +33.33% | +2/6 | +36.36% | +8/22 | +
src/core/apex/coverage | +
+
+ |
+ 61.29% | +19/31 | +60% | +3/5 | +66.66% | +6/9 | +62.06% | +18/29 | +
src/core/apextest | +
+
+ |
+ 43.13% | +22/51 | +20% | +1/5 | +40% | +2/5 | +42.55% | +20/47 | +
src/core/artifacts | +
+
+ |
+ 38.46% | +30/78 | +53.84% | +7/13 | +38.46% | +5/13 | +37.33% | +28/75 | +
src/core/display | +
+
+ |
+ 100% | +2/2 | +100% | +0/0 | +100% | +0/0 | +100% | +2/2 | +
src/core/git | +
+
+ |
+ 14.87% | +36/242 | +4.87% | +2/41 | +10.52% | +6/57 | +14.09% | +32/227 | +
src/core/metadata | +
+
+ |
+ 21.66% | +52/240 | +0% | +0/45 | +21.05% | +4/19 | +21.75% | +52/239 | +
src/core/org | +
+
+ |
+ 34.61% | +27/78 | +41.17% | +7/17 | +31.25% | +5/16 | +37.5% | +27/72 | +
src/core/org/packageQuery | +
+
+ |
+ 50% | +2/4 | +100% | +0/0 | +0% | +0/1 | +50% | +2/4 | +
src/core/package | +
+
+ |
+ 26.92% | +35/130 | +9.52% | +2/21 | +28.57% | +4/14 | +28% | +35/125 | +
src/core/package/analyser | +
+
+ |
+ 67.46% | +56/83 | +60% | +6/10 | +50% | +6/12 | +66.66% | +52/78 | +
src/core/package/components | +
+
+ |
+ 74.02% | +57/77 | +50% | +2/4 | +82.35% | +14/17 | +74.66% | +56/75 | +
src/core/package/coverage | +
+
+ |
+ 93.67% | +74/79 | +70% | +7/10 | +100% | +15/15 | +93.58% | +73/78 | +
src/core/package/dependencies | +
+
+ |
+ 85.25% | +133/156 | +74.35% | +29/39 | +91.66% | +22/24 | +84.56% | +126/149 | +
src/core/package/deploymentFilters | +
+
+ |
+ 84.09% | +37/44 | +42.85% | +3/7 | +66.66% | +2/3 | +90.24% | +37/41 | +
src/core/package/diff | +
+
+ |
+ 36.44% | +82/225 | +36.66% | +11/30 | +36.84% | +7/19 | +36.48% | +81/222 | +
src/core/package/packageCreators | +
+
+ |
+ 14.43% | +28/194 | +0% | +0/28 | +0% | +0/40 | +15.73% | +28/178 | +
src/core/package/propertyFetchers | +
+
+ |
+ 78.94% | +15/19 | +66.66% | +4/6 | +100% | +3/3 | +78.94% | +15/19 | +
src/core/package/validators | +
+
+ |
+ 12.5% | +5/40 | +0% | +0/12 | +0% | +0/3 | +13.51% | +5/37 | +
src/core/package/version | +
+
+ |
+ 75.92% | +41/54 | +75% | +9/12 | +57.14% | +4/7 | +75.55% | +34/45 | +
src/core/permsets | +
+
+ |
+ 85.71% | +48/56 | +100% | +3/3 | +100% | +9/9 | +84.9% | +45/53 | +
src/core/project | +
+
+ |
+ 74.1% | +83/112 | +73.07% | +19/26 | +79.31% | +23/29 | +75% | +78/104 | +
src/core/queryHelper | +
+
+ |
+ 100% | +23/23 | +100% | +4/4 | +100% | +3/3 | +100% | +22/22 | +
src/core/stats | +
+
+ |
+ 18.18% | +8/44 | +0% | +0/13 | +0% | +0/9 | +19.51% | +8/41 | +
src/core/stats/nativeMetricSenderImpl | +
+
+ |
+ 21.05% | +12/57 | +0% | +0/2 | +0% | +0/18 | +21.05% | +12/57 | +
src/core/utils | +
+
+ |
+ 39.86% | +61/153 | +20.68% | +6/29 | +36.36% | +12/33 | +40.41% | +59/146 | +
src/impl/changelog | +
+
+ |
+ 87.5% | +56/64 | +100% | +0/0 | +100% | +11/11 | +86.66% | +52/60 | +
src/impl/dependency | +
+
+ |
+ 100% | +27/27 | +100% | +0/0 | +100% | +5/5 | +100% | +26/26 | +
src/impl/parallelBuilder | +
+
+ |
+ 93.75% | +45/48 | +81.25% | +13/16 | +85.71% | +12/14 | +94.73% | +36/38 | +
src/impl/release | +
+
+ |
+ 70% | +21/30 | +100% | +3/3 | +66.66% | +4/6 | +70% | +21/30 | +
src/utils | +
+
+ |
+ 7.69% | +1/13 | +0% | +0/7 | +0% | +0/1 | +8.33% | +1/12 | +