feat: implement centralized version calculation system

- Create versionUtils.js for unified version logic across components
- Update UpdateManager to use centralized getCurrentVersion()
- Update /api/version endpoint to use centralized logic
- Ensures consistent version calculation between update checks and version display
- Prevents version fragmentation between different system components

This resolves the issue where Settings modal showed inconsistent RC versions
by ensuring both the current version display and update checking use the
same dynamic git-based calculation.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
rcourtman
2025-06-13 16:24:58 +01:00
parent 4512a5b75a
commit a139e5eeb4
3 changed files with 122 additions and 66 deletions
+8 -57
View File
@@ -729,65 +729,16 @@ app.get('/api/alerts/status', (req, res) => {
// Version API endpoint
app.get('/api/version', async (req, res) => {
try {
const { execSync } = require('child_process');
const packageJson = require('../package.json');
const { getCurrentVersionInfo } = require('./versionUtils');
// Get version info using centralized logic
const versionInfo = getCurrentVersionInfo();
const currentVersion = versionInfo.version;
const gitBranch = versionInfo.gitBranch;
const isDevelopment = versionInfo.isDevelopment;
let currentVersion = packageJson.version || 'N/A';
let latestVersion = currentVersion;
let updateAvailable = false;
let gitBranch = null;
// 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') {
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("[Version API] 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;
}
try {
// Try to check for updates, but don't fail if it doesn't work
@@ -804,7 +755,7 @@ app.get('/api/version', async (req, res) => {
latestVersion: latestVersion,
updateAvailable: updateAvailable,
gitBranch: gitBranch,
isDevelopment: gitBranch === 'develop' || process.env.NODE_ENV === 'development'
isDevelopment: isDevelopment
});
} catch (error) {
console.error("[Version API] Error in version endpoint:", error);
+13 -9
View File
@@ -5,6 +5,7 @@ const path = require('path');
const { exec, spawn } = require('child_process');
const { promisify } = require('util');
const { getUpdateChannelPreference } = require('./configLoader');
const { getCurrentVersion } = require('./versionUtils');
const execAsync = promisify(exec);
class UpdateManager {
@@ -64,6 +65,9 @@ class UpdateManager {
try {
console.log('[UpdateManager] Checking for updates...');
// Get the current version using centralized logic
const dynamicCurrentVersion = getCurrentVersion();
// Use override channel if provided and valid, otherwise use config
const configChannel = getUpdateChannelPreference();
const updateChannel = (channelOverride && ['stable', 'rc'].includes(channelOverride))
@@ -125,8 +129,8 @@ class UpdateManager {
if (!latestRelease) {
// No newer RC version found
const updateInfo = {
currentVersion: this.currentVersion,
latestVersion: this.currentVersion,
currentVersion: dynamicCurrentVersion,
latestVersion: dynamicCurrentVersion,
updateAvailable: false,
isDocker: this.isDockerEnvironment(),
releaseNotes: 'No newer RC version available',
@@ -135,7 +139,7 @@ class UpdateManager {
assets: [],
updateChannel: channelDescription
};
console.log(`[UpdateManager] No RC updates available: ${this.currentVersion}`);
console.log(`[UpdateManager] No RC updates available: ${dynamicCurrentVersion}`);
return updateInfo;
}
@@ -145,9 +149,9 @@ class UpdateManager {
const latestVersion = response.data.tag_name.replace('v', '');
// For stable channel, also consider "downgrade" from RC as an update
const isCurrentRC = this.isReleaseCandidate();
const isCurrentRC = this.isReleaseCandidate(dynamicCurrentVersion);
const isStableChannel = updateChannel === 'stable';
const isDifferentVersion = latestVersion !== this.currentVersion;
const isDifferentVersion = latestVersion !== dynamicCurrentVersion;
let updateAvailable;
if (isStableChannel && isCurrentRC && isDifferentVersion) {
@@ -155,14 +159,14 @@ class UpdateManager {
updateAvailable = true;
} else if (updateChannel === 'rc') {
// For RC channel, show update if versions differ or if latest is newer
updateAvailable = isDifferentVersion || semver.gt(latestVersion, this.currentVersion);
updateAvailable = isDifferentVersion || semver.gt(latestVersion, dynamicCurrentVersion);
} else {
// Normal case: only newer versions
updateAvailable = semver.gt(latestVersion, this.currentVersion);
updateAvailable = semver.gt(latestVersion, dynamicCurrentVersion);
}
const updateInfo = {
currentVersion: this.currentVersion,
currentVersion: dynamicCurrentVersion,
latestVersion,
updateAvailable,
isDocker: this.isDockerEnvironment(),
@@ -177,7 +181,7 @@ class UpdateManager {
}))
};
console.log(`[UpdateManager] Current version: ${this.currentVersion}, Latest version: ${latestVersion}, Channel: ${channelDescription}, Docker: ${updateInfo.isDocker}`);
console.log(`[UpdateManager] Current version: ${dynamicCurrentVersion}, Latest version: ${latestVersion}, Channel: ${channelDescription}, Docker: ${updateInfo.isDocker}`);
return updateInfo;
} catch (error) {
+101
View File
@@ -0,0 +1,101 @@
/**
* 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;
}
module.exports = {
getCurrentVersionInfo,
getCurrentVersion
};