// this file contains code to create commits in a consistent way import * as plugins from './mod.plugins.js'; import * as paths from '../paths.js'; import { logger } from '../gitzone.logging.js'; import * as helpers from './mod.helpers.js'; import * as ui from './mod.ui.js'; export const run = async (argvArg: any) => { if (argvArg.format) { const formatMod = await import('../mod_format/index.js'); await formatMod.run(); } ui.printHeader('🔍 Analyzing repository changes...'); const aidoc = new plugins.tsdoc.AiDoc(); await aidoc.start(); const nextCommitObject = await aidoc.buildNextCommitObject(paths.cwd); await aidoc.stop(); ui.printRecommendation({ recommendedNextVersion: nextCommitObject.recommendedNextVersion, recommendedNextVersionLevel: nextCommitObject.recommendedNextVersionLevel, recommendedNextVersionScope: nextCommitObject.recommendedNextVersionScope, recommendedNextVersionMessage: nextCommitObject.recommendedNextVersionMessage, }); const commitInteract = new plugins.smartinteract.SmartInteract(); commitInteract.addQuestions([ { type: 'list', name: `commitType`, message: `Choose TYPE of the commit:`, choices: [`fix`, `feat`, `BREAKING CHANGE`], default: nextCommitObject.recommendedNextVersionLevel, }, { type: 'input', name: `commitScope`, message: `What is the SCOPE of the commit:`, default: nextCommitObject.recommendedNextVersionScope, }, { type: `input`, name: `commitDescription`, message: `What is the DESCRIPTION of the commit?`, default: nextCommitObject.recommendedNextVersionMessage, }, { type: 'confirm', name: `pushToOrigin`, message: `Do you want to push this version now?`, default: true, }, ]); const answerBucket = await commitInteract.runQueue(); const commitString = createCommitStringFromAnswerBucket(answerBucket); const commitVersionType = (() => { switch (answerBucket.getAnswerFor('commitType')) { case 'fix': return 'patch'; case 'feat': return 'minor'; case 'BREAKING CHANGE': return 'major'; } })(); ui.printHeader('✨ Creating Semantic Commit'); ui.printCommitMessage(commitString); const smartshellInstance = new plugins.smartshell.Smartshell({ executor: 'bash', sourceFilePaths: [], }); // Determine total steps (6 if pushing, 5 if not) const totalSteps = answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true') ? 6 : 5; let currentStep = 0; // Step 1: Baking commitinfo currentStep++; ui.printStep(currentStep, totalSteps, '🔧 Baking commit info into code', 'in-progress'); const commitInfo = new plugins.commitinfo.CommitInfo( paths.cwd, commitVersionType, ); await commitInfo.writeIntoPotentialDirs(); ui.printStep(currentStep, totalSteps, '🔧 Baking commit info into code', 'done'); // Step 2: Writing changelog currentStep++; ui.printStep(currentStep, totalSteps, '📄 Generating changelog.md', 'in-progress'); let changelog = nextCommitObject.changelog; changelog = changelog.replaceAll( '{{nextVersion}}', (await commitInfo.getNextPlannedVersion()).versionString, ); changelog = changelog.replaceAll( '{{nextVersionScope}}', `${await answerBucket.getAnswerFor('commitType')}(${await answerBucket.getAnswerFor('commitScope')})`, ); changelog = changelog.replaceAll( '{{nextVersionMessage}}', nextCommitObject.recommendedNextVersionMessage, ); if (nextCommitObject.recommendedNextVersionDetails?.length > 0) { changelog = changelog.replaceAll( '{{nextVersionDetails}}', '- ' + nextCommitObject.recommendedNextVersionDetails.join('\n- '), ); } else { changelog = changelog.replaceAll('\n{{nextVersionDetails}}', ''); } await plugins.smartfile.memory.toFs( changelog, plugins.path.join(paths.cwd, `changelog.md`), ); ui.printStep(currentStep, totalSteps, '📄 Generating changelog.md', 'done'); // Step 3: Staging files currentStep++; ui.printStep(currentStep, totalSteps, '📦 Staging files', 'in-progress'); await smartshellInstance.exec(`git add -A`); ui.printStep(currentStep, totalSteps, '📦 Staging files', 'done'); // Step 4: Creating commit currentStep++; ui.printStep(currentStep, totalSteps, '💾 Creating git commit', 'in-progress'); await smartshellInstance.exec(`git commit -m "${commitString}"`); ui.printStep(currentStep, totalSteps, '💾 Creating git commit', 'done'); // Step 5: Bumping version currentStep++; const projectType = await helpers.detectProjectType(); const newVersion = await helpers.bumpProjectVersion(projectType, commitVersionType, currentStep, totalSteps); // Step 6: Push to remote (optional) const currentBranch = await helpers.detectCurrentBranch(); if ( answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true') ) { currentStep++; ui.printStep(currentStep, totalSteps, `🚀 Pushing to origin/${currentBranch}`, 'in-progress'); await smartshellInstance.exec(`git push origin ${currentBranch} --follow-tags`); ui.printStep(currentStep, totalSteps, `🚀 Pushing to origin/${currentBranch}`, 'done'); } console.log(''); // Add spacing before summary // Get commit SHA for summary const commitShaResult = await smartshellInstance.exec('git rev-parse --short HEAD'); const commitSha = commitShaResult.stdout.trim(); // Print final summary ui.printSummary({ projectType, branch: currentBranch, commitType: answerBucket.getAnswerFor('commitType'), commitScope: answerBucket.getAnswerFor('commitScope'), commitMessage: answerBucket.getAnswerFor('commitDescription'), newVersion: newVersion, commitSha: commitSha, pushed: answerBucket.getAnswerFor('pushToOrigin') && !(process.env.CI === 'true'), }); }; const createCommitStringFromAnswerBucket = ( answerBucket: plugins.smartinteract.AnswerBucket, ) => { const commitType = answerBucket.getAnswerFor('commitType'); const commitScope = answerBucket.getAnswerFor('commitScope'); const commitDescription = answerBucket.getAnswerFor('commitDescription'); return `${commitType}(${commitScope}): ${commitDescription}`; };