UNPKG

@dev-build-deploy/commit-it

Version:
210 lines (209 loc) 7.2 kB
"use strict"; /* * SPDX-FileCopyrightText: 2023 Kevin de Jong <monkaii@hotmail.com> * SPDX-License-Identifier: MIT */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.parseCommitMessage = exports.getFooterElementsFromParagraph = exports.Commit = void 0; const git = __importStar(require("./git")); const TRAILER_REGEX = /^((BREAKING CHANGE:)|([\w-]+(:| #))|([ \t]+)\w*)/i; /** * Git Commit * @class Commit * @member author The commit author and date * @member commiter The commit commiter and date * @member hash The commit hash * @member subject The commit subject * @member body The commit body * @member footer The commit footer * @member raw The commit message */ class Commit { _commit; constructor(commit) { this._commit = commit; } /** * Retrieves the commit information from git using the provided hash * @param props The commit hash and root path * @returns The commit object */ static fromHash(props) { const commit = git.getCommitFromHash(props.hash, props.rootPath ?? process.cwd()); return new Commit(commit); } /** * Creates a Commit object from the provided string * @param props The commit hash, author, committer and message * @returns The commit object */ static fromString(props) { // Git will trim all comments (lines starting with #), so we do the same // to ensure the commit message is parsed correctly. const trimmedMessage = props.message .split(/\r?\n/) .filter(line => !line.startsWith("#")) .join("\n"); const commit = { hash: props.hash, ...parseCommitMessage(trimmedMessage), author: props.author, committer: props.committer, raw: props.message, }; return new Commit(commit); } get author() { return this._commit.author; } get committer() { return this._commit.committer; } get hash() { return this._commit.hash; } get subject() { return this._commit.subject; } get body() { return this._commit.body; } get footer() { return this._commit.footer; } get raw() { return this._commit.raw; } get isFixupCommit() { return this._commit.attributes.isFixup; } get isMergeCommit() { return this._commit.attributes.isMerge; } toJSON() { return this._commit; } } exports.Commit = Commit; /** * Returns a dictionary containing key-value pairs extracted from the footer of the provided commit message. * The key must either be: * - a single word (optional, using - as a seperator) followed by a colon (:) * - BREAKING CHANGE: * * The value is either: * - the remainder of the line * - the remainder of the line + anything that follows on the next lines which is indented by at least one space * * @internal */ function getFooterElementsFromParagraph(footer) { const footerLines = footer.split(/\r?\n/); const result = []; for (let lineNr = 0; lineNr < footerLines.length; lineNr++) { const line = footerLines[lineNr]; const match = TRAILER_REGEX.exec(line); if (match === null) continue; let key = match[1].replace(/:$/, ""); let value = line.substring(match[1].length).trim(); if (match[1].endsWith(" #")) { key = match[1].substring(0, match[1].length - 2); value = `#${value}`; } const matchLine = lineNr; // Check if the value continues on the next line while (lineNr + 1 < footerLines.length && (/^\s/.test(footerLines[lineNr + 1]) || footerLines[lineNr + 1].length === 0)) { lineNr++; value += "\n" + footerLines[lineNr].trim(); } result.push({ lineNumber: matchLine + 1, key, value, }); } return Object.keys(result).length > 0 ? result : undefined; } exports.getFooterElementsFromParagraph = getFooterElementsFromParagraph; /** * Checks if the provided subject is a common (default) merge pattern. * Currently supported: * - GitHub * - BitBucket * - GitLab * * @param subject The subject to check * @returns True if the subject is a common merge pattern, false otherwise */ function subjectIsMergePattern(subject) { const githubMergeRegex = /^Merge pull request #(\d+) from '?([a-zA-Z0-9_./-]+)'?$/; const bitbucketMergeRegex = /^Merged in '?([a-zA-Z0-9_./-]+)'? \(pull request #(\d+)\)$/; const gitlabMergeRegex = /^Merge( remote-tracking)? branch '?([a-zA-Z0-9_./-]+)'?? into '?([a-zA-Z0-9_./-]+)'?$/; return githubMergeRegex.test(subject) || bitbucketMergeRegex.test(subject) || gitlabMergeRegex.test(subject); } /** * Parses the provided commit message (full message, not just the subject) into * a Commit object. * @param message The commit message * @returns The parsed commit object * @internal */ function parseCommitMessage(message) { const isTrailerOnly = (message) => message.split(/\r?\n/).every(line => { const match = TRAILER_REGEX.exec(line); return match !== null; }); const paragraphs = message.split(/^\r?\n/m); let footer = undefined; let body = undefined; if (paragraphs.length > 1 && isTrailerOnly(paragraphs[paragraphs.length - 1])) { footer = paragraphs[paragraphs.length - 1].trim(); paragraphs.pop(); } if (paragraphs.length > 1) { body = paragraphs.splice(1).join("\n").trim(); if (body === "") body = undefined; } const subject = paragraphs[0].trim(); const isFixup = subject.toLowerCase().startsWith("fixup!"); const isMerge = subjectIsMergePattern(subject); return { subject, body, footer: getFooterElementsFromParagraph(footer ?? "")?.reduce((acc, cur) => { acc[cur.key] = cur.value; return acc; }, {}), attributes: { isFixup, isMerge, }, }; } exports.parseCommitMessage = parseCommitMessage;