UNPKG

@mediamonks/commitlint-issue-reference

Version:

Ensures your commit messages always include the same issue key as in your branch name

153 lines (152 loc) 7.67 kB
/* eslint-disable no-param-reassign */ import * as childProcess from 'node:child_process'; import { readFileSync, existsSync } from 'node:fs'; import { relative } from 'node:path'; import chalk from 'chalk'; import { processFooter } from './processors/processFooter.js'; import { processHeader } from './processors/processHeader.js'; import { findIssueReferencesInCommit } from './utils/findIssueReferencesInCommit.js'; import { getIssuePattern } from './utils/getIssuePattern.js'; import { getTicketFromBranch } from './utils/getTicketFromBranch.js'; import { loadCommitMessage, saveCommitMessage } from './utils/io.js'; import { isIgnored } from './utils/isIgnored.js'; import { getErrorBlock, outputFoundTicketInBranch, outputNoTicketInBranch, } from './utils/outputInfo.js'; function getGitBranch() { const { status, stderr, stdout } = childProcess.spawnSync('git', [ 'rev-parse', '--abbrev-ref', 'HEAD', ], { env: { ...process.env, GIT_DIR: undefined, GIT_WORK_TREE: undefined } }); if (status !== 0) { throw new Error(stderr.toString()); } return stdout.toString().trim(); } export default function processCommitMessage({ file, message, branch, configFile, autoAdd, location, issuePrefix, issueCommitPattern, issueBranchPattern, debug, }) { if (debug) { /* eslint-disable no-console */ console.log(chalk.grey(`[debug] OPTIONS`)); console.log(chalk.grey(`[debug] file: ${file}`)); console.log(chalk.grey(`[debug] message: ${message}`)); console.log(chalk.grey(`[debug] branch: ${branch}`)); console.log(chalk.grey(`[debug] configFile: ${configFile}`)); console.log(chalk.grey(`[debug] autoAdd: ${autoAdd}`)); console.log(chalk.grey(`[debug] location: ${location}`)); console.log(chalk.grey(`[debug] issuePrefix: ${issuePrefix}`)); console.log(chalk.grey(`[debug] issueCommitPattern: ${issueCommitPattern}`)); console.log(chalk.grey(`[debug] issueBranchPattern: ${issueBranchPattern}`)); /* eslint-enable no-console */ } // load config file from disk (package.json), // and get the configuration object from the "commitlintIssueReference" key if (configFile) { const fileDoesExist = existsSync(configFile); const isDefaultPath = relative(process.cwd(), configFile) === 'package.json'; if (!fileDoesExist && !isDefaultPath) { // eslint-disable-next-line no-console console.log(`Error: Config file "${chalk.blue(configFile)}" does not exist. Resolved from "${chalk.blue(process.cwd())}."`); return false; } if (fileDoesExist) { const contents = readFileSync(configFile, 'utf8'); const config = JSON.parse(contents).commitlintIssueReference ?? {}; // set values from config file if they are not explicitly set location ?? (location = config.location); autoAdd ?? (autoAdd = config.autoAdd); issuePrefix ?? (issuePrefix = config.issuePrefix); issueCommitPattern ?? (issueCommitPattern = config.issueCommitPattern); issueBranchPattern ?? (issueBranchPattern = config.issueBranchPattern); debug ?? (debug = config.debug); } } // set default values if they have not been set location ?? (location = 'header'); if (debug) { /* eslint-disable no-console */ console.log(chalk.grey(`[debug] RESOLVED`)); console.log(chalk.grey(`[debug] autoAdd: ${autoAdd}`)); console.log(chalk.grey(`[debug] location: ${location}`)); console.log(chalk.grey(`[debug] issuePrefix: ${issuePrefix}`)); console.log(chalk.grey(`[debug] issueCommitPattern: ${issueCommitPattern}`)); console.log(chalk.grey(`[debug] issueBranchPattern: ${issueBranchPattern}`)); /* eslint-enable no-console */ } // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition if (location !== 'header' && location !== 'footer') { // eslint-disable-next-line no-console console.log(`${getErrorBlock()} ${chalk.blue('location')} must be either ${chalk.blue('header')} or ${chalk.blue('footer')}.`); return false; } if (!message && !file) { // eslint-disable-next-line no-console console.log(`${getErrorBlock()} Either ${chalk.blue('message')} or ${chalk.blue('file')} must be provided, otherwise there is nothing to lint.`); return false; } if (!file) { if (autoAdd) { // eslint-disable-next-line no-console console.log(`${getErrorBlock()} ${chalk.blue('file')} must be provided when using ${chalk.blue('--auto-add')}. Please do not use ${chalk.blue('--auto-add')} or provide a ${chalk.blue('file')}.`); return false; } // explicitly disable autoAdd = false; } // default to true if not set autoAdd ?? (autoAdd = true); if (!issueCommitPattern && issueBranchPattern) { // eslint-disable-next-line no-console console.log(`${getErrorBlock()} ${chalk.blue('issueCommitPattern')} must be provided when using ${chalk.blue('issueBranchPattern')}. If they are equal, use ${chalk.blue('issueCommitPattern')} instead.`); return false; } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion message ?? (message = loadCommitMessage(file)); branch ?? (branch = getGitBranch()); if (debug) { /* eslint-disable no-console */ console.log(chalk.grey(`[debug] -- after --`)); console.log(chalk.grey(`[debug] message: ${message}`)); console.log(chalk.grey(`[debug] branch: ${branch}`)); console.log(chalk.grey(`[debug] autoAdd: ${autoAdd}`)); /* eslint-enable no-console */ } if (isIgnored(message)) { // eslint-disable-next-line no-console console.log('This is an auto-generated commit message, skipping linting.'); return true; } const issuePattern = getIssuePattern(issueCommitPattern ? { commit: issueCommitPattern, branch: issueBranchPattern } : issuePrefix); issuePrefix ?? (issuePrefix = issuePattern.issuePrefix); if (issuePrefix === undefined) { // eslint-disable-next-line no-console console.log(`${getErrorBlock()} ${chalk.blue('issuePrefix')} could not be determined from the different ${chalk.blue('issueCommitPattern')} and ${chalk.blue('issueBranchPattern')}, which could result in incorrect matching. Please provide ${chalk.blue('issuePrefix')} explicitly. The ${chalk.blue('issuePrefix')} should be defined as value being added to the beginning of the issue reference after it has been fetched from the branch name.`); return false; } let ticketInBranch = getTicketFromBranch(branch, issuePattern.branchPattern); if (ticketInBranch && issuePrefix && !ticketInBranch.startsWith(issuePrefix)) { ticketInBranch = `${issuePrefix}${ticketInBranch}`; } if (!ticketInBranch) { outputNoTicketInBranch(); return true; } outputFoundTicketInBranch(ticketInBranch); const issuesInCommit = findIssueReferencesInCommit(message, ticketInBranch, issuePattern.commitPattern, location); const updateMessage = (newMessage) => { if (file) { saveCommitMessage(newMessage, file); } }; if (location === 'header') { return processHeader(ticketInBranch, issuesInCommit, message, updateMessage, autoAdd); } return processFooter(ticketInBranch, issuesInCommit, message, updateMessage, autoAdd); }