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 @@
-
+
+ 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 | +