For commits from git log entries additional fields are available such as the reference hash and author name, update the utility functions in commit-message to include the parsed fields. Additionally define, per commit message type, whether to include the commit in a release notes entry. PR Close #41458
		
			
				
	
	
		
			170 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			170 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
/**
 | 
						|
 * @license
 | 
						|
 * Copyright Google LLC 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 {Commit as ParsedCommit, Options, sync as parse} from 'conventional-commits-parser';
 | 
						|
 | 
						|
 | 
						|
/** A parsed commit, containing the information needed to validate the commit. */
 | 
						|
export interface Commit {
 | 
						|
  /** The full raw text of the commit. */
 | 
						|
  fullText: string;
 | 
						|
  /** The header line of the commit, will be used in the changelog entries. */
 | 
						|
  header: string;
 | 
						|
  /** The full body of the commit, not including the footer. */
 | 
						|
  body: string;
 | 
						|
  /** The footer of the commit, containing issue references and note sections. */
 | 
						|
  footer: string;
 | 
						|
  /** A list of the references to other issues made throughout the commit message. */
 | 
						|
  references: ParsedCommit.Reference[];
 | 
						|
  /** The type of the commit message. */
 | 
						|
  type: string;
 | 
						|
  /** The scope of the commit message. */
 | 
						|
  scope: string;
 | 
						|
  /** The npm scope of the commit message. */
 | 
						|
  npmScope: string;
 | 
						|
  /** The subject of the commit message. */
 | 
						|
  subject: string;
 | 
						|
  /** A list of breaking change notes in the commit message. */
 | 
						|
  breakingChanges: ParsedCommit.Note[];
 | 
						|
  /** A list of deprecation notes in the commit message. */
 | 
						|
  deprecations: ParsedCommit.Note[];
 | 
						|
  /** Whether the commit is a fixup commit. */
 | 
						|
  isFixup: boolean;
 | 
						|
  /** Whether the commit is a squash commit. */
 | 
						|
  isSquash: boolean;
 | 
						|
  /** Whether the commit is a revert commit. */
 | 
						|
  isRevert: boolean;
 | 
						|
}
 | 
						|
 | 
						|
/** A parsed commit which originated from a Git Log entry */
 | 
						|
export interface CommitFromGitLog extends Commit {
 | 
						|
  author: string;
 | 
						|
  hash: string;
 | 
						|
  shortHash: string;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * A list of tuples expressing the fields to extract from each commit log entry. The tuple contains
 | 
						|
 * two values, the first is the key for the property and the second is the template shortcut for the
 | 
						|
 * git log command.
 | 
						|
 */
 | 
						|
const commitFields = {
 | 
						|
  hash: '%H',
 | 
						|
  shortHash: '%h',
 | 
						|
  author: '%aN',
 | 
						|
};
 | 
						|
/** The additional fields to be included in commit log entries for parsing. */
 | 
						|
export type CommitFields = typeof commitFields;
 | 
						|
/** The commit fields described as git log format entries for parsing. */
 | 
						|
export const commitFieldsAsFormat = (fields: CommitFields) => {
 | 
						|
  return Object.entries(fields).map(([key, value]) => `%n-${key}-%n${value}`).join('');
 | 
						|
};
 | 
						|
/**
 | 
						|
 * The git log format template to create git log entries for parsing.
 | 
						|
 *
 | 
						|
 * The conventional commits parser expects to parse the standard git log raw body (%B) into its
 | 
						|
 * component parts. Additionally it will parse additional fields with keys defined by
 | 
						|
 * `-{key name}-` separated by new lines.
 | 
						|
 * */
 | 
						|
export const gitLogFormatForParsing = `%B${commitFieldsAsFormat(commitFields)}`;
 | 
						|
/** Markers used to denote the start of a note section in a commit. */
 | 
						|
enum NoteSections {
 | 
						|
  BREAKING_CHANGE = 'BREAKING CHANGE',
 | 
						|
  DEPRECATED = 'DEPRECATED',
 | 
						|
}
 | 
						|
/** Regex determining if a commit is a fixup. */
 | 
						|
const FIXUP_PREFIX_RE = /^fixup! /i;
 | 
						|
/** Regex determining if a commit is a squash. */
 | 
						|
const SQUASH_PREFIX_RE = /^squash! /i;
 | 
						|
/** Regex determining if a commit is a revert. */
 | 
						|
const REVERT_PREFIX_RE = /^revert:? /i;
 | 
						|
/**
 | 
						|
 * Regex pattern for parsing the header line of a commit.
 | 
						|
 *
 | 
						|
 * Several groups are being matched to be used in the parsed commit object, being mapped to the
 | 
						|
 * `headerCorrespondence` object.
 | 
						|
 *
 | 
						|
 * The pattern can be broken down into component parts:
 | 
						|
 * - `(\w+)` - a capturing group discovering the type of the commit.
 | 
						|
 * - `(?:\((?:([^/]+)\/)?([^)]+)\))?` - a pair of capturing groups to capture the scope and,
 | 
						|
 * optionally the npmScope of the commit.
 | 
						|
 * - `(.*)` - a capturing group discovering the subject of the commit.
 | 
						|
 */
 | 
						|
const headerPattern = /^(\w+)(?:\((?:([^/]+)\/)?([^)]+)\))?: (.*)$/;
 | 
						|
