mirror of
https://github.com/SuperClaude-Org/SuperClaude_Framework.git
synced 2025-12-17 09:46:06 +00:00
✨ Add automatic update checking for PyPI and NPM packages
- Check for updates on startup (once per 24h) - Show update banner when new version available - Support --no-update-check and --auto-update flags - Add SUPERCLAUDE_AUTO_UPDATE environment variable - Implement for both Python (PyPI) and Node.js (NPM)
This commit is contained in:
parent
06ee059c0b
commit
291b8a0c2b
@ -73,6 +73,10 @@ def create_global_parser() -> argparse.ArgumentParser:
|
||||
help="Force execution, skipping checks")
|
||||
global_parser.add_argument("--yes", "-y", action="store_true",
|
||||
help="Automatically answer yes to all prompts")
|
||||
global_parser.add_argument("--no-update-check", action="store_true",
|
||||
help="Skip checking for updates")
|
||||
global_parser.add_argument("--auto-update", action="store_true",
|
||||
help="Automatically install updates without prompting")
|
||||
|
||||
return global_parser
|
||||
|
||||
@ -199,6 +203,26 @@ def main() -> int:
|
||||
operations = register_operation_parsers(subparsers, global_parser)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Check for updates unless disabled
|
||||
if not args.quiet and not getattr(args, 'no_update_check', False):
|
||||
try:
|
||||
from setup.utils.updater import check_for_updates
|
||||
# Check for updates in the background
|
||||
updated = check_for_updates(
|
||||
current_version="4.0.6",
|
||||
auto_update=getattr(args, 'auto_update', False)
|
||||
)
|
||||
# If updated, suggest restart
|
||||
if updated:
|
||||
print("\n🔄 SuperClaude was updated. Please restart to use the new version.")
|
||||
return 0
|
||||
except ImportError:
|
||||
# Updater module not available, skip silently
|
||||
pass
|
||||
except Exception:
|
||||
# Any other error, skip silently
|
||||
pass
|
||||
|
||||
# No operation provided? Show help manually unless in quiet mode
|
||||
if not args.operation:
|
||||
if not args.quiet:
|
||||
|
||||
276
bin/checkUpdate.js
Normal file
276
bin/checkUpdate.js
Normal file
@ -0,0 +1,276 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Auto-update checker for SuperClaude NPM package
|
||||
* Checks npm registry for newer versions and offers automatic updates
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { spawnSync } = require('child_process');
|
||||
const https = require('https');
|
||||
|
||||
const CACHE_FILE = path.join(process.env.HOME || process.env.USERPROFILE, '.claude', '.npm_update_check');
|
||||
const CHECK_INTERVAL = 86400000; // 24 hours in milliseconds
|
||||
const TIMEOUT = 2000; // 2 seconds
|
||||
const PACKAGE_NAME = '@bifrost_inc/superclaude';
|
||||
|
||||
/**
|
||||
* Get the current package version from package.json
|
||||
*/
|
||||
function getCurrentVersion() {
|
||||
try {
|
||||
const packagePath = path.join(__dirname, '..', 'package.json');
|
||||
const packageData = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
||||
return packageData.version;
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we should perform an update check based on last check time
|
||||
*/
|
||||
function shouldCheckUpdate(force = false) {
|
||||
if (force) return true;
|
||||
|
||||
try {
|
||||
if (!fs.existsSync(CACHE_FILE)) return true;
|
||||
|
||||
const data = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
|
||||
const lastCheck = data.lastCheck || 0;
|
||||
|
||||
// Check if 24 hours have passed
|
||||
return Date.now() - lastCheck > CHECK_INTERVAL;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current timestamp as last check time
|
||||
*/
|
||||
function saveCheckTimestamp() {
|
||||
const cacheDir = path.dirname(CACHE_FILE);
|
||||
|
||||
// Create directory if it doesn't exist
|
||||
if (!fs.existsSync(cacheDir)) {
|
||||
fs.mkdirSync(cacheDir, { recursive: true });
|
||||
}
|
||||
|
||||
let data = {};
|
||||
try {
|
||||
if (fs.existsSync(CACHE_FILE)) {
|
||||
data = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'));
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
|
||||
data.lastCheck = Date.now();
|
||||
fs.writeFileSync(CACHE_FILE, JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Query npm registry for the latest version
|
||||
*/
|
||||
function getLatestVersion() {
|
||||
return new Promise((resolve) => {
|
||||
const options = {
|
||||
hostname: 'registry.npmjs.org',
|
||||
path: `/${PACKAGE_NAME}/latest`,
|
||||
method: 'GET',
|
||||
timeout: TIMEOUT,
|
||||
headers: {
|
||||
'User-Agent': 'SuperClaude-Updater'
|
||||
}
|
||||
};
|
||||
|
||||
const req = https.request(options, (res) => {
|
||||
let data = '';
|
||||
|
||||
res.on('data', (chunk) => {
|
||||
data += chunk;
|
||||
});
|
||||
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const packageData = JSON.parse(data);
|
||||
resolve(packageData.version);
|
||||
} catch {
|
||||
resolve(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', () => resolve(null));
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
resolve(null);
|
||||
});
|
||||
|
||||
req.setTimeout(TIMEOUT);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare version strings
|
||||
*/
|
||||
function isNewerVersion(current, latest) {
|
||||
if (!current || !latest) return false;
|
||||
|
||||
const currentParts = current.split('.').map(Number);
|
||||
const latestParts = latest.split('.').map(Number);
|
||||
|
||||
for (let i = 0; i < Math.max(currentParts.length, latestParts.length); i++) {
|
||||
const currentPart = currentParts[i] || 0;
|
||||
const latestPart = latestParts[i] || 0;
|
||||
|
||||
if (latestPart > currentPart) return true;
|
||||
if (latestPart < currentPart) return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if npm or yarn is being used globally
|
||||
*/
|
||||
function detectPackageManager() {
|
||||
// Check if installed globally with npm
|
||||
const npmResult = spawnSync('npm', ['list', '-g', PACKAGE_NAME], {
|
||||
encoding: 'utf8',
|
||||
shell: true
|
||||
});
|
||||
|
||||
if (npmResult.status === 0 && npmResult.stdout.includes(PACKAGE_NAME)) {
|
||||
return 'npm';
|
||||
}
|
||||
|
||||
// Check if installed globally with yarn
|
||||
const yarnResult = spawnSync('yarn', ['global', 'list'], {
|
||||
encoding: 'utf8',
|
||||
shell: true
|
||||
});
|
||||
|
||||
if (yarnResult.status === 0 && yarnResult.stdout.includes(PACKAGE_NAME)) {
|
||||
return 'yarn';
|
||||
}
|
||||
|
||||
return 'npm'; // Default to npm
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate update command
|
||||
*/
|
||||
function getUpdateCommand() {
|
||||
const pm = detectPackageManager();
|
||||
|
||||
if (pm === 'yarn') {
|
||||
return `yarn global upgrade ${PACKAGE_NAME}`;
|
||||
}
|
||||
|
||||
return `npm update -g ${PACKAGE_NAME}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show update banner
|
||||
*/
|
||||
function showUpdateBanner(currentVersion, latestVersion, autoUpdate = false) {
|
||||
const updateCmd = getUpdateCommand();
|
||||
|
||||
console.log('\n\x1b[36m╔════════════════════════════════════════════════╗\x1b[0m');
|
||||
console.log(`\x1b[36m║\x1b[33m 🚀 Update Available: ${currentVersion} → ${latestVersion} \x1b[36m║\x1b[0m`);
|
||||
console.log(`\x1b[36m║\x1b[32m Run: ${updateCmd.padEnd(30)} \x1b[36m║\x1b[0m`);
|
||||
console.log('\x1b[36m╚════════════════════════════════════════════════╝\x1b[0m\n');
|
||||
|
||||
return autoUpdate || process.env.SUPERCLAUDE_AUTO_UPDATE === 'true';
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the update
|
||||
*/
|
||||
function performUpdate() {
|
||||
const updateCmd = getUpdateCommand();
|
||||
console.log('\x1b[36m🔄 Updating SuperClaude...\x1b[0m');
|
||||
|
||||
const cmdParts = updateCmd.split(' ');
|
||||
const result = spawnSync(cmdParts[0], cmdParts.slice(1), {
|
||||
stdio: 'inherit',
|
||||
shell: true
|
||||
});
|
||||
|
||||
if (result.status === 0) {
|
||||
console.log('\x1b[32m✅ Update completed successfully!\x1b[0m');
|
||||
console.log('\x1b[33mPlease restart SuperClaude to use the new version.\x1b[0m');
|
||||
return true;
|
||||
} else {
|
||||
console.log('\x1b[33m⚠️ Update failed. Please run manually:\x1b[0m');
|
||||
console.log(` ${updateCmd}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Main function to check and notify for updates
|
||||
*/
|
||||
async function checkAndNotify(options = {}) {
|
||||
const { force = false, autoUpdate = false, silent = false } = options;
|
||||
|
||||
// Check environment variables
|
||||
if (process.env.SUPERCLAUDE_NO_UPDATE_CHECK === 'true') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if enough time has passed
|
||||
if (!shouldCheckUpdate(force)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get current version
|
||||
const currentVersion = getCurrentVersion();
|
||||
if (!currentVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get latest version
|
||||
const latestVersion = await getLatestVersion();
|
||||
if (!latestVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save timestamp
|
||||
saveCheckTimestamp();
|
||||
|
||||
// Compare versions
|
||||
if (!isNewerVersion(currentVersion, latestVersion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Show banner unless silent
|
||||
if (!silent) {
|
||||
const shouldUpdate = showUpdateBanner(currentVersion, latestVersion, autoUpdate);
|
||||
|
||||
if (shouldUpdate) {
|
||||
return performUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Export functions for use in other modules
|
||||
module.exports = {
|
||||
checkAndNotify,
|
||||
getCurrentVersion,
|
||||
getLatestVersion,
|
||||
isNewerVersion
|
||||
};
|
||||
|
||||
// If run directly, perform check
|
||||
if (require.main === module) {
|
||||
checkAndNotify({
|
||||
force: process.argv.includes('--force'),
|
||||
autoUpdate: process.argv.includes('--auto-update')
|
||||
});
|
||||
}
|
||||
22
bin/cli.js
22
bin/cli.js
@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
const { spawnSync } = require("child_process");
|
||||
const { detectPython, detectPip } = require("./checkEnv");
|
||||
const { checkAndNotify } = require("./checkUpdate");
|
||||
|
||||
let pythonCmd = detectPython();
|
||||
if (!pythonCmd) {
|
||||
@ -10,12 +11,33 @@ if (!pythonCmd) {
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
// Parse command line arguments for update control
|
||||
const noUpdateCheck = args.includes('--no-update-check');
|
||||
const autoUpdate = args.includes('--auto-update');
|
||||
const isQuiet = args.includes('--quiet') || args.includes('-q');
|
||||
|
||||
// Special case: update command
|
||||
if (args[0] === "update") {
|
||||
require("./update");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Check for updates unless disabled
|
||||
if (!noUpdateCheck && !isQuiet) {
|
||||
// Run update check asynchronously to avoid blocking
|
||||
checkAndNotify({
|
||||
autoUpdate: autoUpdate,
|
||||
silent: false
|
||||
}).then(updated => {
|
||||
if (updated) {
|
||||
console.log("\n🔄 SuperClaude was updated. Please restart to use the new version.");
|
||||
process.exit(0);
|
||||
}
|
||||
}).catch(() => {
|
||||
// Silently ignore update check errors
|
||||
});
|
||||
}
|
||||
|
||||
// Forward everything to Python SuperClaude
|
||||
const result = spawnSync(pythonCmd, ["-m", "SuperClaude", ...args], { stdio: "inherit", shell: true });
|
||||
process.exit(result.status);
|
||||
|
||||
310
setup/utils/updater.py
Normal file
310
setup/utils/updater.py
Normal file
@ -0,0 +1,310 @@
|
||||
"""
|
||||
Auto-update checker for SuperClaude Framework
|
||||
Checks PyPI for newer versions and offers automatic updates
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import time
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Optional, Tuple
|
||||
from packaging import version
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from .ui import display_info, display_warning, display_success, Colors
|
||||
from .logger import get_logger
|
||||
|
||||
|
||||
class UpdateChecker:
|
||||
"""Handles automatic update checking for SuperClaude"""
|
||||
|
||||
PYPI_URL = "https://pypi.org/pypi/SuperClaude/json"
|
||||
CACHE_FILE = Path.home() / ".claude" / ".update_check"
|
||||
CHECK_INTERVAL = 86400 # 24 hours in seconds
|
||||
TIMEOUT = 2 # seconds
|
||||
|
||||
def __init__(self, current_version: str):
|
||||
"""
|
||||
Initialize update checker
|
||||
|
||||
Args:
|
||||
current_version: Current installed version
|
||||
"""
|
||||
self.current_version = current_version
|
||||
self.logger = get_logger()
|
||||
|
||||
def should_check_update(self, force: bool = False) -> bool:
|
||||
"""
|
||||
Determine if we should check for updates based on last check time
|
||||
|
||||
Args:
|
||||
force: Force check regardless of last check time
|
||||
|
||||
Returns:
|
||||
True if update check should be performed
|
||||
"""
|
||||
if force:
|
||||
return True
|
||||
|
||||
if not self.CACHE_FILE.exists():
|
||||
return True
|
||||
|
||||
try:
|
||||
with open(self.CACHE_FILE, 'r') as f:
|
||||
data = json.load(f)
|
||||
last_check = data.get('last_check', 0)
|
||||
|
||||
# Check if 24 hours have passed
|
||||
if time.time() - last_check > self.CHECK_INTERVAL:
|
||||
return True
|
||||
|
||||
except (json.JSONDecodeError, KeyError):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def save_check_timestamp(self):
|
||||
"""Save the current timestamp as last check time"""
|
||||
self.CACHE_FILE.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
data = {}
|
||||
if self.CACHE_FILE.exists():
|
||||
try:
|
||||
with open(self.CACHE_FILE, 'r') as f:
|
||||
data = json.load(f)
|
||||
except:
|
||||
pass
|
||||
|
||||
data['last_check'] = time.time()
|
||||
|
||||
with open(self.CACHE_FILE, 'w') as f:
|
||||
json.dump(data, f)
|
||||
|
||||
def get_latest_version(self) -> Optional[str]:
|
||||
"""
|
||||
Query PyPI for the latest version of SuperClaude
|
||||
|
||||
Returns:
|
||||
Latest version string or None if check fails
|
||||
"""
|
||||
try:
|
||||
# Create request with timeout
|
||||
req = urllib.request.Request(
|
||||
self.PYPI_URL,
|
||||
headers={'User-Agent': 'SuperClaude-Updater'}
|
||||
)
|
||||
|
||||
# Set timeout for the request
|
||||
with urllib.request.urlopen(req, timeout=self.TIMEOUT) as response:
|
||||
data = json.loads(response.read().decode())
|
||||
latest = data.get('info', {}).get('version')
|
||||
|
||||
if self.logger:
|
||||
self.logger.debug(f"Latest PyPI version: {latest}")
|
||||
|
||||
return latest
|
||||
|
||||
except (urllib.error.URLError, urllib.error.HTTPError, json.JSONDecodeError) as e:
|
||||
if self.logger:
|
||||
self.logger.debug(f"Failed to check PyPI: {e}")
|
||||
return None
|
||||
except Exception as e:
|
||||
if self.logger:
|
||||
self.logger.debug(f"Unexpected error checking updates: {e}")
|
||||
return None
|
||||
|
||||
def compare_versions(self, latest: str) -> bool:
|
||||
"""
|
||||
Compare current version with latest version
|
||||
|
||||
Args:
|
||||
latest: Latest version string
|
||||
|
||||
Returns:
|
||||
True if update is available
|
||||
"""
|
||||
try:
|
||||
return version.parse(latest) > version.parse(self.current_version)
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def detect_installation_method(self) -> str:
|
||||
"""
|
||||
Detect how SuperClaude was installed (pip, pipx, etc.)
|
||||
|
||||
Returns:
|
||||
Installation method string
|
||||
"""
|
||||
# Check pipx first
|
||||
try:
|
||||
result = subprocess.run(
|
||||
['pipx', 'list'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=2
|
||||
)
|
||||
if 'SuperClaude' in result.stdout or 'superclaude' in result.stdout:
|
||||
return 'pipx'
|
||||
except:
|
||||
pass
|
||||
|
||||
# Check if pip installation exists
|
||||
try:
|
||||
result = subprocess.run(
|
||||
[sys.executable, '-m', 'pip', 'show', 'SuperClaude'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=2
|
||||
)
|
||||
if result.returncode == 0:
|
||||
# Check if it's a user installation
|
||||
if '--user' in result.stdout or Path.home() in Path(result.stdout):
|
||||
return 'pip-user'
|
||||
return 'pip'
|
||||
except:
|
||||
pass
|
||||
|
||||
return 'unknown'
|
||||
|
||||
def get_update_command(self) -> str:
|
||||
"""
|
||||
Get the appropriate update command based on installation method
|
||||
|
||||
Returns:
|
||||
Update command string
|
||||
"""
|
||||
method = self.detect_installation_method()
|
||||
|
||||
commands = {
|
||||
'pipx': 'pipx upgrade SuperClaude',
|
||||
'pip-user': 'pip install --upgrade --user SuperClaude',
|
||||
'pip': 'pip install --upgrade SuperClaude',
|
||||
'unknown': 'pip install --upgrade SuperClaude'
|
||||
}
|
||||
|
||||
return commands.get(method, commands['unknown'])
|
||||
|
||||
def show_update_banner(self, latest: str, auto_update: bool = False) -> bool:
|
||||
"""
|
||||
Display update available banner
|
||||
|
||||
Args:
|
||||
latest: Latest version available
|
||||
auto_update: Whether to auto-update without prompting
|
||||
|
||||
Returns:
|
||||
True if user wants to update
|
||||
"""
|
||||
update_cmd = self.get_update_command()
|
||||
|
||||
# Display banner
|
||||
print(f"\n{Colors.CYAN}╔════════════════════════════════════════════════╗{Colors.RESET}")
|
||||
print(f"{Colors.CYAN}║{Colors.YELLOW} 🚀 Update Available: {self.current_version} → {latest} {Colors.CYAN}║{Colors.RESET}")
|
||||
print(f"{Colors.CYAN}║{Colors.GREEN} Run: {update_cmd:<30} {Colors.CYAN}║{Colors.RESET}")
|
||||
print(f"{Colors.CYAN}╚════════════════════════════════════════════════╝{Colors.RESET}\n")
|
||||
|
||||
if auto_update:
|
||||
return True
|
||||
|
||||
# Check if running in non-interactive mode
|
||||
if not sys.stdin.isatty():
|
||||
return False
|
||||
|
||||
# Prompt user
|
||||
try:
|
||||
response = input(f"{Colors.YELLOW}Would you like to update now? (y/N): {Colors.RESET}").strip().lower()
|
||||
return response in ['y', 'yes']
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
return False
|
||||
|
||||
def perform_update(self) -> bool:
|
||||
"""
|
||||
Execute the update command
|
||||
|
||||
Returns:
|
||||
True if update succeeded
|
||||
"""
|
||||
update_cmd = self.get_update_command()
|
||||
|
||||
print(f"{Colors.CYAN}🔄 Updating SuperClaude...{Colors.RESET}")
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
update_cmd.split(),
|
||||
capture_output=False,
|
||||
text=True
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
display_success("Update completed successfully!")
|
||||
print(f"{Colors.YELLOW}Please restart SuperClaude to use the new version.{Colors.RESET}")
|
||||
return True
|
||||
else:
|
||||
display_warning("Update failed. Please run manually:")
|
||||
print(f" {update_cmd}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
display_warning(f"Could not auto-update: {e}")
|
||||
print(f"Please run manually: {update_cmd}")
|
||||
return False
|
||||
|
||||
def check_and_notify(self, force: bool = False, auto_update: bool = False) -> bool:
|
||||
"""
|
||||
Main method to check for updates and notify user
|
||||
|
||||
Args:
|
||||
force: Force check regardless of last check time
|
||||
auto_update: Automatically update if available
|
||||
|
||||
Returns:
|
||||
True if update was performed
|
||||
"""
|
||||
# Check if we should skip based on environment variable
|
||||
if os.getenv('SUPERCLAUDE_NO_UPDATE_CHECK', '').lower() in ['true', '1', 'yes']:
|
||||
return False
|
||||
|
||||
# Check if auto-update is enabled via environment
|
||||
if os.getenv('SUPERCLAUDE_AUTO_UPDATE', '').lower() in ['true', '1', 'yes']:
|
||||
auto_update = True
|
||||
|
||||
# Check if enough time has passed
|
||||
if not self.should_check_update(force):
|
||||
return False
|
||||
|
||||
# Get latest version
|
||||
latest = self.get_latest_version()
|
||||
if not latest:
|
||||
return False
|
||||
|
||||
# Save timestamp
|
||||
self.save_check_timestamp()
|
||||
|
||||
# Compare versions
|
||||
if not self.compare_versions(latest):
|
||||
return False
|
||||
|
||||
# Show banner and potentially update
|
||||
if self.show_update_banner(latest, auto_update):
|
||||
return self.perform_update()
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def check_for_updates(current_version: str = "4.0.6", **kwargs) -> bool:
|
||||
"""
|
||||
Convenience function to check for updates
|
||||
|
||||
Args:
|
||||
current_version: Current installed version
|
||||
**kwargs: Additional arguments passed to check_and_notify
|
||||
|
||||
Returns:
|
||||
True if update was performed
|
||||
"""
|
||||
checker = UpdateChecker(current_version)
|
||||
return checker.check_and_notify(**kwargs)
|
||||
Loading…
x
Reference in New Issue
Block a user