mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-22 03:04:03 +00:00
75ba508c76
* feat: dramatically improve automated changelog generation - Add detailed analysis logging and error handling - Clean up commit messages by removing prefixes (feat:, fix:, etc.) - Group changes by type with proper emoji categories - Include version progression info (v3.25.2 → v3.25.3) - Add installation instructions and Docker commands - Filter out technical/automated commits from user-facing changelog - Provide enhanced fallback using git log if analysis fails - Include release statistics and change counts This replaces the generic 'Automated stable release' message with comprehensive, user-friendly changelogs. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * chore: bump version to 3.25.2-rc3 for RC release * fix: improve installer resilience against GitHub API rate limits Add fallback mechanism in check_release_exists() to verify release existence by attempting direct tarball download when GitHub API is rate limited. This prevents installation failures when the API quota is exceeded. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: ensure install script included in releases and fix changelog generation - Fix missing analysis property in versionUtils error fallbacks - Improve tarball creation to always include install-pulse.sh - Add verification step to ensure install script is included in releases - This resolves the issue where releases were missing the installer script 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * chore: bump version to 3.25.2-rc5 for RC release * fix: update package.json version to 3.25.5 for proper RC versioning This ensures new RC releases are versioned ahead of the current stable release (v3.25.4), so they appear at the top of the releases page as expected. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * chore: bump version to 3.25.5-rc1 for RC release * fix: pass tarballAsset parameter to _performUpdate function Fixes 'tarballAsset is not defined' error when applying updates through the UI. The tarballAsset variable was defined in the parent scope but not passed to the _performUpdate function. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * chore: bump version to 3.25.5-rc2 for RC release * docs: add CLAUDE.md with git workflow and release automation notes Documents the RC workflow behavior, common git conflicts, and testing procedures for future development work. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * chore: bump version to 3.25.5-rc3 for RC release * chore: bump version to 3.25.4-rc1 for RC release --------- Co-authored-by: rcourtman <dev@pulse.local> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: GitHub Action <action@github.com>
327 lines
11 KiB
JavaScript
327 lines
11 KiB
JavaScript
/**
|
|
* Centralized version calculation utility
|
|
* Used by both /api/version endpoint and UpdateManager to ensure consistency
|
|
*/
|
|
|
|
const { execSync } = require('child_process');
|
|
const path = require('path');
|
|
|
|
/**
|
|
* Calculate the current version dynamically from git
|
|
* @returns {Object} Version information including version, branch, and isDevelopment
|
|
*/
|
|
function getCurrentVersionInfo() {
|
|
try {
|
|
const packageJson = require('../package.json');
|
|
|
|
let currentVersion = packageJson.version || 'N/A';
|
|
let gitBranch = null;
|
|
let isDevelopment = false;
|
|
|
|
// Try to detect git branch and calculate dynamic version
|
|
try {
|
|
const gitDir = path.join(__dirname, '..');
|
|
|
|
// Get current branch
|
|
gitBranch = execSync('git branch --show-current', {
|
|
cwd: gitDir,
|
|
encoding: 'utf8'
|
|
}).trim();
|
|
|
|
// If on develop branch, calculate RC version from git
|
|
if (gitBranch === 'develop') {
|
|
isDevelopment = true;
|
|
try {
|
|
// Get the latest stable release tag
|
|
const latestStableTag = execSync('git tag -l "v*" | grep -v "rc\\\\|alpha\\\\|beta" | sort -V | tail -1', {
|
|
cwd: gitDir,
|
|
encoding: 'utf8',
|
|
shell: '/bin/bash'
|
|
}).trim();
|
|
|
|
if (latestStableTag) {
|
|
// Remove 'v' prefix to get base version
|
|
const baseVersion = latestStableTag.replace(/^v/, '');
|
|
|
|
// Count commits since the latest stable tag
|
|
const commitsSince = execSync(`git rev-list --count ${latestStableTag}..HEAD`, {
|
|
cwd: gitDir,
|
|
encoding: 'utf8'
|
|
}).trim();
|
|
|
|
const commitsCount = parseInt(commitsSince, 10);
|
|
|
|
if (commitsCount > 0) {
|
|
// Calculate RC version: base version + rc + commit count
|
|
currentVersion = `${baseVersion}-rc${commitsCount}`;
|
|
} else {
|
|
// No commits since stable, use base version
|
|
currentVersion = baseVersion;
|
|
}
|
|
}
|
|
} catch (versionError) {
|
|
console.log('[VersionUtils] Could not calculate RC version from git, using package.json');
|
|
// Fall back to package.json version
|
|
currentVersion = packageJson.version;
|
|
}
|
|
}
|
|
} catch (gitError) {
|
|
// Git not available or not a git repo
|
|
gitBranch = null;
|
|
currentVersion = packageJson.version;
|
|
}
|
|
|
|
return {
|
|
version: currentVersion,
|
|
gitBranch: gitBranch,
|
|
isDevelopment: isDevelopment || gitBranch === 'develop' || process.env.NODE_ENV === 'development'
|
|
};
|
|
} catch (error) {
|
|
console.warn('[VersionUtils] Error getting current version:', error.message);
|
|
const packageJson = require('../package.json');
|
|
return {
|
|
version: packageJson.version || 'N/A',
|
|
gitBranch: null,
|
|
isDevelopment: false
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get just the version string (for backwards compatibility)
|
|
* @returns {string} The current version
|
|
*/
|
|
function getCurrentVersion() {
|
|
return getCurrentVersionInfo().version;
|
|
}
|
|
|
|
/**
|
|
* Analyze commits since the last stable release to suggest version bump
|
|
* @returns {Object} Analysis with suggested version bump and reasoning
|
|
*/
|
|
function analyzeCommitsForVersionBump() {
|
|
try {
|
|
const { execSync } = require('child_process');
|
|
const packageJson = require('../package.json');
|
|
const gitDir = path.join(__dirname, '..');
|
|
|
|
// Get the latest stable release tag (no RC/alpha/beta)
|
|
let latestStableTag;
|
|
try {
|
|
latestStableTag = execSync('git tag -l "v*" | grep -v "rc\\|alpha\\|beta" | sort -V | tail -1', {
|
|
cwd: gitDir,
|
|
encoding: 'utf8',
|
|
shell: '/bin/bash'
|
|
}).trim();
|
|
} catch (error) {
|
|
// No stable tags found, use v0.0.0 as baseline
|
|
latestStableTag = 'v0.0.0';
|
|
}
|
|
|
|
if (!latestStableTag) {
|
|
latestStableTag = 'v0.0.0';
|
|
}
|
|
|
|
// Get commit messages since last stable release
|
|
let commitMessages;
|
|
try {
|
|
commitMessages = execSync(`git log ${latestStableTag}..HEAD --pretty=format:"%s"`, {
|
|
cwd: gitDir,
|
|
encoding: 'utf8'
|
|
}).trim();
|
|
} catch (error) {
|
|
// If git log fails, assume no commits
|
|
commitMessages = '';
|
|
}
|
|
|
|
if (!commitMessages) {
|
|
return {
|
|
currentStableVersion: latestStableTag.replace(/^v/, ''),
|
|
suggestedVersion: packageJson.version,
|
|
bumpType: 'none',
|
|
reasoning: 'No commits since last stable release',
|
|
commits: [],
|
|
analysis: {
|
|
breaking: [],
|
|
features: [],
|
|
fixes: [],
|
|
other: []
|
|
},
|
|
totalCommits: 0
|
|
};
|
|
}
|
|
|
|
const commits = commitMessages.split('\n').filter(msg => msg.trim());
|
|
|
|
// Analyze commit types
|
|
const analysis = {
|
|
breaking: [],
|
|
features: [],
|
|
fixes: [],
|
|
other: []
|
|
};
|
|
|
|
commits.forEach(commit => {
|
|
const msg = commit.toLowerCase();
|
|
|
|
// Check for breaking changes
|
|
if (commit.includes('BREAKING CHANGE') || commit.includes('!:')) {
|
|
analysis.breaking.push(commit);
|
|
}
|
|
// Check for features
|
|
else if (msg.startsWith('feat:') || msg.startsWith('feature:')) {
|
|
analysis.features.push(commit);
|
|
}
|
|
// Check for fixes
|
|
else if (msg.startsWith('fix:') || msg.startsWith('bugfix:')) {
|
|
analysis.fixes.push(commit);
|
|
}
|
|
// Everything else
|
|
else {
|
|
analysis.other.push(commit);
|
|
}
|
|
});
|
|
|
|
// Determine version bump type
|
|
let bumpType = 'patch';
|
|
let reasoning = '';
|
|
|
|
if (analysis.breaking.length > 0) {
|
|
bumpType = 'major';
|
|
reasoning = `Major bump due to ${analysis.breaking.length} breaking change(s)`;
|
|
} else if (analysis.features.length > 0) {
|
|
bumpType = 'minor';
|
|
reasoning = `Minor bump due to ${analysis.features.length} new feature(s)`;
|
|
} else if (analysis.fixes.length > 0) {
|
|
bumpType = 'patch';
|
|
reasoning = `Patch bump due to ${analysis.fixes.length} bug fix(es)`;
|
|
} else {
|
|
bumpType = 'patch';
|
|
reasoning = `Patch bump for ${analysis.other.length} other change(s)`;
|
|
}
|
|
|
|
// Calculate suggested version
|
|
const currentStableVersion = latestStableTag.replace(/^v/, '');
|
|
const suggestedVersion = calculateNextVersion(currentStableVersion, bumpType);
|
|
|
|
return {
|
|
currentStableVersion,
|
|
suggestedVersion,
|
|
bumpType,
|
|
reasoning,
|
|
commits: commits,
|
|
analysis,
|
|
totalCommits: commits.length
|
|
};
|
|
|
|
} catch (error) {
|
|
console.warn('[VersionUtils] Error analyzing commits for version bump:', error.message);
|
|
const packageJson = require('../package.json');
|
|
return {
|
|
currentStableVersion: packageJson.version,
|
|
suggestedVersion: packageJson.version,
|
|
bumpType: 'none',
|
|
reasoning: 'Error analyzing commits',
|
|
commits: [],
|
|
analysis: {
|
|
breaking: [],
|
|
features: [],
|
|
fixes: [],
|
|
other: []
|
|
},
|
|
totalCommits: 0
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculate the next version based on current version and bump type
|
|
* @param {string} currentVersion - Current semantic version (e.g., "3.24.0")
|
|
* @param {string} bumpType - Type of bump: major, minor, or patch
|
|
* @returns {string} Next version
|
|
*/
|
|
function calculateNextVersion(currentVersion, bumpType) {
|
|
try {
|
|
// Parse current version
|
|
const versionMatch = currentVersion.match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
if (!versionMatch) {
|
|
throw new Error(`Invalid version format: ${currentVersion}`);
|
|
}
|
|
|
|
let [, major, minor, patch] = versionMatch.map(Number);
|
|
|
|
switch (bumpType) {
|
|
case 'major':
|
|
major += 1;
|
|
minor = 0;
|
|
patch = 0;
|
|
break;
|
|
case 'minor':
|
|
minor += 1;
|
|
patch = 0;
|
|
break;
|
|
case 'patch':
|
|
patch += 1;
|
|
break;
|
|
default:
|
|
// No bump
|
|
break;
|
|
}
|
|
|
|
return `${major}.${minor}.${patch}`;
|
|
} catch (error) {
|
|
console.warn('[VersionUtils] Error calculating next version:', error.message);
|
|
return currentVersion;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if current branch should trigger a stable release
|
|
* (i.e., we're on main branch and last commit was a merge from develop)
|
|
* @returns {boolean} True if this should trigger a stable release
|
|
*/
|
|
function shouldTriggerStableRelease() {
|
|
try {
|
|
const { execSync } = require('child_process');
|
|
const gitDir = path.join(__dirname, '..');
|
|
|
|
// Check if we're on main branch
|
|
const currentBranch = execSync('git branch --show-current', {
|
|
cwd: gitDir,
|
|
encoding: 'utf8'
|
|
}).trim();
|
|
|
|
if (currentBranch !== 'main') {
|
|
return false;
|
|
}
|
|
|
|
// Check if the last commit was a merge from develop
|
|
try {
|
|
const lastCommitMessage = execSync('git log -1 --pretty=format:"%s"', {
|
|
cwd: gitDir,
|
|
encoding: 'utf8'
|
|
}).trim();
|
|
|
|
// Look for merge commit patterns from develop
|
|
const isMergeFromDevelop = lastCommitMessage.includes('Merge branch \'develop\'') ||
|
|
lastCommitMessage.includes('Merge pull request') ||
|
|
lastCommitMessage.includes('develop');
|
|
|
|
return isMergeFromDevelop;
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
|
|
} catch (error) {
|
|
console.warn('[VersionUtils] Error checking for stable release trigger:', error.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
getCurrentVersionInfo,
|
|
getCurrentVersion,
|
|
analyzeCommitsForVersionBump,
|
|
calculateNextVersion,
|
|
shouldTriggerStableRelease
|
|
}; |