mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-21 10:43:36 +00:00
f53550fc6c
- 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>
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
|
|
}; |