From a139e5eeb44d2e6f5514928b030b9f06bf46c354 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Fri, 13 Jun 2025 16:24:58 +0100 Subject: [PATCH] feat: implement centralized version calculation system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- server/index.js | 65 ++++---------------------- server/updateManager.js | 22 +++++---- server/versionUtils.js | 101 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 122 insertions(+), 66 deletions(-) create mode 100644 server/versionUtils.js diff --git a/server/index.js b/server/index.js index 85fbf1bbc..aa048b4f5 100644 --- a/server/index.js +++ b/server/index.js @@ -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); diff --git a/server/updateManager.js b/server/updateManager.js index 2d4e176f0..c7f0dff7e 100644 --- a/server/updateManager.js +++ b/server/updateManager.js @@ -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) { diff --git a/server/versionUtils.js b/server/versionUtils.js new file mode 100644 index 000000000..28075edfd --- /dev/null +++ b/server/versionUtils.js @@ -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 +}; \ No newline at end of file