import is from '@sindresorhus/is'; import type { CommitMessageJSON } from '../../../types'; /** * @see https://git-scm.com/docs/git-commit#_discussion * * [optional prefix]: <suject> * [optional body] * [optional footer] */ export abstract class CommitMessage { private static readonly SEPARATOR: string = ':'; private static readonly EXTRA_WHITESPACES = /\s+/g; private _body = ''; private _footer = ''; private _subject = ''; static formatPrefix(prefix: string): string { if (!prefix) { return ''; } if (prefix.endsWith(CommitMessage.SEPARATOR)) { return prefix; } return `${prefix}${CommitMessage.SEPARATOR}`; } toJSON(): CommitMessageJSON { return { body: this._body, footer: this._footer, subject: this._subject, }; } toString(): string { const parts: readonly (string | undefined)[] = [ this.title, this._body, this._footer, ]; return parts.filter(is.nonEmptyStringAndNotWhitespace).join('\n\n'); } get title(): string { return [CommitMessage.formatPrefix(this.prefix), this.formatSubject()] .join(' ') .trim(); } set body(value: string) { this._body = this.normalizeInput(value); } set footer(value: string) { this._footer = this.normalizeInput(value); } set subject(value: string) { this._subject = this.normalizeInput(value); this._subject = this._subject?.replace( CommitMessage.EXTRA_WHITESPACES, ' ', ); } formatSubject(): string { if (!this._subject) { return ''; } if (this.prefix) { return this._subject.charAt(0).toLowerCase() + this._subject.slice(1); } return this._subject.charAt(0).toUpperCase() + this._subject.slice(1); } protected abstract get prefix(): string; protected normalizeInput(value: string | null | undefined): string { return value?.trim() ?? ''; } }