Files
pulse/versionUtils.js
T
rcourtman f53550fc6c fix: simplify frontend update channel switching for better UX
- Remove confusing "preview mode" - channels switch and save immediately
- Eliminate complex localStorage channel persistence logic
- Reduce update reload time from 10+ seconds to 3 seconds
- Remove redundant "Switch to X & Update" buttons
- Make channel switching instant with immediate feedback

Users can now simply change the dropdown to switch channels instantly,
then click Apply Update for a fast 3-second reload experience.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-06-14 11:01:03 +01:00

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
};