From 8d847e0c5ff3ed346bc99abdfd8d6a1f9d40b649 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 13 Jun 2025 19:02:53 +0100 Subject: [PATCH] fix: improve changelog generation to use correct commit range - Replace analyzeCommitsForVersionBump() with direct git log approach - Use tail -2 < /dev/null | head -1 to get the previous stable tag correctly - Filter out release commits to avoid circular references - Better commit message parsing for features/fixes/other changes - This should fix the '0 commits' issue in changelogs --- .github/workflows/stable-release.yml | 197 ++++++++++++++++----------- 1 file changed, 114 insertions(+), 83 deletions(-) diff --git a/.github/workflows/stable-release.yml b/.github/workflows/stable-release.yml index b76894e28..02ad148a8 100644 --- a/.github/workflows/stable-release.yml +++ b/.github/workflows/stable-release.yml @@ -187,105 +187,149 @@ jobs: id: changelog env: NEW_VERSION: ${{ needs.detect-stable-release.outputs.suggested-version }} + BUMP_TYPE: ${{ needs.detect-stable-release.outputs.bump-type }} + REASONING: ${{ needs.detect-stable-release.outputs.reasoning }} run: | - # Get the previous stable tag + # Get the previous stable tag (exclude current release tag) PREV_TAG=$(git tag -l "v*" | grep -v "rc\|alpha\|beta" | sort -V | tail -2 | head -1) if [ -z "$PREV_TAG" ]; then PREV_TAG="v0.0.0" fi - echo "šŸ“ Generating changelog from $PREV_TAG to v$NEW_VERSION" + echo "šŸ“ Generating changelog from $PREV_TAG to current HEAD" + echo "šŸ“Š Looking for commits in range: $PREV_TAG..HEAD" - # Analyze commits for changelog + # Use direct git log approach for reliable commit analysis node -e " const { execSync } = require('child_process'); - const { analyzeCommitsForVersionBump } = require('./server/versionUtils'); const fs = require('fs'); try { - console.log('šŸ” Starting changelog generation...'); - const analysis = analyzeCommitsForVersionBump(); - console.log('šŸ“Š Analysis results:', { - currentStable: analysis.currentStableVersion, - suggested: analysis.suggestedVersion, - bumpType: analysis.bumpType, - totalCommits: analysis.totalCommits, - breaking: analysis.analysis.breaking.length, - features: analysis.analysis.features.length, - fixes: analysis.analysis.fixes.length, - other: analysis.analysis.other.length + const prevTag = '$PREV_TAG'; + const newVersion = '$NEW_VERSION'; + const bumpType = '$BUMP_TYPE'; + const reasoning = '$REASONING'; + + console.log('šŸ” Analyzing commits from', prevTag, 'to HEAD'); + + // Get commits since previous stable release + let commits = []; + try { + const gitCmd = prevTag === 'v0.0.0' ? 'git log HEAD --oneline --no-merges' : \`git log \${prevTag}..HEAD --oneline --no-merges\`; + const gitLog = execSync(gitCmd, { encoding: 'utf8' }); + commits = gitLog.trim().split('\\n').filter(line => + line.trim() && + !line.includes('šŸ¤– Generated with') && + !line.includes('chore: release v') + ); + } catch (e) { + console.warn('Could not get git log:', e.message); + commits = []; + } + + console.log('šŸ“Š Found', commits.length, 'commits to analyze'); + + // Analyze commit types + const analysis = { + breaking: [], + features: [], + fixes: [], + other: [] + }; + + commits.forEach(commit => { + const message = commit.replace(/^[a-f0-9]+\\s+/, ''); // Remove hash + console.log('Analyzing:', message); + + if (message.includes('!:') || message.toLowerCase().includes('breaking')) { + analysis.breaking.push(message); + } else if (message.startsWith('feat:') || message.startsWith('feat(')) { + analysis.features.push(message); + } else if (message.startsWith('fix:') || message.startsWith('fix(')) { + analysis.fixes.push(message); + } else { + analysis.other.push(message); + } }); - // Generate meaningful changelog based on actual commits - let changelog = '## What\\'s Changed\\n\\n'; + console.log('šŸ“Š Analysis results:', { + totalCommits: commits.length, + breaking: analysis.breaking.length, + features: analysis.features.length, + fixes: analysis.fixes.length, + other: analysis.other.length + }); + + // Generate meaningful changelog + let changelog = '## What\\\\\\'s Changed\\\\n\\\\n'; // Add version summary - changelog += '**' + analysis.bumpType.charAt(0).toUpperCase() + analysis.bumpType.slice(1) + ' release** with ' + analysis.totalCommits + ' commit' + (analysis.totalCommits !== 1 ? 's' : '') + ' since v' + analysis.currentStableVersion + '\\n\\n'; + changelog += '**' + bumpType.charAt(0).toUpperCase() + bumpType.slice(1) + ' release** with ' + commits.length + ' commit' + (commits.length !== 1 ? 's' : '') + ' since ' + prevTag + '\\\\n\\\\n'; - if (analysis.analysis.breaking.length > 0) { - changelog += '### šŸ’„ Breaking Changes\\n'; - analysis.analysis.breaking.forEach(commit => { - changelog += '- ' + commit.replace(/^(fix|feat|docs|style|refactor|test|chore)!?:\\s*/, '') + '\\n'; + if (analysis.breaking.length > 0) { + changelog += '### šŸ’„ Breaking Changes\\\\n'; + analysis.breaking.forEach(commit => { + changelog += '- ' + commit.replace(/^(fix|feat|docs|style|refactor|test|chore)!?:\\\\s*/, '') + '\\\\n'; }); - changelog += '\\n'; + changelog += '\\\\n'; } - if (analysis.analysis.features.length > 0) { - changelog += '### ✨ New Features\\n'; - analysis.analysis.features.forEach(commit => { - changelog += '- ' + commit.replace(/^feat:\\s*/, '') + '\\n'; + if (analysis.features.length > 0) { + changelog += '### ✨ New Features\\\\n'; + analysis.features.forEach(commit => { + changelog += '- ' + commit.replace(/^feat(\\\\([^)]*\\\\))?:\\\\s*/, '') + '\\\\n'; }); - changelog += '\\n'; + changelog += '\\\\n'; } - if (analysis.analysis.fixes.length > 0) { - changelog += '### šŸ› Bug Fixes\\n'; - analysis.analysis.fixes.forEach(commit => { - changelog += '- ' + commit.replace(/^fix:\\s*/, '') + '\\n'; + if (analysis.fixes.length > 0) { + changelog += '### šŸ› Bug Fixes\\\\n'; + analysis.fixes.forEach(commit => { + changelog += '- ' + commit.replace(/^fix(\\\\([^)]*\\\\))?:\\\\s*/, '') + '\\\\n'; }); - changelog += '\\n'; + changelog += '\\\\n'; } - if (analysis.analysis.other.length > 0) { - changelog += '### šŸ”§ Other Changes\\n'; - analysis.analysis.other.forEach(commit => { - let cleanCommit = commit.replace(/^(docs|style|refactor|test|chore):\\s*/, ''); + if (analysis.other.length > 0) { + changelog += '### šŸ”§ Other Changes\\\\n'; + analysis.other.forEach(commit => { + let cleanCommit = commit.replace(/^(docs|style|refactor|test|chore)(\\\\([^)]*\\\\))?:\\\\s*/, ''); // Skip very technical commits that aren't user-facing - if (!cleanCommit.includes('šŸ¤– Generated with') && !cleanCommit.includes('resolve:') && cleanCommit.length > 10) { - changelog += '- ' + cleanCommit + '\\n'; + if (!cleanCommit.includes('resolve:') && cleanCommit.length > 10) { + changelog += '- ' + cleanCommit + '\\\\n'; } }); - changelog += '\\n'; + changelog += '\\\\n'; } - changelog += '### šŸ“Š Release Information\\n'; - changelog += '- **Release type**: ' + analysis.bumpType + ' (v' + analysis.currentStableVersion + ' → v' + analysis.suggestedVersion + ')\\n'; - changelog += '- **Total changes**: ' + analysis.totalCommits + ' commits\\n'; - changelog += '- **Breaking changes**: ' + analysis.analysis.breaking.length + '\\n'; - changelog += '- **New features**: ' + analysis.analysis.features.length + '\\n'; - changelog += '- **Bug fixes**: ' + analysis.analysis.fixes.length + '\\n'; - changelog += '\\n'; + changelog += '### šŸ“Š Release Information\\\\n'; + changelog += '- **Release type**: ' + bumpType + ' (' + prevTag + ' → v' + newVersion + ')\\\\n'; + changelog += '- **Total changes**: ' + commits.length + ' commits\\\\n'; + changelog += '- **Breaking changes**: ' + analysis.breaking.length + '\\\\n'; + changelog += '- **New features**: ' + analysis.features.length + '\\\\n'; + changelog += '- **Bug fixes**: ' + analysis.fixes.length + '\\\\n'; + changelog += '\\\\n'; - changelog += '### 🐳 Docker\\n'; - changelog += '\\`\\`\\`bash\\n'; - changelog += '# Latest stable release\\n'; - changelog += 'docker pull rcourtman/pulse:v' + analysis.suggestedVersion + '\\n'; - changelog += 'docker pull rcourtman/pulse:latest\\n'; - changelog += '\\`\\`\\`\\n'; - changelog += '\\n'; + changelog += '### 🐳 Docker\\\\n'; + changelog += '\\\\`\\\\`\\\\`bash\\\\n'; + changelog += '# Latest stable release\\\\n'; + changelog += 'docker pull rcourtman/pulse:v' + newVersion + '\\\\n'; + changelog += 'docker pull rcourtman/pulse:latest\\\\n'; + changelog += '\\\\`\\\\`\\\\`\\\\n'; + changelog += '\\\\n'; - changelog += '### šŸ“„ Installation\\n'; - changelog += '\\`\\`\\`bash\\n'; - changelog += '# Automated installer (recommended)\\n'; - changelog += 'curl -sL https://raw.githubusercontent.com/rcourtman/Pulse/main/scripts/install-pulse.sh | bash\\n'; - changelog += '\\n'; - changelog += '# Or download and extract tarball manually\\n'; - changelog += 'wget https://github.com/rcourtman/Pulse/releases/download/v' + analysis.suggestedVersion + '/pulse-v' + analysis.suggestedVersion + '.tar.gz\\n'; - changelog += '\\`\\`\\`\\n'; - changelog += '\\n'; + changelog += '### šŸ“„ Installation\\\\n'; + changelog += '\\\\`\\\\`\\\\`bash\\\\n'; + changelog += '# Automated installer (recommended)\\\\n'; + changelog += 'curl -sL https://raw.githubusercontent.com/rcourtman/Pulse/main/scripts/install-pulse.sh | bash\\\\n'; + changelog += '\\\\n'; + changelog += '# Or download and extract tarball manually\\\\n'; + changelog += 'wget https://github.com/rcourtman/Pulse/releases/download/v' + newVersion + '/pulse-v' + newVersion + '.tar.gz\\\\n'; + changelog += '\\\\`\\\\`\\\\`\\\\n'; + changelog += '\\\\n'; changelog += 'šŸ¤– *This release was automatically generated from the develop branch*'; - console.log('āœ… Generated changelog with', changelog.split('\\n').length, 'lines'); + console.log('āœ… Generated changelog with', changelog.split('\\\\n').length, 'lines'); // Write to file for GitHub release fs.writeFileSync('CHANGELOG.md', changelog); @@ -295,25 +339,12 @@ jobs: console.error('āŒ Error generating changelog:', error); console.error('Stack trace:', error.stack); - // Enhanced fallback with basic git log - let fallbackChangelog = '## Release v$NEW_VERSION\\n\\n'; - try { - const gitLog = execSync('git log $(git describe --tags --abbrev=0)..HEAD --oneline --no-merges', { encoding: 'utf8' }); - if (gitLog.trim()) { - fallbackChangelog += '### Changes since last release:\\n'; - gitLog.trim().split('\\n').forEach(line => { - if (line.trim() && !line.includes('šŸ¤– Generated with')) { - fallbackChangelog += '- ' + line.replace(/^[a-f0-9]+\\s+/, '') + '\\n'; - } - }); - } else { - fallbackChangelog += 'See commit history for details.\\n'; - } - } catch (gitError) { - fallbackChangelog += 'See commit history for details.\\n'; - } - - fallbackChangelog += '\\nšŸ¤– *Automated release - detailed changelog generation failed*'; + // Enhanced fallback + let fallbackChangelog = '## Release v$NEW_VERSION\\\\n\\\\n'; + fallbackChangelog += '**$BUMP_TYPE release** - $REASONING\\\\n\\\\n'; + fallbackChangelog += '### Changes\\\\n'; + fallbackChangelog += 'See commit history for details.\\\\n\\\\n'; + fallbackChangelog += 'šŸ¤– *Automated release - detailed changelog generation failed*'; fs.writeFileSync('CHANGELOG.md', fallbackChangelog); console.log('šŸ“ Fallback changelog written');