mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 13:02:59 +00:00
fix(plugins): Fix GitHub install and update functionality for plugins installed from URLs (#167)
* fix(plugins): Fix GitHub install button for single plugin installation - Clone install button before attaching event listener to prevent duplicate handlers - Add safety checks for pluginStatusDiv element - Move installFromCustomRegistry function definition earlier in file - Add error logging when button/elements not found - Ensure consistent button reference usage in event handlers Fixes issue where Install button in 'Install Single Plugin' section was not working properly. * fix(plugins): Add button type and better logging for install button - Add type='button' to install button to prevent form submission - Add console logging to debug click handler attachment - Add preventDefault and stopPropagation to click handler - Improve error logging for debugging * fix(plugins): Re-attach install button handler when section is shown - Extract install button handler to separate function - Re-attach handler when GitHub install section is toggled visible - Add data attribute to prevent duplicate handler attachments - Add comprehensive logging for debugging - Handler now attaches even if section starts hidden * fix(plugins): Add comprehensive logging to debug install button handler - Add logging at function entry points - Add logging when section is shown and handler re-attached - Add logging before and after calling attachInstallButtonHandler - Helps diagnose why handler isn't being attached * fix(plugins): Expose GitHub install handlers globally and add fallback - Expose setupGitHubInstallHandlers and attachInstallButtonHandler to window object - Add fallback handler attachment after page load delay - Fix typo in getElementById call - Allows manual testing from browser console - Ensures handlers are accessible even if IIFE scope issues occur * fix(plugins): Add fallback handler attachment after page load * fix(plugins): Ensure GitHub install handlers are set up even if already initialized - Add check to verify setupGitHubInstallHandlers exists before calling - Call setupGitHubInstallHandlers even if initializePlugins was already called - Add comprehensive logging to track function execution - Helps diagnose why handlers aren't being attached * fix(plugins): Add more prominent logging markers for easier debugging * fix(plugins): Add simple standalone handler for GitHub plugin installation - Create handleGitHubPluginInstall() function defined early and globally - Add inline onclick handler to button as fallback - Bypasses complex initialization flow and IIFE scope issues - Direct approach that works immediately without dependencies - Provides clear error messages and logging * chore: Update 7-segment-clock plugin submodule - Update to latest version with scaling support - Includes compatible_versions field fix for plugin store installation * fix(plugins): Add update and uninstall handling to global event delegation fallback - Add 'update' action handling in handleGlobalPluginAction fallback - Add 'uninstall' action handling with confirmation dialog - Fixes issue where update/uninstall buttons did nothing - Buttons now work even if handlePluginAction isn't available yet * fix(plugins): Improve error message for plugin updates from GitHub URLs - Check if plugin is a git repository before checking registry - Provide more accurate error messages for plugins installed from URLs - Fixes misleading 'Plugin not found in registry' error for git-based plugins - Update should work for plugins installed from GitHub URLs even if not in registry * fix(plugins): Add detailed logging for plugin update failures - Log git command that failed and return code - Add logging before/after update attempt - Log whether plugin is detected as git repository - Helps diagnose why updates fail for plugins installed from URLs * fix(plugins): Add better logging for plugin update detection - Log when plugin is detected as git repository - Log when plugin is not a git repository - Provide helpful message for ZIP-installed plugins - Helps diagnose why updates fail for plugins installed from URLs * fix(plugins): Enable updates for plugins installed from GitHub URLs - Get git remote URL from plugin directory even if .git is missing - If plugin not in registry but has remote URL, reinstall as git repo - Allows updating plugins installed from URLs even if git clone failed initially - Falls back to reinstalling from original URL to enable future updates * fix(plugins): Reinstall from git remote URL if plugin not in registry - When plugin is not a git repo and not in registry, check for git remote URL - If remote URL exists, reinstall plugin from that URL to enable future updates - Handles case where plugin was installed from URL but git clone failed initially * fix(plugins): Improve git update error handling and logging - Make git fetch non-fatal (log warning but continue) - Make git checkout non-fatal (log warning but continue) - Add detailed error messages for common git failures - Log which git command failed and return code - Better handling of authentication, merge conflicts, and unrelated histories * fix(plugins): Add detailed exception logging to update endpoint - Log full traceback when update fails - Log exception details in catch block - Helps diagnose update failures from API endpoint * fix(plugins): Handle untracked files during plugin update - Remove .dependencies_installed marker file before pull (safe to regenerate) - Stash untracked files using 'git stash -u' if they can't be removed - Prevents 'untracked files would be overwritten' errors during update - Fixes issue where .dependencies_installed blocks git pull * chore: Update 7-segment-clock submodule with improved clone instructions --------- Co-authored-by: Chuck <chuck@example.com>
This commit is contained in:
@@ -1338,6 +1338,16 @@ class PluginStoreManager:
|
||||
if branch == 'HEAD':
|
||||
branch = ''
|
||||
|
||||
# Get remote URL
|
||||
remote_url_result = subprocess.run(
|
||||
['git', '-C', str(plugin_path), 'config', '--get', 'remote.origin.url'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
check=False
|
||||
)
|
||||
remote_url = remote_url_result.stdout.strip() if remote_url_result.returncode == 0 else None
|
||||
|
||||
# Get commit date in ISO format
|
||||
date_result = subprocess.run(
|
||||
['git', '-C', str(plugin_path), 'log', '-1', '--format=%cI', 'HEAD'],
|
||||
@@ -1353,6 +1363,10 @@ class PluginStoreManager:
|
||||
'short_sha': sha[:7] if sha else '',
|
||||
'branch': branch
|
||||
}
|
||||
|
||||
# Add remote URL if available
|
||||
if remote_url:
|
||||
result['remote_url'] = remote_url
|
||||
|
||||
# Add commit date if available
|
||||
if commit_date_iso:
|
||||
@@ -1465,13 +1479,18 @@ class PluginStoreManager:
|
||||
self.logger.info(f"Updating {plugin_id} via git pull (local branch: {local_branch})...")
|
||||
try:
|
||||
# Fetch latest changes first to get all remote branch info
|
||||
subprocess.run(
|
||||
# If fetch fails, we'll still try to pull (might work with existing remote refs)
|
||||
fetch_result = subprocess.run(
|
||||
['git', '-C', str(plugin_path), 'fetch', 'origin'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=60,
|
||||
check=True
|
||||
check=False
|
||||
)
|
||||
if fetch_result.returncode != 0:
|
||||
self.logger.warning(f"Git fetch failed for {plugin_id}: {fetch_result.stderr or fetch_result.stdout}. Will still attempt pull.")
|
||||
else:
|
||||
self.logger.debug(f"Successfully fetched remote changes for {plugin_id}")
|
||||
|
||||
# Determine which remote branch to pull from
|
||||
# Strategy: Use what the local branch is tracking, or find the best match
|
||||
@@ -1546,17 +1565,49 @@ class PluginStoreManager:
|
||||
self.logger.info(f"Falling back to local branch name {local_branch} for pull")
|
||||
|
||||
# Ensure we're on the local branch
|
||||
subprocess.run(
|
||||
checkout_result = subprocess.run(
|
||||
['git', '-C', str(plugin_path), 'checkout', local_branch],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
check=True
|
||||
check=False
|
||||
)
|
||||
if checkout_result.returncode != 0:
|
||||
self.logger.warning(f"Git checkout to {local_branch} failed for {plugin_id}: {checkout_result.stderr or checkout_result.stdout}. Will still attempt pull.")
|
||||
|
||||
# Check for local changes and stash them if needed
|
||||
# Use --untracked-files=no to skip untracked files check (much faster)
|
||||
# Check for local changes and untracked files that might conflict
|
||||
# First, check for untracked files that would be overwritten
|
||||
try:
|
||||
# Check for untracked files
|
||||
untracked_result = subprocess.run(
|
||||
['git', '-C', str(plugin_path), 'status', '--porcelain', '--untracked-files=all'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
check=False
|
||||
)
|
||||
untracked_files = []
|
||||
if untracked_result.returncode == 0:
|
||||
for line in untracked_result.stdout.strip().split('\n'):
|
||||
if line.startswith('??'):
|
||||
# Untracked file
|
||||
file_path = line[3:].strip()
|
||||
untracked_files.append(file_path)
|
||||
|
||||
# Remove marker files that are safe to delete (they'll be regenerated)
|
||||
safe_to_remove = ['.dependencies_installed']
|
||||
removed_files = []
|
||||
for file_name in safe_to_remove:
|
||||
file_path = plugin_path / file_name
|
||||
if file_path.exists() and file_name in untracked_files:
|
||||
try:
|
||||
file_path.unlink()
|
||||
removed_files.append(file_name)
|
||||
self.logger.info(f"Removed marker file {file_name} from {plugin_id} before update")
|
||||
except Exception as e:
|
||||
self.logger.warning(f"Could not remove {file_name} from {plugin_id}: {e}")
|
||||
|
||||
# Check for tracked file changes
|
||||
status_result = subprocess.run(
|
||||
['git', '-C', str(plugin_path), 'status', '--porcelain', '--untracked-files=no'],
|
||||
capture_output=True,
|
||||
@@ -1565,6 +1616,12 @@ class PluginStoreManager:
|
||||
check=False
|
||||
)
|
||||
has_changes = bool(status_result.stdout.strip())
|
||||
|
||||
# If there are remaining untracked files (not safe to remove), stash them
|
||||
remaining_untracked = [f for f in untracked_files if f not in removed_files]
|
||||
if remaining_untracked:
|
||||
self.logger.info(f"Found {len(remaining_untracked)} untracked files in {plugin_id}, will stash them")
|
||||
has_changes = True
|
||||
except subprocess.TimeoutExpired:
|
||||
# If status check times out, assume there might be changes and proceed
|
||||
self.logger.warning(f"Git status check timed out for {plugin_id}, proceeding with update")
|
||||
@@ -1575,8 +1632,9 @@ class PluginStoreManager:
|
||||
if has_changes:
|
||||
self.logger.info(f"Stashing local changes in {plugin_id} before update")
|
||||
try:
|
||||
# Use -u to include untracked files in stash
|
||||
stash_result = subprocess.run(
|
||||
['git', '-C', str(plugin_path), 'stash', 'push', '-m', f'LEDMatrix auto-stash before update {plugin_id}'],
|
||||
['git', '-C', str(plugin_path), 'stash', 'push', '-u', '-m', f'LEDMatrix auto-stash before update {plugin_id}'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
@@ -1584,13 +1642,14 @@ class PluginStoreManager:
|
||||
)
|
||||
if stash_result.returncode == 0:
|
||||
stash_info = " (local changes were stashed)"
|
||||
self.logger.info(f"Stashed local changes for {plugin_id}")
|
||||
self.logger.info(f"Stashed local changes (including untracked files) for {plugin_id}")
|
||||
else:
|
||||
self.logger.warning(f"Failed to stash local changes for {plugin_id}: {stash_result.stderr}")
|
||||
except subprocess.TimeoutExpired:
|
||||
self.logger.warning(f"Stash operation timed out for {plugin_id}, proceeding with pull")
|
||||
|
||||
# Pull from the determined remote branch
|
||||
self.logger.info(f"Pulling from origin/{remote_pull_branch} for {plugin_id}...")
|
||||
pull_result = subprocess.run(
|
||||
['git', '-C', str(plugin_path), 'pull', 'origin', remote_pull_branch],
|
||||
capture_output=True,
|
||||
@@ -1616,20 +1675,82 @@ class PluginStoreManager:
|
||||
|
||||
except subprocess.CalledProcessError as git_error:
|
||||
error_output = git_error.stderr or git_error.stdout or "Unknown error"
|
||||
self.logger.warning(f"Git update failed for {plugin_id}: {error_output}")
|
||||
# Check if it's a merge conflict or local changes issue
|
||||
if "would be overwritten" in error_output or "local changes" in error_output.lower():
|
||||
cmd_str = ' '.join(git_error.cmd) if hasattr(git_error, 'cmd') else 'unknown'
|
||||
self.logger.error(f"Git update failed for {plugin_id}")
|
||||
self.logger.error(f"Command: {cmd_str}")
|
||||
self.logger.error(f"Return code: {git_error.returncode}")
|
||||
self.logger.error(f"Error output: {error_output}")
|
||||
|
||||
# Check for specific error conditions
|
||||
error_lower = error_output.lower()
|
||||
if "would be overwritten" in error_output or "local changes" in error_lower:
|
||||
self.logger.warning(f"Plugin {plugin_id} has local changes that prevent update. Consider committing or stashing changes manually.")
|
||||
elif "refusing to merge unrelated histories" in error_lower:
|
||||
self.logger.error(f"Plugin {plugin_id} has unrelated git histories. Plugin may need to be reinstalled.")
|
||||
elif "authentication" in error_lower or "permission denied" in error_lower:
|
||||
self.logger.error(f"Authentication failed for {plugin_id}. Check git credentials or repository permissions.")
|
||||
elif "not found" in error_lower or "does not exist" in error_lower:
|
||||
self.logger.error(f"Remote branch or repository not found for {plugin_id}. Check repository URL and branch name.")
|
||||
elif "merge conflict" in error_lower or "conflict" in error_lower:
|
||||
self.logger.error(f"Merge conflict detected for {plugin_id}. Resolve conflicts manually or reinstall plugin.")
|
||||
|
||||
return False
|
||||
except subprocess.TimeoutExpired:
|
||||
self.logger.warning(f"Git update timed out for {plugin_id}")
|
||||
return False
|
||||
|
||||
# Not a git repository - try registry-based update
|
||||
# Not a git repository - try to get repo URL from git config if it exists
|
||||
# (in case .git directory was removed but remote URL is still in config)
|
||||
repo_url = None
|
||||
try:
|
||||
remote_url_result = subprocess.run(
|
||||
['git', '-C', str(plugin_path), 'config', '--get', 'remote.origin.url'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
check=False
|
||||
)
|
||||
if remote_url_result.returncode == 0:
|
||||
repo_url = remote_url_result.stdout.strip()
|
||||
self.logger.info(f"Found git remote URL for {plugin_id}: {repo_url}")
|
||||
except Exception as e:
|
||||
self.logger.debug(f"Could not get git remote URL: {e}")
|
||||
|
||||
# Try registry-based update
|
||||
self.logger.info(f"Plugin {plugin_id} is not a git repository, checking registry...")
|
||||
self.fetch_registry(force_refresh=True)
|
||||
plugin_info_remote = self.get_plugin_info(plugin_id, fetch_latest_from_github=True)
|
||||
|
||||
# If not in registry but we have a repo URL, try reinstalling from that URL
|
||||
if not plugin_info_remote and repo_url:
|
||||
self.logger.info(f"Plugin {plugin_id} not in registry but has git remote URL. Reinstalling from {repo_url} to enable updates...")
|
||||
try:
|
||||
# Get current branch if possible
|
||||
branch_result = subprocess.run(
|
||||
['git', '-C', str(plugin_path), 'rev-parse', '--abbrev-ref', 'HEAD'],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=10,
|
||||
check=False
|
||||
)
|
||||
branch = branch_result.stdout.strip() if branch_result.returncode == 0 else None
|
||||
if branch == 'HEAD' or not branch:
|
||||
branch = 'main'
|
||||
|
||||
# Reinstall from URL
|
||||
result = self.install_from_url(repo_url, plugin_id=plugin_id, branch=branch)
|
||||
if result.get('success'):
|
||||
self.logger.info(f"Successfully reinstalled {plugin_id} from {repo_url} as git repository")
|
||||
return True
|
||||
else:
|
||||
self.logger.warning(f"Failed to reinstall {plugin_id} from {repo_url}: {result.get('error')}")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error reinstalling {plugin_id} from URL: {e}")
|
||||
|
||||
if not plugin_info_remote:
|
||||
self.logger.warning(f"Plugin {plugin_id} not found in registry and not a git repository; cannot update automatically")
|
||||
if not repo_url:
|
||||
self.logger.warning(f"Plugin may have been installed via ZIP download. Try reinstalling from GitHub URL to enable updates.")
|
||||
return False
|
||||
|
||||
repo_url = plugin_info_remote.get('repo')
|
||||
|
||||
Reference in New Issue
Block a user