From 684a4e60e114883908d7d4e41b84c49dce260ebd Mon Sep 17 00:00:00 2001 From: Kevin de Jong Date: Thu, 7 Dec 2023 17:15:13 +0100 Subject: [PATCH] feat: add `--verbose` option to the CLI to pretty print commits --- dist/bump/index.js | 37 +++++++--- dist/cli/index.js | 163 +++++++++++++++++++++++++++++++++-------- dist/validate/index.js | 39 ++++++---- docs/cli.md | 9 ++- src/cli/cli.ts | 54 ++++++++------ src/cli/colors.ts | 20 +++++ src/cli/utils.ts | 36 ++++++++- 7 files changed, 275 insertions(+), 83 deletions(-) create mode 100644 src/cli/colors.ts diff --git a/dist/bump/index.js b/dist/bump/index.js index 706da777..3bc826e5 100644 --- a/dist/bump/index.js +++ b/dist/bump/index.js @@ -33856,8 +33856,8 @@ function processCommitsForBump(commits, config) { // _before_ they were merged, and certain GitHub CI settings may append // a reference to the PR number in merge commits. const configCopy = Object.create(config); - configCopy.setRuleActivationStatus("C014", false); // SubjectExceedsLineLengthLimit - configCopy.setRuleActivationStatus("C019", false); // SubjectContainsIssueReference + configCopy.setRuleActive("C014", false); // SubjectExceedsLineLengthLimit + configCopy.setRuleActive("C019", false); // SubjectContainsIssueReference return (0, validate_1.processCommits)(commits, configCopy); } /** @@ -34809,8 +34809,8 @@ const FOOTER_REGEX = /^(?[\w-]+|BREAKING\sCHANGE|[\w-\s]+\sby)(?::[ ]|[ ] */ class Footer { constructor(token, value) { - this._token = token; - this._value = value; + this.token = token; + this.value = value; } set token(token) { if (token === "BREAKING CHANGE") { @@ -34831,8 +34831,8 @@ class Footer { get value() { return this._value.trimEnd(); } - appendParagrah(paragrah) { - this._value += os.EOL + paragrah; + appendParagraph(paragraph) { + this._value += os.EOL + paragraph; } } /** @@ -34845,21 +34845,34 @@ function getConventionalCommitMetadata(message) { let body = []; if (message.length > 1) { let endOfBody = 1; + let ignoreEmptyLines = false; // eslint-disable-next-line github/array-foreach message.slice(1).forEach((line, index) => { var _a; const matches = (_a = FOOTER_REGEX.exec(line)) === null || _a === void 0 ? void 0 : _a.groups; + const currentTrailer = footers[footers.length - 1]; if (matches) { footers.push(new Footer(matches.token, matches.value)); + ignoreEmptyLines = false; + } + else if (/^-{8,}$/.test(line)) { + // End current trailer when a `---------` line is detected (i.e. as inserted + // by GitHub for certain merge strategies). + ignoreEmptyLines = true; + } + else if (ignoreEmptyLines && line.trim() === "") { + // Ignore empty lines after `---------` line + // until the next paragraph or footer element is detected. } - else if (footers.length > 0 && /^\s*/.test(line)) { - // Multiline trailers use folding - footers[footers.length - 1].appendParagrah(line); + else if (currentTrailer && (/^\s+/.test(line) || line.trim() === "")) { + // Multiline trailers use folding (RFC822), the exception being for empty lines + currentTrailer.appendParagraph(line); } else { // Discard detected git trailers as non-compliant item has been found endOfBody = index + 1; footers = []; + ignoreEmptyLines = false; } }); // Set the body @@ -35092,7 +35105,7 @@ class Configuration { get initialDevelopment() { return this._initialDevelopment; } - setRuleActivationStatus(ruleId, enabled) { + setRuleActive(ruleId, enabled) { const rule = this.rules.get(ruleId); if (rule !== undefined) { rule.enabled = enabled; @@ -35121,7 +35134,7 @@ class Configuration { */ if (typeof data[key] === "object") { for (const item of data[key]) { - this.setRuleActivationStatus(item, key === "enable"); + this.setRuleActive(item, key === "enable"); } } else { @@ -35172,7 +35185,7 @@ class Configuration { } } else { - core.info(`Warning: "${key}.${typ}.${entry}" is unknown and has no effect.`); + core.warning(`Warning: "${key}.${typ}.${entry}" is unknown and has no effect.`); } } this.tags[typ] = tagObject; diff --git a/dist/cli/index.js b/dist/cli/index.js index 87ca1335..dff57e76 100755 --- a/dist/cli/index.js +++ b/dist/cli/index.js @@ -32312,17 +32312,13 @@ const dedent_1 = __importDefault(__nccwpck_require__(5281)); const core = __importStar(__nccwpck_require__(2186)); const fs = __importStar(__nccwpck_require__(7147)); const os = __importStar(__nccwpck_require__(2037)); +const Color = __importStar(__nccwpck_require__(7871)); const commit_1 = __nccwpck_require__(1730); const config_1 = __nccwpck_require__(6373); const errors_1 = __nccwpck_require__(6976); const commander_1 = __nccwpck_require__(4379); const utils_1 = __nccwpck_require__(2449); const program = new commander_1.Command(); -const gray = "\x1b[90m"; -const red = "\x1b[91m"; -const green = "\x1b[92m"; -const yellow = "\x1b[93m"; -const reset = "\x1b[0m"; program .name("commisery") .description("Commisery Conventional Commit Message Manager") @@ -32330,26 +32326,35 @@ program program .command("check") .description("Checks whether commit messages adhere to the Conventional Commits standard.") + .option("-v, --verbose", "also print commit message metadata.", false) .argument("[TARGET...]", `The \`TARGET\` can be: - a single commit hash - a file containing the commit message to check - a revision range that \`git rev-list\` can interpret When TARGET is omitted, 'HEAD' is implied.`) - .action(async (target) => { + .action(async (target, options) => { const config = new config_1.Configuration(program.opts().config); if (target.length === 0) { target = ["HEAD"]; } let messages = []; if (fs.existsSync(target.join(" "))) { - messages = [fs.readFileSync(target.join(" "), "utf8")]; + messages = [ + { + sha: target.join(" "), + body: fs.readFileSync(target.join(" "), "utf8"), + }, + ]; } else { messages = await (0, utils_1.getCommitMessages)(target); } for (const message of messages) { try { - new commit_1.ConventionalCommitMessage(message, undefined, config); + const commitmessage = new commit_1.ConventionalCommitMessage(message.body, message.sha, config); + if (options.verbose) { + (0, utils_1.prettyPrintCommitMessage)(commitmessage); + } } catch (error) { if (error instanceof errors_1.ConventionalCommitError) { @@ -32366,38 +32371,73 @@ program .command("overview") .description("Lists the accepted Conventional Commit types and Rules (including description)") .action(() => { - var _a, _b; + var _a; const config = new config_1.Configuration(program.opts().config); core.info((0, dedent_1.default)(` Conventional Commit types -------------------------`)); for (const key in config.tags) { const bumps = config.tags[key].bump && key !== "fix" - ? ` ${yellow}(bumps patch)${reset}` + ? ` ${Color.YELLOW("(bumps patch)")}` : ""; - core.info(`${key}: ${gray}${config.tags[key].description}${reset}${bumps}`); + core.info(`${key}: ${Color.GRAY((_a = config.tags[key].description) !== null && _a !== void 0 ? _a : "")}${bumps}`); } core.info(os.EOL); core.info((0, dedent_1.default)(` Commisery Validation rules -------------------------- - [${green}o${reset}]: ${gray}rule is enabled${reset}, [${red}x${reset}]: ${gray}rule is disabled${reset} + [${Color.GREEN("o")}]: ${Color.GRAY("rule is enabled")}, [${Color.RED("x")}]: ${Color.GRAY("rule is disabled")} `)); core.info(os.EOL); - for (const rule in config.rules) { - const status = ((_a = config.rules.get(rule)) === null || _a === void 0 ? void 0 : _a.enabled) - ? `${green}o${reset}` - : `${red}x${reset}`; - core.info(`[${status}] ${rule}: ${gray}${(_b = config.rules.get(rule)) === null || _b === void 0 ? void 0 : _b.description}${reset}`); - } + config.rules.forEach((rule, key) => { + var _a; + const status = rule.enabled + ? `${Color.GREEN("o")}` + : `${Color.RED("x")}`; + core.info(`[${status}] ${key}: ${Color.GRAY((_a = rule.description) !== null && _a !== void 0 ? _a : "")}`); + }); }); program.parse(); +/***/ }), + +/***/ 7871: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +/** + * Copyright (C) 2023, TomTom (http://tomtom.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +Object.defineProperty(exports, "__esModule", ({ value: true })); +exports.YELLOW = exports.GREEN = exports.RED = exports.GRAY = void 0; +const GRAY = (message) => `\x1b[90m${message}\x1b[0m`; +exports.GRAY = GRAY; +const RED = (message) => `\x1b[91m${message}\x1b[0m`; +exports.RED = RED; +const GREEN = (message) => `\x1b[92m${message}\x1b[0m`; +exports.GREEN = GREEN; +const YELLOW = (message) => `\x1b[93m${message}\x1b[0m`; +exports.YELLOW = YELLOW; + + /***/ }), /***/ 2449: -/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { +/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { "use strict"; @@ -32416,9 +32456,39 @@ program.parse(); * See the License for the specific language governing permissions and * limitations under the License. */ +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; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.getCommitMessages = exports.getRootPath = void 0; +exports.prettyPrintCommitMessage = exports.getCommitMessages = exports.getRootPath = void 0; const simple_git_1 = __nccwpck_require__(9103); +const Color = __importStar(__nccwpck_require__(7871)); +const os = __importStar(__nccwpck_require__(2037)); +const dedent_1 = __importDefault(__nccwpck_require__(5281)); +const semver_1 = __nccwpck_require__(8593); let __ROOT_PATH = undefined; /** * Determine the root path of the GIT project @@ -32475,7 +32545,7 @@ async function getCommitMessages(target) { const messages = []; for (const hash of commitHashes) { try { - messages.push(await getCommitMessage(hash)); + messages.push({ sha: hash, body: await getCommitMessage(hash) }); } catch (error) { continue; @@ -32484,6 +32554,24 @@ async function getCommitMessages(target) { return messages; } exports.getCommitMessages = getCommitMessages; +function prettyPrintCommitMessage(commit) { + var _a, _b; + console.log(""); + console.log((0, dedent_1.default)(` + ${Color.RED(`[[---- (Commit ${commit.hexsha}) ----]]`)} + ${Color.GREEN("Type")}: ${commit.type} + ${Color.GREEN("Scope")}: ${(_a = commit.scope) !== null && _a !== void 0 ? _a : "None"} + ${Color.GREEN("Breaking Change")}: ${commit.breakingChange ? Color.RED("Yes") : Color.GREEN("No")} + ${Color.GREEN("Bump")}: ${semver_1.SemVerType[commit.bump]} + ${Color.GREEN("Description")}: ${commit.description} + ${Color.GREEN("Body")}: ${Color.GRAY((_b = commit.body) !== null && _b !== void 0 ? _b : "None")} + ${Color.GREEN("Footers")}: + ${commit.footers + .map(footer => `${footer.token}: ${Color.GRAY(footer.value)}`) + .join(os.EOL)} + `)); +} +exports.prettyPrintCommitMessage = prettyPrintCommitMessage; /***/ }), @@ -32546,8 +32634,8 @@ const FOOTER_REGEX = /^(?[\w-]+|BREAKING\sCHANGE|[\w-\s]+\sby)(?::[ ]|[ ] */ class Footer { constructor(token, value) { - this._token = token; - this._value = value; + this.token = token; + this.value = value; } set token(token) { if (token === "BREAKING CHANGE") { @@ -32568,8 +32656,8 @@ class Footer { get value() { return this._value.trimEnd(); } - appendParagrah(paragrah) { - this._value += os.EOL + paragrah; + appendParagraph(paragraph) { + this._value += os.EOL + paragraph; } } /** @@ -32582,21 +32670,34 @@ function getConventionalCommitMetadata(message) { let body = []; if (message.length > 1) { let endOfBody = 1; + let ignoreEmptyLines = false; // eslint-disable-next-line github/array-foreach message.slice(1).forEach((line, index) => { var _a; const matches = (_a = FOOTER_REGEX.exec(line)) === null || _a === void 0 ? void 0 : _a.groups; + const currentTrailer = footers[footers.length - 1]; if (matches) { footers.push(new Footer(matches.token, matches.value)); + ignoreEmptyLines = false; + } + else if (/^-{8,}$/.test(line)) { + // End current trailer when a `---------` line is detected (i.e. as inserted + // by GitHub for certain merge strategies). + ignoreEmptyLines = true; + } + else if (ignoreEmptyLines && line.trim() === "") { + // Ignore empty lines after `---------` line + // until the next paragraph or footer element is detected. } - else if (footers.length > 0 && /^\s*/.test(line)) { - // Multiline trailers use folding - footers[footers.length - 1].appendParagrah(line); + else if (currentTrailer && (/^\s+/.test(line) || line.trim() === "")) { + // Multiline trailers use folding (RFC822), the exception being for empty lines + currentTrailer.appendParagraph(line); } else { // Discard detected git trailers as non-compliant item has been found endOfBody = index + 1; footers = []; + ignoreEmptyLines = false; } }); // Set the body @@ -32829,7 +32930,7 @@ class Configuration { get initialDevelopment() { return this._initialDevelopment; } - setRuleActivationStatus(ruleId, enabled) { + setRuleActive(ruleId, enabled) { const rule = this.rules.get(ruleId); if (rule !== undefined) { rule.enabled = enabled; @@ -32858,7 +32959,7 @@ class Configuration { */ if (typeof data[key] === "object") { for (const item of data[key]) { - this.setRuleActivationStatus(item, key === "enable"); + this.setRuleActive(item, key === "enable"); } } else { @@ -32909,7 +33010,7 @@ class Configuration { } } else { - core.info(`Warning: "${key}.${typ}.${entry}" is unknown and has no effect.`); + core.warning(`Warning: "${key}.${typ}.${entry}" is unknown and has no effect.`); } } this.tags[typ] = tagObject; diff --git a/dist/validate/index.js b/dist/validate/index.js index a90d5e58..7db2e608 100644 --- a/dist/validate/index.js +++ b/dist/validate/index.js @@ -33668,7 +33668,7 @@ async function determineLabels(conventionalCommits, config) { /** * Validate action entrypoint * - * Validates commits agains the Conventional Commits specification. + * Validates commits against the Conventional Commits specification. * @internal */ async function run() { @@ -33817,8 +33817,8 @@ function processCommitsForBump(commits, config) { // _before_ they were merged, and certain GitHub CI settings may append // a reference to the PR number in merge commits. const configCopy = Object.create(config); - configCopy.setRuleActivationStatus("C014", false); // SubjectExceedsLineLengthLimit - configCopy.setRuleActivationStatus("C019", false); // SubjectContainsIssueReference + configCopy.setRuleActive("C014", false); // SubjectExceedsLineLengthLimit + configCopy.setRuleActive("C019", false); // SubjectContainsIssueReference return (0, validate_1.processCommits)(commits, configCopy); } /** @@ -34770,8 +34770,8 @@ const FOOTER_REGEX = /^(?[\w-]+|BREAKING\sCHANGE|[\w-\s]+\sby)(?::[ ]|[ ] */ class Footer { constructor(token, value) { - this._token = token; - this._value = value; + this.token = token; + this.value = value; } set token(token) { if (token === "BREAKING CHANGE") { @@ -34792,8 +34792,8 @@ class Footer { get value() { return this._value.trimEnd(); } - appendParagrah(paragrah) { - this._value += os.EOL + paragrah; + appendParagraph(paragraph) { + this._value += os.EOL + paragraph; } } /** @@ -34806,21 +34806,34 @@ function getConventionalCommitMetadata(message) { let body = []; if (message.length > 1) { let endOfBody = 1; + let ignoreEmptyLines = false; // eslint-disable-next-line github/array-foreach message.slice(1).forEach((line, index) => { var _a; const matches = (_a = FOOTER_REGEX.exec(line)) === null || _a === void 0 ? void 0 : _a.groups; + const currentTrailer = footers[footers.length - 1]; if (matches) { footers.push(new Footer(matches.token, matches.value)); + ignoreEmptyLines = false; + } + else if (/^-{8,}$/.test(line)) { + // End current trailer when a `---------` line is detected (i.e. as inserted + // by GitHub for certain merge strategies). + ignoreEmptyLines = true; + } + else if (ignoreEmptyLines && line.trim() === "") { + // Ignore empty lines after `---------` line + // until the next paragraph or footer element is detected. } - else if (footers.length > 0 && /^\s*/.test(line)) { - // Multiline trailers use folding - footers[footers.length - 1].appendParagrah(line); + else if (currentTrailer && (/^\s+/.test(line) || line.trim() === "")) { + // Multiline trailers use folding (RFC822), the exception being for empty lines + currentTrailer.appendParagraph(line); } else { // Discard detected git trailers as non-compliant item has been found endOfBody = index + 1; footers = []; + ignoreEmptyLines = false; } }); // Set the body @@ -35053,7 +35066,7 @@ class Configuration { get initialDevelopment() { return this._initialDevelopment; } - setRuleActivationStatus(ruleId, enabled) { + setRuleActive(ruleId, enabled) { const rule = this.rules.get(ruleId); if (rule !== undefined) { rule.enabled = enabled; @@ -35082,7 +35095,7 @@ class Configuration { */ if (typeof data[key] === "object") { for (const item of data[key]) { - this.setRuleActivationStatus(item, key === "enable"); + this.setRuleActive(item, key === "enable"); } } else { @@ -35133,7 +35146,7 @@ class Configuration { } } else { - core.info(`Warning: "${key}.${typ}.${entry}" is unknown and has no effect.`); + core.warning(`Warning: "${key}.${typ}.${entry}" is unknown and has no effect.`); } } this.tags[typ] = tagObject; diff --git a/docs/cli.md b/docs/cli.md index d5c79e2d..d79c1d40 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -31,16 +31,21 @@ Usage: commisery check [options] [TARGET...] Checks whether commit messages adhere to the Conventional Commits standard. Arguments: - TARGET The `TARGET` can be: + TARGET The `TARGET` can be: - a single commit hash - a file containing the commit message to check - a revision range that `git rev-list` can interpret When TARGET is omitted, 'HEAD' is implied. Options: - -h, --help display help for command + -v, --verbose also print commit message metadata (default: false) + -h, --help display help for command ``` +> :bulb: flag will provide an overview of the parsed Conventional Commits elements for each correct message encountered. +> This can be valuable to investigate scenarios in which you expected a different version bump than +> the actual output of the `bump`-action. + ### (Pre-) Commit hook You can use the CLI as a hook in Git to check messages you wrote by creating a `.git/hooks/commit-msg` file with these contents: diff --git a/src/cli/cli.ts b/src/cli/cli.ts index 0d734230..fe8669d1 100644 --- a/src/cli/cli.ts +++ b/src/cli/cli.ts @@ -21,18 +21,15 @@ import * as core from "@actions/core"; import * as fs from "fs"; import * as os from "os"; +import * as Color from "./colors"; + import { ConventionalCommitMessage } from "../commit"; import { Configuration } from "../config"; import { ConventionalCommitError } from "../errors"; import { Command } from "commander"; -import { getCommitMessages } from "./utils"; +import { getCommitMessages, prettyPrintCommitMessage } from "./utils"; const program = new Command(); -const gray = "\x1b[90m"; -const red = "\x1b[91m"; -const green = "\x1b[92m"; -const yellow = "\x1b[93m"; -const reset = "\x1b[0m"; program .name("commisery") @@ -44,6 +41,7 @@ program .description( "Checks whether commit messages adhere to the Conventional Commits standard." ) + .option("-v, --verbose", "also print commit message metadata.", false) .argument( "[TARGET...]", `The \`TARGET\` can be: @@ -52,23 +50,36 @@ program - a revision range that \`git rev-list\` can interpret When TARGET is omitted, 'HEAD' is implied.` ) - .action(async (target: string[]) => { + .action(async (target: string[], options) => { const config = new Configuration(program.opts().config); if (target.length === 0) { target = ["HEAD"]; } - let messages: string[] = []; + let messages: { sha: string; body: string }[] = []; if (fs.existsSync(target.join(" "))) { - messages = [fs.readFileSync(target.join(" "), "utf8")]; + messages = [ + { + sha: target.join(" "), + body: fs.readFileSync(target.join(" "), "utf8"), + }, + ]; } else { messages = await getCommitMessages(target); } for (const message of messages) { try { - new ConventionalCommitMessage(message, undefined, config); + const commitmessage = new ConventionalCommitMessage( + message.body, + message.sha, + config + ); + + if (options.verbose) { + prettyPrintCommitMessage(commitmessage); + } } catch (error: unknown) { if (error instanceof ConventionalCommitError) { for (const err of error.errors) { @@ -99,10 +110,10 @@ program for (const key in config.tags) { const bumps: string = config.tags[key].bump && key !== "fix" - ? ` ${yellow}(bumps patch)${reset}` + ? ` ${Color.YELLOW("(bumps patch)")}` : ""; core.info( - `${key}: ${gray}${config.tags[key].description}${reset}${bumps}` + `${key}: ${Color.GRAY(config.tags[key].description ?? "")}${bumps}` ); } @@ -112,21 +123,20 @@ program dedent(` Commisery Validation rules -------------------------- - [${green}o${reset}]: ${gray}rule is enabled${reset}, [${red}x${reset}]: ${gray}rule is disabled${reset} + [${Color.GREEN("o")}]: ${Color.GRAY("rule is enabled")}, [${Color.RED( + "x" + )}]: ${Color.GRAY("rule is disabled")} `) ); core.info(os.EOL); - for (const rule in config.rules) { - const status: string = config.rules.get(rule)?.enabled - ? `${green}o${reset}` - : `${red}x${reset}`; - core.info( - `[${status}] ${rule}: ${gray}${config.rules.get(rule) - ?.description}${reset}` - ); - } + config.rules.forEach((rule, key) => { + const status: string = rule.enabled + ? `${Color.GREEN("o")}` + : `${Color.RED("x")}`; + core.info(`[${status}] ${key}: ${Color.GRAY(rule.description ?? "")}`); + }); }); program.parse(); diff --git a/src/cli/colors.ts b/src/cli/colors.ts new file mode 100644 index 00000000..5cc534e1 --- /dev/null +++ b/src/cli/colors.ts @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2023, TomTom (http://tomtom.com). + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const GRAY = (message: string): string => `\x1b[90m${message}\x1b[0m`; +export const RED = (message: string): string => `\x1b[91m${message}\x1b[0m`; +export const GREEN = (message: string): string => `\x1b[92m${message}\x1b[0m`; +export const YELLOW = (message: string): string => `\x1b[93m${message}\x1b[0m`; diff --git a/src/cli/utils.ts b/src/cli/utils.ts index 37baf9a5..ab094eba 100644 --- a/src/cli/utils.ts +++ b/src/cli/utils.ts @@ -15,6 +15,11 @@ */ import { GitError, simpleGit } from "simple-git"; +import { ConventionalCommitMessage } from "../commit"; +import * as Color from "./colors"; +import * as os from "os"; +import dedent from "dedent"; +import { SemVerType } from "../semver"; let __ROOT_PATH: string | undefined = undefined; @@ -61,7 +66,9 @@ async function getCommitMessage(target: string): Promise { * Retrieves a list of commit messages based on the provided target * parameter */ -export async function getCommitMessages(target: string[]): Promise { +export async function getCommitMessages( + target: string[] +): Promise<{ sha: string; body: string }[]> { const git = simpleGit(await getRootPath()); let commitHashes: string[] = []; @@ -76,10 +83,10 @@ export async function getCommitMessages(target: string[]): Promise { commitHashes.push(target[0]); } - const messages: string[] = []; + const messages: { sha: string; body: string }[] = []; for (const hash of commitHashes) { try { - messages.push(await getCommitMessage(hash)); + messages.push({ sha: hash, body: await getCommitMessage(hash) }); } catch (error: unknown) { continue; } @@ -87,3 +94,26 @@ export async function getCommitMessages(target: string[]): Promise { return messages; } + +export function prettyPrintCommitMessage( + commit: ConventionalCommitMessage +): void { + console.log(""); + console.log( + dedent(` + ${Color.RED(`[[---- (Commit ${commit.hexsha}) ----]]`)} + ${Color.GREEN("Type")}: ${commit.type} + ${Color.GREEN("Scope")}: ${commit.scope ?? "None"} + ${Color.GREEN("Breaking Change")}: ${ + commit.breakingChange ? Color.RED("Yes") : Color.GREEN("No") + } + ${Color.GREEN("Bump")}: ${SemVerType[commit.bump]} + ${Color.GREEN("Description")}: ${commit.description} + ${Color.GREEN("Body")}: ${Color.GRAY(commit.body ?? "None")} + ${Color.GREEN("Footers")}: + ${commit.footers + .map(footer => `${footer.token}: ${Color.GRAY(footer.value)}`) + .join(os.EOL)} + `) + ); +}