/**
 | 
						|
 * The property names used for the values extracted from the header via the `headerPattern` regex.
 | 
						|
 */
 | 
						|
const headerCorrespondence = ['type', 'npmScope', 'scope', 'subject'];
 | 
						|
/**
 | 
						|
 * Configuration options for the commit parser.
 | 
						|
 *
 | 
						|
 * NOTE: An extended type from `Options` must be used because the current
 | 
						|
 * @types/conventional-commits-parser version does not include the `notesPattern` field.
 | 
						|
 */
 | 
						|
const parseOptions: Options&{notesPattern: (keywords: string) => RegExp} = {
 | 
						|
  commentChar: '#',
 | 
						|
  headerPattern,
 | 
						|
  headerCorrespondence,
 | 
						|
  noteKeywords: [NoteSections.BREAKING_CHANGE, NoteSections.DEPRECATED],
 | 
						|
  notesPattern: (keywords: string) => new RegExp(`(${keywords})(?:: ?)(.*)`),
 | 
						|
};
 | 
						|
 | 
						|
/** Parse a commit message into its composite parts. */
 | 
						|
export const parseCommitMessage: (fullText: string) => Commit = parseInternal;
 | 
						|
 | 
						|
/** Parse a commit message from a git log entry into its composite parts. */
 | 
						|
export const parseCommitFromGitLog: (fullText: Buffer) => CommitFromGitLog = parseInternal;
 | 
						|
 | 
						|
/** Parse a full commit message into its composite parts. */
 | 
						|
function parseInternal(fullText: string): Commit;
 | 
						|
function parseInternal(fullText: Buffer): CommitFromGitLog;
 | 
						|
function parseInternal(fullText: string|Buffer): CommitFromGitLog|Commit {
 | 
						|
  // Ensure the fullText symbol is a `string`, even if a Buffer was provided.
 | 
						|
  fullText = fullText.toString();
 | 
						|
  /** The commit message text with the fixup and squash markers stripped out. */
 | 
						|
  const strippedCommitMsg = fullText.replace(FIXUP_PREFIX_RE, '')
 | 
						|
                                .replace(SQUASH_PREFIX_RE, '')
 | 
						|
                                .replace(REVERT_PREFIX_RE, '');
 | 
						|
  /** The initially parsed commit. */
 | 
						|
  const commit = parse(strippedCommitMsg, parseOptions);
 | 
						|
  /** A list of breaking change notes from the commit. */
 | 
						|
  const breakingChanges: ParsedCommit.Note[] = [];
 | 
						|
  /** A list of deprecation notes from the commit. */
 | 
						|
  const deprecations: ParsedCommit.Note[] = [];
 | 
						|
 | 
						|
  // Extract the commit message notes by marked types into their respective lists.
 | 
						|
  commit.notes.forEach((note: ParsedCommit.Note) => {
 | 
						|
    if (note.title === NoteSections.BREAKING_CHANGE) {
 | 
						|
      return breakingChanges.push(note);
 | 
						|
    }
 | 
						|
    if (note.title === NoteSections.DEPRECATED) {
 | 
						|
      return deprecations.push(note);
 | 
						|
    }
 | 
						|
  });
 | 
						|
 | 
						|
  return {
 | 
						|
    fullText,
 | 
						|
    breakingChanges,
 | 
						|
    deprecations,
 | 
						|
    body: commit.body || '',
 | 
						|
    footer: commit.footer || '',
 | 
						|
    header: commit.header || '',
 | 
						|
    references: commit.references,
 | 
						|
    scope: commit.scope || '',
 | 
						|
    subject: commit.subject || '',
 | 
						|
    type: commit.type || '',
 | 
						|
    npmScope: commit.npmScope || '',
 | 
						|
    isFixup: FIXUP_PREFIX_RE.test(fullText),
 | 
						|
    isSquash: SQUASH_PREFIX_RE.test(fullText),
 | 
						|
    isRevert: REVERT_PREFIX_RE.test(fullText),
 | 
						|
    author: commit.author || undefined,
 | 
						|
    hash: commit.hash || undefined,
 | 
						|
    shortHash: commit.shortHash || undefined,
 | 
						|
  };
 | 
						|
}
 |