import { exec, execSync } from 'child_process'; import { promises as fs } from 'fs'; import chalk from 'chalk'; import ora from 'ora'; const commitTypes: Record = { feat: '✨', fix: '🐛', docs: '📚', style: '🎨', refactor: '🔨', test: '✅', chore: '🛠️', perf: '⚡', ci: '🔧', build: '📦', revert: '⏪', }; const defaultEmoji = '🔖'; async function run(): Promise { const spinner = ora('Running custom commit message check...').start(); try { console.log(chalk.blue('Running custom commit message check...')); console.log(); const commitMsgFile = process.argv[2]; if (!commitMsgFile) { spinner.fail('Error: Commit message file path not provided.'); console.error(chalk.red('Error: Commit message file path not provided.')); process.exit(1); } const commitMsg = (await fs.readFile(commitMsgFile, 'utf8')).trim(); // Check for duplicate commit messages in the last 100 commits const duplicateCommitMsg = execSync(`git log -n 100 --pretty=format:%s`) .toString() .split('\n'); // Extract emojis from commitTypes const emojis = Object.values(commitTypes); // Function to remove an emoji from the start of the string const removeEmoji = (message: string): string => { for (const emoji of emojis) { if (message.startsWith(emoji)) { return message.slice(emoji.length).trim(); } } if (message.startsWith(defaultEmoji)) { return message.slice(defaultEmoji.length).trim(); } return message; }; const cleanMessages = duplicateCommitMsg.map(removeEmoji); if (cleanMessages.includes(commitMsg)) { spinner.fail(chalk.bold.red('Duplicate Commit Detected')); console.log(); console.error( chalk.white.bgRed.bold(' ERROR: ') + chalk.redBright(' A duplicate commit message has been detected.') ); console.log(); console.log( chalk.yellowBright('TIP: ') + chalk.white(' Please use a unique commit message to keep the history clean.') ); console.log(); process.exit(1); } spinner.succeed('Message is not duplicated'); console.log(chalk.green('Message is not duplicated')); console.log(); } catch (err) { spinner.fail('Error running custom commit message check.'); console.error(chalk.red('Error:', err)); process.exit(1); } const spinner2 = ora('Running commitlint...').start(); try { console.log(chalk.blue('Running commitlint...')); console.log(); const commitMsgFile = process.argv[2]; if (!commitMsgFile) { spinner2.fail('Error: Commit message file path not provided.'); console.error(chalk.red('Error: Commit message file path not provided.')); process.exit(1); } const commitMsg = (await fs.readFile(commitMsgFile, 'utf8')).trim(); // Run commitlint exec( `npx commitlint --edit ${commitMsgFile}`, async (error, stdout, stderr) => { if (error) { spinner2.fail('Commitlint check failed.'); console.error(chalk.red(stdout || stderr)); console.error(chalk.red('Commitlint check failed.')); console.log(); console.error( chalk.yellow('Hint: Commit message should follow the Conventional Commits standard.') ); console.error(chalk.yellow('See: https://www.conventionalcommits.org/en/v1.0.0/')); console.error(chalk.yellow('Examples:')); console.error(chalk.yellow(' feat: add a new feature')); console.error(chalk.yellow(' fix: fix a bug')); console.error(chalk.yellow(' docs: update documentation')); process.exit(1); } else { spinner2.succeed('Commitlint check passed.'); console.log(chalk.green('Commitlint check passed.')); console.log(chalk.green(stdout)); // Add emoji to the commit message const commitRegex = /^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?:\s.+/; const match = commitMsg.match(commitRegex); if (match) { const commitType = match[1]; const emoji = commitTypes[commitType] || defaultEmoji; const newCommitMsg = `${emoji} ${commitMsg}`; await fs.writeFile(commitMsgFile, newCommitMsg + '\n', 'utf8'); console.log(chalk.green('Commit message updated with emoji:'), newCommitMsg); } else { const newCommitMsg = `${defaultEmoji} ${commitMsg}`; await fs.writeFile(commitMsgFile, newCommitMsg + '\n', 'utf8'); console.log( chalk.yellow('Commit message did not match expected format, added default emoji:'), newCommitMsg ); } process.exit(0); } } ); } catch (err) { spinner2.fail('Error running commitlint.'); console.error(chalk.red('Error:', err)); process.exit(1); } } run();