feat(dev-infra): handle excluding files via globs in pullapprove (#36162)
Updates the pullapprove verification script to handle cases of excluding globs from groups. PR Close #36162
This commit is contained in:
		
							parent
							
								
									bfa7b1a494
								
							
						
					
					
						commit
						83e4a76afa
					
				| @ -3,11 +3,15 @@ load("@npm_bazel_typescript//:index.bzl", "ts_library") | ||||
| ts_library( | ||||
|     name = "pullapprove", | ||||
|     srcs = [ | ||||
|         "group.ts", | ||||
|         "logging.ts", | ||||
|         "parse-yaml.ts", | ||||
|         "verify.ts", | ||||
|     ], | ||||
|     module_name = "@angular/dev-infra-private/pullapprove", | ||||
|     visibility = ["//dev-infra:__subpackages__"], | ||||
|     deps = [ | ||||
|         "//dev-infra/utils:config", | ||||
|         "@npm//@types/minimatch", | ||||
|         "@npm//@types/node", | ||||
|         "@npm//@types/shelljs", | ||||
|  | ||||
							
								
								
									
										158
									
								
								dev-infra/pullapprove/group.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								dev-infra/pullapprove/group.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,158 @@ | ||||
| /** | ||||
|  * @license | ||||
|  * Copyright Google Inc. All Rights Reserved. | ||||
|  * | ||||
|  * Use of this source code is governed by an MIT-style license that can be | ||||
|  * found in the LICENSE file at https://angular.io/license
 | ||||
|  */ | ||||
| import {IMinimatch, Minimatch, match} from 'minimatch'; | ||||
| 
 | ||||
| import {PullApproveGroupConfig} from './parse-yaml'; | ||||
| 
 | ||||
| /** A condition for a group. */ | ||||
| interface GroupCondition { | ||||
|   glob: string; | ||||
|   matcher: IMinimatch; | ||||
|   matchedFiles: Set<string>; | ||||
| } | ||||
| 
 | ||||
| /** Result of testing files against the group. */ | ||||
| export interface PullApproveGroupResult { | ||||
|   groupName: string; | ||||
|   matchedIncludes: GroupCondition[]; | ||||
|   matchedExcludes: GroupCondition[]; | ||||
|   matchedCount: number; | ||||
|   unmatchedIncludes: GroupCondition[]; | ||||
|   unmatchedExcludes: GroupCondition[]; | ||||
|   unmatchedCount: number; | ||||
| } | ||||
| 
 | ||||
| // Regex Matcher for contains_any_globs conditions
 | ||||
| const CONTAINS_ANY_GLOBS_REGEX = /^'([^']+)',?$/; | ||||
| 
 | ||||
| const CONDITION_TYPES = { | ||||
|   INCLUDE_GLOBS: /^contains_any_globs/, | ||||
|   EXCLUDE_GLOBS: /^not contains_any_globs/, | ||||
|   ATTR_LENGTH: /^len\(.*\)/, | ||||
| }; | ||||
| 
 | ||||
| /** A PullApprove group to be able to test files against. */ | ||||
| export class PullApproveGroup { | ||||
|   // Lines which were not able to be parsed as expected.
 | ||||
|   private misconfiguredLines: string[] = []; | ||||
|   // Conditions for the group for including files.
 | ||||
|   private includeConditions: GroupCondition[] = []; | ||||
|   // Conditions for the group for excluding files.
 | ||||
|   private excludeConditions: GroupCondition[] = []; | ||||
|   // Whether the group has file matchers.
 | ||||
|   public hasMatchers = false; | ||||
| 
 | ||||
|   constructor(public groupName: string, group: PullApproveGroupConfig) { | ||||
|     for (let condition of group.conditions) { | ||||
|       condition = condition.trim(); | ||||
| 
 | ||||
|       if (condition.match(CONDITION_TYPES.INCLUDE_GLOBS)) { | ||||
|         const [conditions, misconfiguredLines] = getLinesForContainsAnyGlobs(condition); | ||||
|         conditions.forEach(globString => this.includeConditions.push({ | ||||
|           glob: globString, | ||||
|           matcher: new Minimatch(globString, {dot: true}), | ||||
|           matchedFiles: new Set<string>(), | ||||
|         })); | ||||
|         this.misconfiguredLines.push(...misconfiguredLines); | ||||
|         this.hasMatchers = true; | ||||
|       } else if (condition.match(CONDITION_TYPES.EXCLUDE_GLOBS)) { | ||||
|         const [conditions, misconfiguredLines] = getLinesForContainsAnyGlobs(condition); | ||||
|         conditions.forEach(globString => this.excludeConditions.push({ | ||||
|           glob: globString, | ||||
|           matcher: new Minimatch(globString, {dot: true}), | ||||
|           matchedFiles: new Set<string>(), | ||||
|         })); | ||||
|         this.misconfiguredLines.push(...misconfiguredLines); | ||||
|         this.hasMatchers = true; | ||||
|       } else if (condition.match(CONDITION_TYPES.ATTR_LENGTH)) { | ||||
|         // Currently a noop as we do not take any action on this condition type.
 | ||||
|       } else { | ||||
|         const errMessage = `Unrecognized condition found, unable to parse the following condition: \n\n` + | ||||
|         `From the [${groupName}] group:\n` + | ||||
|         ` - ${condition}` + | ||||
|         `\n\n` + | ||||
|         `Known condition regexs:\n` +  | ||||
|         `${Object.entries(CONDITION_TYPES).map(([k, v]) => `  ${k}  -  ${v}`).join('\n')}` + | ||||
|         `\n\n`; | ||||
|         console.error(errMessage); | ||||
|         process.exit(1); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** Retrieve all of the lines which were not able to be parsed. */ | ||||
|   getBadLines(): string[] { return this.misconfiguredLines; } | ||||
| 
 | ||||
|   /** Retrieve the results for the Group, all matched and unmatched conditions. */ | ||||
|   getResults(): PullApproveGroupResult { | ||||
|     const matchedIncludes = this.includeConditions.filter(c => !!c.matchedFiles.size); | ||||
|     const matchedExcludes = this.excludeConditions.filter(c => !!c.matchedFiles.size); | ||||
|     const unmatchedIncludes = this.includeConditions.filter(c => !c.matchedFiles.size); | ||||
|     const unmatchedExcludes = this.excludeConditions.filter(c => !c.matchedFiles.size); | ||||
|     const unmatchedCount = unmatchedIncludes.length + unmatchedExcludes.length; | ||||
|     const matchedCount = matchedIncludes.length + matchedExcludes.length; | ||||
|     return { | ||||
|       matchedIncludes, | ||||
|       matchedExcludes, | ||||
|       matchedCount, | ||||
|       unmatchedIncludes, | ||||
|       unmatchedExcludes, | ||||
|       unmatchedCount, | ||||
|       groupName: this.groupName, | ||||
|     }; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Tests a provided file path to determine if it would be considered matched by | ||||
|    * the pull approve group's conditions. | ||||
|    */ | ||||
|   testFile(file: string) { | ||||
|     let matched = false; | ||||
|     this.includeConditions.forEach((includeCondition: GroupCondition) => { | ||||
|       if (includeCondition.matcher.match(file)) { | ||||
|         let matchedExclude = false; | ||||
|         this.excludeConditions.forEach((excludeCondition: GroupCondition) => { | ||||
|           if (excludeCondition.matcher.match(file)) { | ||||
|             // Add file as a discovered exclude as it is negating a matched
 | ||||
|             // include condition.
 | ||||
|             excludeCondition.matchedFiles.add(file); | ||||
|             matchedExclude = true; | ||||
|           } | ||||
|         }); | ||||
|         // An include condition is only considered matched if no exclude
 | ||||
|         // conditions are found to matched the file.
 | ||||
|         if (!matchedExclude) { | ||||
|           includeCondition.matchedFiles.add(file); | ||||
|           matched = true; | ||||
|         } | ||||
|       } | ||||
|     }); | ||||
|     return matched; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Extract all of the individual globs from a group condition, | ||||
|  * providing both the valid and invalid lines. | ||||
|  */ | ||||
| function getLinesForContainsAnyGlobs(lines: string) { | ||||
|   const invalidLines: string[] = []; | ||||
|   const validLines = lines.split('\n') | ||||
|                          .slice(1, -1) | ||||
|                          .map((glob: string) => { | ||||
|                            const trimmedGlob = glob.trim(); | ||||
|                            const match = trimmedGlob.match(CONTAINS_ANY_GLOBS_REGEX); | ||||
|                            if (!match) { | ||||
|                              invalidLines.push(trimmedGlob); | ||||
|                              return ''; | ||||
|                            } | ||||
|                            return match[1]; | ||||
|                          }) | ||||
|                          .filter(globString => !!globString); | ||||
|   return [validLines, invalidLines]; | ||||
| } | ||||
							
								
								
									
										42
									
								
								dev-infra/pullapprove/logging.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								dev-infra/pullapprove/logging.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| /** | ||||
|  * @license | ||||
|  * Copyright Google Inc. All Rights Reserved. | ||||
|  * | ||||
|  * Use of this source code is governed by an MIT-style license that can be | ||||
|  * found in the LICENSE file at https://angular.io/license
 | ||||
|  */ | ||||
| import {PullApproveGroupResult} from './group'; | ||||
| 
 | ||||
| /** Create logs for each pullapprove group result. */ | ||||
| export function logGroup(group: PullApproveGroupResult, matched = true) { | ||||
|   const includeConditions = matched ? group.matchedIncludes : group.unmatchedIncludes; | ||||
|   const excludeConditions = matched ? group.matchedExcludes : group.unmatchedExcludes; | ||||
|   console.groupCollapsed(`[${group.groupName}]`); | ||||
|   if (includeConditions.length) { | ||||
|     console.group('includes'); | ||||
|     includeConditions.forEach( | ||||
|         matcher => console.info(`${matcher.glob} - ${matcher.matchedFiles.size}`)); | ||||
|     console.groupEnd(); | ||||
|   } | ||||
|   if (excludeConditions.length) { | ||||
|     console.group('excludes'); | ||||
|     excludeConditions.forEach( | ||||
|         matcher => console.info(`${matcher.glob} - ${matcher.matchedFiles.size}`)); | ||||
|     console.groupEnd(); | ||||
|   } | ||||
|   console.groupEnd(); | ||||
| } | ||||
| 
 | ||||
| /** Logs a header within a text drawn box. */ | ||||
| export function logHeader(...params: string[]) { | ||||
|   const totalWidth = 80; | ||||
|   const fillWidth = totalWidth - 2; | ||||
|   const headerText = params.join(' ').substr(0, fillWidth); | ||||
|   const leftSpace = Math.ceil((fillWidth - headerText.length) / 2); | ||||
|   const rightSpace = fillWidth - leftSpace - headerText.length; | ||||
|   const fill = (count: number, content: string) => content.repeat(count); | ||||
| 
 | ||||
|   console.info(`┌${fill(fillWidth, '─')}┐`); | ||||
|   console.info(`│${fill(leftSpace, ' ')}${headerText}${fill(rightSpace, ' ')}│`); | ||||
|   console.info(`└${fill(fillWidth, '─')}┘`); | ||||
| } | ||||
							
								
								
									
										33
									
								
								dev-infra/pullapprove/parse-yaml.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								dev-infra/pullapprove/parse-yaml.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| /** | ||||
|  * @license | ||||
|  * Copyright Google Inc. All Rights Reserved. | ||||
|  * | ||||
|  * Use of this source code is governed by an MIT-style license that can be | ||||
|  * found in the LICENSE file at https://angular.io/license
 | ||||
|  */ | ||||
| import {parse as parseYaml} from 'yaml'; | ||||
| 
 | ||||
| export interface PullApproveGroupConfig { | ||||
|   conditions: string; | ||||
|   reviewers: { | ||||
|     users: string[], | ||||
|     teams: string[], | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export interface PullApproveConfig { | ||||
|   version: number; | ||||
|   github_api_version?: string; | ||||
|   pullapprove_conditions?: { | ||||
|     condition: string, | ||||
|     unmet_status: string, | ||||
|     explanation: string, | ||||
|   }[]; | ||||
|   groups: { | ||||
|     [key: string]: PullApproveGroupConfig, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function parsePullApproveYaml(rawYaml: string): PullApproveConfig { | ||||
|   return parseYaml(rawYaml) as PullApproveConfig; | ||||
| } | ||||
| @ -6,210 +6,114 @@ | ||||
|  * found in the LICENSE file at https://angular.io/license
 | ||||
|  */ | ||||
| import {readFileSync} from 'fs'; | ||||
| import {IMinimatch, Minimatch} from 'minimatch'; | ||||
| import * as path from 'path'; | ||||
| import {cd, exec, set} from 'shelljs'; | ||||
| import {parse as parseYaml} from 'yaml'; | ||||
| 
 | ||||
| interface GlobMatcher { | ||||
|   group: string; | ||||
|   glob: string; | ||||
|   matcher: IMinimatch; | ||||
|   matchCount: number; | ||||
| } | ||||
| import {PullApproveGroup} from './group'; | ||||
| import {logGroup, logHeader} from './logging'; | ||||
| import {parsePullApproveYaml} from './parse-yaml'; | ||||
| import {getRepoBaseDir} from '../utils/config'; | ||||
| 
 | ||||
| export function verify() { | ||||
|   // Exit early on shelljs errors
 | ||||
|   set('-e'); | ||||
| 
 | ||||
|   // Regex Matcher for contains_any_globs conditions
 | ||||
|   const CONTAINS_ANY_GLOBS_REGEX = /^'([^']+)',?$/; | ||||
|   // Full path of the angular project directory
 | ||||
|   const ANGULAR_PROJECT_DIR = process.cwd(); | ||||
|   // Change to the Angular project directory
 | ||||
|   cd(ANGULAR_PROJECT_DIR); | ||||
| 
 | ||||
|   // Whether to log verbosely
 | ||||
|   const VERBOSE_MODE = process.argv.includes('-v'); | ||||
|   // Full path of the angular project directory
 | ||||
|   const PROJECT_DIR = getRepoBaseDir(); | ||||
|   // Change to the Angular project directory
 | ||||
|   cd(PROJECT_DIR); | ||||
|   // Full path to PullApprove config file
 | ||||
|   const PULL_APPROVE_YAML_PATH = path.resolve(ANGULAR_PROJECT_DIR, '.pullapprove.yml'); | ||||
|   const PULL_APPROVE_YAML_PATH = path.resolve(PROJECT_DIR, '.pullapprove.yml'); | ||||
|   // All relative path file names in the git repo, this is retrieved using git rather
 | ||||
|   // that a glob so that we only get files that are checked in, ignoring things like
 | ||||
|   // node_modules, .bazelrc.user, etc
 | ||||
|   const ALL_FILES = exec('git ls-tree --full-tree -r --name-only HEAD', {silent: true}) | ||||
|                         .trim() | ||||
|                         .split('\n') | ||||
|                         .filter((_: string) => !!_); | ||||
|   if (!ALL_FILES.length) { | ||||
|     console.error( | ||||
|         `No files were found to be in the git tree, did you run this command from \n` + | ||||
|         `inside the angular repository?`); | ||||
|   const REPO_FILES = | ||||
|       exec('git ls-files', {silent: true}).trim().split('\n').filter((_: string) => !!_); | ||||
|   // The pull approve config file.
 | ||||
|   const pullApproveYamlRaw = readFileSync(PULL_APPROVE_YAML_PATH, 'utf8'); | ||||
|   // JSON representation of the pullapprove yaml file.
 | ||||
|   const pullApprove = parsePullApproveYaml(pullApproveYamlRaw); | ||||
|   // All of the groups defined in the pullapprove yaml.
 | ||||
|   const groups = Object.entries(pullApprove.groups).map(([groupName, group]) => { | ||||
|     return new PullApproveGroup(groupName, group); | ||||
|   }); | ||||
|   // PullApprove groups without matchers.
 | ||||
|   const groupsWithoutMatchers = groups.filter(group => !group.hasMatchers); | ||||
|   // PullApprove groups with matchers.
 | ||||
|   const groupsWithMatchers = groups.filter(group => group.hasMatchers); | ||||
|   // All lines from group conditions which are not parsable.
 | ||||
|   const groupsWithBadLines = groups.filter(g => !!g.getBadLines().length); | ||||
|   // If any groups contains bad lines, log bad lines and exit failing.
 | ||||
|   if (groupsWithBadLines.length) { | ||||
|     logHeader('PullApprove config file parsing failure'); | ||||
|     console.info(`Discovered errors in ${groupsWithBadLines.length} groups`); | ||||
|     groupsWithBadLines.forEach(group => { | ||||
|       console.info(` - [${group.groupName}]`); | ||||
|       group.getBadLines().forEach(line => console.info(`    ${line}`)); | ||||
|     }); | ||||
|     console.info( | ||||
|         `Correct the invalid conditions, before PullApprove verification can be completed`); | ||||
|     process.exit(1); | ||||
|   } | ||||
|   // Files which are matched by at least one group.
 | ||||
|   const matchedFiles: string[] = []; | ||||
|   // Files which are not matched by at least one group.
 | ||||
|   const unmatchedFiles: string[] = []; | ||||
| 
 | ||||
|   /** Gets the glob matching information from each group's condition. */ | ||||
|   function getGlobMatchersFromCondition( | ||||
|       groupName: string, condition: string): [GlobMatcher[], string[]] { | ||||
|     const trimmedCondition = condition.trim(); | ||||
|     const globMatchers: GlobMatcher[] = []; | ||||
|     const badConditionLines: string[] = []; | ||||
| 
 | ||||
|     // If the condition starts with contains_any_globs, evaluate all of the globs
 | ||||
|     if (trimmedCondition.startsWith('contains_any_globs')) { | ||||
|       trimmedCondition.split('\n') | ||||
|           .slice(1, -1) | ||||
|           .map(glob => { | ||||
|             const trimmedGlob = glob.trim(); | ||||
|             const match = trimmedGlob.match(CONTAINS_ANY_GLOBS_REGEX); | ||||
|             if (!match) { | ||||
|               badConditionLines.push(trimmedGlob); | ||||
|               return ''; | ||||
|             } | ||||
|             return match[1]; | ||||
|           }) | ||||
|           .filter(globString => !!globString) | ||||
|           .forEach(globString => globMatchers.push({ | ||||
|             group: groupName, | ||||
|             glob: globString, | ||||
|             matcher: new Minimatch(globString, {dot: true}), | ||||
|             matchCount: 0, | ||||
|           })); | ||||
|     } | ||||
|     return [globMatchers, badConditionLines]; | ||||
|   } | ||||
| 
 | ||||
|   /** Create logs for each review group. */ | ||||
|   function logGroups(groups: Map<string, Map<string, GlobMatcher>>) { | ||||
|     Array.from(groups.entries()).sort().forEach(([groupName, globs]) => { | ||||
|       console.groupCollapsed(groupName); | ||||
|       Array.from(globs.values()) | ||||
|           .sort((a, b) => b.matchCount - a.matchCount) | ||||
|           .forEach(glob => console.info(`${glob.glob} - ${glob.matchCount}`)); | ||||
|       console.groupEnd(); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   /** Logs a header within a text drawn box. */ | ||||
|   function logHeader(...params: string[]) { | ||||
|     const totalWidth = 80; | ||||
|     const fillWidth = totalWidth - 2; | ||||
|     const headerText = params.join(' ').substr(0, fillWidth); | ||||
|     const leftSpace = Math.ceil((fillWidth - headerText.length) / 2); | ||||
|     const rightSpace = fillWidth - leftSpace - headerText.length; | ||||
|     const fill = (count: number, content: string) => content.repeat(count); | ||||
| 
 | ||||
|     console.info(`┌${fill(fillWidth, '─')}┐`); | ||||
|     console.info(`│${fill(leftSpace, ' ')}${headerText}${fill(rightSpace, ' ')}│`); | ||||
|     console.info(`└${fill(fillWidth, '─')}┘`); | ||||
|   } | ||||
| 
 | ||||
|   /** Runs the pull approve verification check on provided files. */ | ||||
|   function runVerification(files: string[]) { | ||||
|     // All of the globs created for each group's conditions.
 | ||||
|     const allGlobs: GlobMatcher[] = []; | ||||
|     // The pull approve config file.
 | ||||
|     const pullApprove = readFileSync(PULL_APPROVE_YAML_PATH, {encoding: 'utf8'}); | ||||
|     // All of the PullApprove groups, parsed from the PullApprove yaml file.
 | ||||
|     const parsedPullApproveGroups = | ||||
|         parseYaml(pullApprove).groups as{[key: string]: {conditions: string}}; | ||||
|     // All files which were found to match a condition in PullApprove.
 | ||||
|     const matchedFiles = new Set<string>(); | ||||
|     // All files which were not found to match a condition in PullApprove.
 | ||||
|     const unmatchedFiles = new Set<string>(); | ||||
|     // All PullApprove groups which matched at least one file.
 | ||||
|     const matchedGroups = new Map<string, Map<string, GlobMatcher>>(); | ||||
|     // All PullApprove groups which did not match at least one file.
 | ||||
|     const unmatchedGroups = new Map<string, Map<string, GlobMatcher>>(); | ||||
|     // All condition lines which were not able to be correctly parsed, by group.
 | ||||
|     const badConditionLinesByGroup = new Map<string, string[]>(); | ||||
|     // Total number of condition lines which were not able to be correctly parsed.
 | ||||
|     let badConditionLineCount = 0; | ||||
| 
 | ||||
|     // Get all of the globs from the PullApprove group conditions.
 | ||||
|     Object.entries(parsedPullApproveGroups).forEach(([groupName, group]) => { | ||||
|       for (const condition of group.conditions) { | ||||
|         const [matchers, badConditions] = getGlobMatchersFromCondition(groupName, condition); | ||||
|         if (badConditions.length) { | ||||
|           badConditionLinesByGroup.set(groupName, badConditions); | ||||
|           badConditionLineCount += badConditions.length; | ||||
|         } | ||||
|         allGlobs.push(...matchers); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
|     if (badConditionLineCount) { | ||||
|       console.info(`Discovered ${badConditionLineCount} parsing errors in PullApprove conditions`); | ||||
|       console.info(`Attempted parsing using: ${CONTAINS_ANY_GLOBS_REGEX}`); | ||||
|       console.info(); | ||||
|       console.info(`Unable to properly parse the following line(s) by group:`); | ||||
|       badConditionLinesByGroup.forEach((badConditionLines, groupName) => { | ||||
|         console.info(`- ${groupName}:`); | ||||
|         badConditionLines.forEach(line => console.info(`    ${line}`)); | ||||
|       }); | ||||
|       console.info(); | ||||
|       console.info( | ||||
|           `Please correct the invalid conditions, before PullApprove verification can be completed`); | ||||
|       process.exit(1); | ||||
|     } | ||||
| 
 | ||||
|     // Check each file for if it is matched by a PullApprove condition.
 | ||||
|     for (let file of files) { | ||||
|       const matched = allGlobs.filter(glob => glob.matcher.match(file)); | ||||
|       matched.length ? matchedFiles.add(file) : unmatchedFiles.add(file); | ||||
|       matched.forEach(glob => glob.matchCount++); | ||||
|     } | ||||
| 
 | ||||
|     // Add each glob for each group to a map either matched or unmatched.
 | ||||
|     allGlobs.forEach(glob => { | ||||
|       const groups = glob.matchCount ? matchedGroups : unmatchedGroups; | ||||
|       const globs = groups.get(glob.group) || new Map<string, GlobMatcher>(); | ||||
|       // Set the globs map in the groups map
 | ||||
|       groups.set(glob.group, globs); | ||||
|       // Set the glob in the globs map
 | ||||
|       globs.set(glob.glob, glob); | ||||
|     }); | ||||
| 
 | ||||
|     // PullApprove is considered verified if no files or groups are found to be unsed.
 | ||||
|     const verificationSucceeded = !(unmatchedFiles.size || unmatchedGroups.size); | ||||
| 
 | ||||
|     /** | ||||
|      * Overall result | ||||
|      */ | ||||
|     logHeader('Result'); | ||||
|     if (verificationSucceeded) { | ||||
|       console.info('PullApprove verification succeeded!'); | ||||
|   // Test each file in the repo against each group for being matched.
 | ||||
|   REPO_FILES.forEach((file: string) => { | ||||
|     if (groupsWithMatchers.filter(group => group.testFile(file)).length) { | ||||
|       matchedFiles.push(file); | ||||
|     } else { | ||||
|       console.info(`PullApprove verification failed.\n`); | ||||
|       console.info(`Please update '.pullapprove.yml' to ensure that all necessary`); | ||||
|       console.info(`files/directories have owners and all patterns that appear in`); | ||||
|       console.info(`the file correspond to actual files/directories in the repo.`); | ||||
|       unmatchedFiles.push(file); | ||||
|     } | ||||
|     /** | ||||
|      * File by file Summary | ||||
|      */ | ||||
|     logHeader('PullApprove file match results'); | ||||
|     console.groupCollapsed(`Matched Files (${matchedFiles.size} files)`); | ||||
|     VERBOSE_MODE && matchedFiles.forEach(file => console.info(file)); | ||||
|     console.groupEnd(); | ||||
|     console.groupCollapsed(`Unmatched Files (${unmatchedFiles.size} files)`); | ||||
|     unmatchedFiles.forEach(file => console.info(file)); | ||||
|     console.groupEnd(); | ||||
|   }); | ||||
|   // Results for each group
 | ||||
|   const resultsByGroup = groupsWithMatchers.map(group => group.getResults()); | ||||
|   // Whether all group condition lines match at least one file and all files
 | ||||
|   // are matched by at least one group.
 | ||||
|   const verificationSucceeded = | ||||
|       resultsByGroup.every(r => !r.unmatchedCount) && !unmatchedFiles.length; | ||||
| 
 | ||||
|     /** | ||||
|      * Group by group Summary | ||||
|      */ | ||||
|     logHeader('PullApprove group matches'); | ||||
|     console.groupCollapsed(`Matched Groups (${matchedGroups.size} groups)`); | ||||
|     VERBOSE_MODE && logGroups(matchedGroups); | ||||
|     console.groupEnd(); | ||||
|     console.groupCollapsed(`Unmatched Groups (${unmatchedGroups.size} groups)`); | ||||
|     logGroups(unmatchedGroups); | ||||
|     console.groupEnd(); | ||||
| 
 | ||||
|     // Provide correct exit code based on verification success.
 | ||||
|     process.exit(verificationSucceeded ? 0 : 1); | ||||
|   /** | ||||
|    * Overall result | ||||
|    */ | ||||
|   logHeader('Overall Result'); | ||||
|   if (verificationSucceeded) { | ||||
|     console.info('PullApprove verification succeeded!'); | ||||
|   } else { | ||||
|     console.info(`PullApprove verification failed.\n`); | ||||
|     console.info(`Please update '.pullapprove.yml' to ensure that all necessary`); | ||||
|     console.info(`files/directories have owners and all patterns that appear in`); | ||||
|     console.info(`the file correspond to actual files/directories in the repo.`); | ||||
|   } | ||||
|   /** | ||||
|    * File by file Summary | ||||
|    */ | ||||
|   logHeader('PullApprove results by file'); | ||||
|   console.groupCollapsed(`Matched Files (${matchedFiles.length} files)`); | ||||
|   VERBOSE_MODE && matchedFiles.forEach(file => console.info(file)); | ||||
|   console.groupEnd(); | ||||
|   console.groupCollapsed(`Unmatched Files (${unmatchedFiles.length} files)`); | ||||
|   unmatchedFiles.forEach(file => console.info(file)); | ||||
|   console.groupEnd(); | ||||
|   /** | ||||
|    * Group by group Summary | ||||
|    */ | ||||
|   logHeader('PullApprove results by group'); | ||||
|   console.groupCollapsed(`Groups without matchers (${groupsWithoutMatchers.length} groups)`); | ||||
|   VERBOSE_MODE && groupsWithoutMatchers.forEach(group => console.info(`${group.groupName}`)); | ||||
|   console.groupEnd(); | ||||
|   const matchedGroups = resultsByGroup.filter(group => !group.unmatchedCount); | ||||
|   console.groupCollapsed(`Matched conditions by Group (${matchedGroups.length} groups)`); | ||||
|   VERBOSE_MODE && matchedGroups.forEach(group => logGroup(group)); | ||||
|   console.groupEnd(); | ||||
|   const unmatchedGroups = resultsByGroup.filter(group => group.unmatchedCount); | ||||
|   console.groupCollapsed(`Unmatched conditions by Group (${unmatchedGroups.length} groups)`); | ||||
|   unmatchedGroups.forEach(group => logGroup(group, false)); | ||||
|   console.groupEnd(); | ||||
| 
 | ||||
| 
 | ||||
|   runVerification(ALL_FILES); | ||||
|   // Provide correct exit code based on verification success.
 | ||||
|   process.exit(verificationSucceeded ? 0 : 1); | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user