git-aiflow
Version:
🚀 An AI-powered workflow automation tool for effortless Git-based development, combining smart GitLab/GitHub merge & pull request creation with Conan package management.
224 lines • 8.86 kB
JavaScript
import updateNotifier from 'update-notifier';
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { Shell } from '../shell.js';
import { logger } from '../logger.js';
import semverCompare from 'semver-compare';
/**
* Update checker utility for managing version checks and automatic updates
*/
export class UpdateChecker {
constructor() {
this.shell = Shell.instance();
this.checkIntervalMs = 120 * 60 * 1000; // 2 hours
// Try multiple possible locations for package.json
const currentFilePath = fileURLToPath(import.meta.url);
const currentDir = dirname(currentFilePath);
const possiblePaths = [
// When running from built dist directory
join(currentDir, '..', '..', 'package.json'),
join(currentDir, '..', 'package.json'),
// When running from source
join(currentDir, '..', '..', 'package.json'),
// When running from global installation
join(currentDir, 'package.json'),
// Alternative paths for different installation scenarios
join(dirname(dirname(currentDir)), 'package.json')
];
let packageJsonPath = '';
for (const path of possiblePaths) {
if (existsSync(path)) {
packageJsonPath = path;
break;
}
}
if (!packageJsonPath) {
throw new Error('Could not locate package.json file');
}
try {
this.packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
}
catch (error) {
throw new Error(`Failed to read package.json from ${packageJsonPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
// Set cache file path in user's home directory
const homeDir = process.env.HOME || process.env.USERPROFILE || '';
const cacheDir = join(homeDir, '.aiflow');
this.cacheFilePath = join(cacheDir, 'update-cache.json');
// Ensure cache directory exists
if (!existsSync(cacheDir)) {
mkdirSync(cacheDir, { recursive: true });
}
}
/**
* Check if the current installation is a global npm installation
*/
isGlobalInstallation() {
try {
// Check if the script is running from a global npm installation
const scriptPath = process.argv[1];
// Common patterns for global npm installations
const globalPatterns = [
'/usr/local/lib/node_modules/',
'/usr/lib/node_modules/',
'\\AppData\\Roaming\\npm\\node_modules\\',
'\\Program Files\\nodejs\\node_modules\\',
'/opt/homebrew/lib/node_modules/',
'/.nvm/versions/node/',
'/.volta/tools/image/node/',
'/node_modules/.bin/',
'\\node_modules\\.bin\\'
];
const isGlobal = globalPatterns.some(pattern => scriptPath.includes(pattern) || scriptPath.includes(pattern.replace(/\//g, '\\')));
if (isGlobal) {
return true;
}
return false;
}
catch (error) {
logger.warn('⚠️ Failed to detect installation type:', error instanceof Error ? error.message : 'Unknown error');
return false;
}
}
/**
* Load update cache from file
*/
loadCache() {
try {
if (existsSync(this.cacheFilePath)) {
const cacheData = readFileSync(this.cacheFilePath, 'utf8');
return JSON.parse(cacheData);
}
}
catch (error) {
logger.warn('⚠️ Failed to load update cache:', error instanceof Error ? error.message : 'Unknown error');
}
return { lastCheckTime: 0 };
}
/**
* Save update cache to file
*/
saveCache(cache) {
try {
writeFileSync(this.cacheFilePath, JSON.stringify(cache, null, 2), 'utf8');
}
catch (error) {
logger.warn('⚠️ Failed to save update cache:', error instanceof Error ? error.message : 'Unknown error');
}
}
/**
* Check if it's time to check for updates
*/
shouldCheckForUpdates() {
const cache = this.loadCache();
const now = Date.now();
return (now - cache.lastCheckTime) >= this.checkIntervalMs;
}
/**
* Get the correct npm command for the current platform
*/
getNpmCommand() {
const platform = process.platform;
if (platform === 'win32') {
// On Windows, try npm.cmd first, then npm.exe
return 'npm.cmd';
}
return 'npm';
}
/**
* Perform automatic update installation
*/
async performAutoUpdate(latestVersion) {
try {
logger.info(`🔄 Updating from ${this.packageJson.version} to ${latestVersion}...`);
const npmCommand = this.getNpmCommand();
// Use runWithExitCode for better error handling
const result = this.shell.runWithExitCode(npmCommand, 'install', '-g', 'git-aiflow@latest');
// Check if the command was successful based on exit code
if (result.success && result.exitCode === 0) {
logger.info(`✅ Successfully updated to git-aiflow@${latestVersion}`);
logger.info('🔄 Please restart the command to use the new version.');
return true;
}
else {
logger.error(`❌ Failed to update git-aiflow (exit code: ${result.exitCode}):`, result.output);
return false;
}
}
catch (error) {
logger.error(`❌ Error during auto-update:`, error instanceof Error ? error.message : 'Unknown error');
return false;
}
}
/**
* Check for updates and automatically update if available
*/
async checkAndUpdate() {
try {
// Only check if this is a global installation
if (!this.isGlobalInstallation()) {
logger.info('🔄 Skipping update check (not a global installation)');
return;
}
logger.info('🔄 Checking for updates...');
// Only check if enough time has passed since last check
if (!this.shouldCheckForUpdates()) {
logger.info('🔄 Skipping update check (not enough time has passed since last check)');
return;
}
// Use update-notifier to check for updates
const notifier = updateNotifier({
pkg: this.packageJson,
updateCheckInterval: 0 // We handle the interval ourselves
});
// Update the cache with current check time
const cache = this.loadCache();
cache.lastCheckTime = Date.now();
if (notifier.update && semverCompare(notifier.update.latest, this.packageJson.version) === 1) {
logger.info(`📦 Update available: ${this.packageJson.version} → ${notifier.update.latest}`);
// Store the latest version in cache
cache.lastVersion = notifier.update.latest;
this.saveCache(cache);
// Perform automatic update
const updateSuccess = await this.performAutoUpdate(notifier.update.latest);
if (updateSuccess) {
// Exit the process after successful update so user can restart with new version
logger.info('🚀 Update completed! Please run the command again.');
return;
}
}
else {
// No update available, just save the cache
this.saveCache(cache);
}
}
catch (error) {
logger.warn('⚠️ Update check failed:', error instanceof Error ? error.message : 'Unknown error');
// Still update the cache to avoid repeated failed checks
const cache = this.loadCache();
cache.lastCheckTime = Date.now();
this.saveCache(cache);
}
}
/**
* Force check for updates (ignoring cache)
*/
async forceCheckAndUpdate() {
// Clear cache to force check
const cache = this.loadCache();
cache.lastCheckTime = 0;
this.saveCache(cache);
await this.checkAndUpdate();
}
/**
* Get current version info
*/
getVersionInfo() {
return {
name: this.packageJson.name,
version: this.packageJson.version
};
}
}
//# sourceMappingURL=update-checker.js.map