mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-10 21:03:01 +00:00
refactor: migrate from submodules to multi-root workspace for plugins (#198)
* refactor: migrate from submodules to multi-root workspace for plugins - Updated LEDMatrix.code-workspace to include all plugin repos as root folders - Removed symlinks from plugin-repos/ and plugins/ directories - Updated .gitignore to reflect new plugin management approach - Added setup_plugin_repos.py script for managing plugin symlinks (if needed) - Added MULTI_ROOT_WORKSPACE_SETUP.md documentation Plugins are now managed as independent repositories via multi-root workspace, allowing for easier development and independent updates without modifying the LEDMatrix project structure. * Fix MULTI_ROOT_WORKSPACE_SETUP.md and add JSON error handling - Remove deprecated clone_plugin_repos.py command reference - Add language tag to directory tree code fence (fixes MD040) - Add JSONDecodeError handling in setup_plugin_repos.py with user-friendly error messages --------- Co-authored-by: Chuck <chuck@example.com>
This commit is contained in:
27
.gitignore
vendored
27
.gitignore
vendored
@@ -35,29 +35,8 @@ htmlcov/
|
|||||||
# Cache directory (root level only, not src/cache which is source code)
|
# Cache directory (root level only, not src/cache which is source code)
|
||||||
/cache/
|
/cache/
|
||||||
|
|
||||||
# Development plugins directory (contains symlinks to plugin repos)
|
# Development plugins directory
|
||||||
# Plugins are installed via plugin store, not bundled with main repo
|
# Plugins are managed as separate repositories via multi-root workspace
|
||||||
# Allow git submodules
|
# See docs/MULTI_ROOT_WORKSPACE_SETUP.md for details
|
||||||
plugins/*
|
plugins/*
|
||||||
!plugins/.gitkeep
|
!plugins/.gitkeep
|
||||||
!plugins/odds-ticker/
|
|
||||||
!plugins/clock-simple/
|
|
||||||
!plugins/text-display/
|
|
||||||
!plugins/basketball-scoreboard/
|
|
||||||
!plugins/soccer-scoreboard/
|
|
||||||
!plugins/calendar/
|
|
||||||
!plugins/mqtt-notifications/
|
|
||||||
!plugins/youtube-stats/
|
|
||||||
!plugins/olympics-countdown/
|
|
||||||
!plugins/ledmatrix-stocks/
|
|
||||||
!plugins/ledmatrix-music/
|
|
||||||
!plugins/static-image/
|
|
||||||
!plugins/football-scoreboard/
|
|
||||||
!plugins/hockey-scoreboard/
|
|
||||||
!plugins/baseball-scoreboard/
|
|
||||||
!plugins/christmas-countdown/
|
|
||||||
!plugins/ledmatrix-flights/
|
|
||||||
!plugins/ledmatrix-leaderboard/
|
|
||||||
!plugins/ledmatrix-weather/
|
|
||||||
!plugins/ledmatrix-news/
|
|
||||||
!plugins/ledmatrix-of-the-day/
|
|
||||||
|
|||||||
@@ -1,7 +1,99 @@
|
|||||||
{
|
{
|
||||||
"folders": [
|
"folders": [
|
||||||
{
|
{
|
||||||
"path": "."
|
"path": ".",
|
||||||
}
|
"name": "LEDMatrix (Main)"
|
||||||
]
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-odds-ticker",
|
||||||
|
"name": "Odds Ticker"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-clock-simple",
|
||||||
|
"name": "Clock Simple"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-text-display",
|
||||||
|
"name": "Text Display"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-basketball-scoreboard",
|
||||||
|
"name": "Basketball Scoreboard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-soccer-scoreboard",
|
||||||
|
"name": "Soccer Scoreboard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-calendar",
|
||||||
|
"name": "Calendar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-olympics-countdown",
|
||||||
|
"name": "Olympics Countdown"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-stocks",
|
||||||
|
"name": "Stocks"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-music",
|
||||||
|
"name": "Music"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-static-image",
|
||||||
|
"name": "Static Image"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-football-scoreboard",
|
||||||
|
"name": "Football Scoreboard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-hockey-scoreboard",
|
||||||
|
"name": "Hockey Scoreboard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-baseball-scoreboard",
|
||||||
|
"name": "Baseball Scoreboard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-christmas-countdown",
|
||||||
|
"name": "Christmas Countdown"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-flights",
|
||||||
|
"name": "Flights"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-leaderboard",
|
||||||
|
"name": "Leaderboard"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-weather",
|
||||||
|
"name": "Weather"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-news",
|
||||||
|
"name": "News"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-of-the-day",
|
||||||
|
"name": "Of The Day"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-youtube-stats",
|
||||||
|
"name": "YouTube Stats"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "../ledmatrix-plugins",
|
||||||
|
"name": "Plugin Registry"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"files.exclude": {
|
||||||
|
"**/.git": true,
|
||||||
|
"**/__pycache__": true,
|
||||||
|
"**/*.pyc": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
169
docs/MULTI_ROOT_WORKSPACE_SETUP.md
Normal file
169
docs/MULTI_ROOT_WORKSPACE_SETUP.md
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
# Multi-Root Workspace Setup Guide
|
||||||
|
|
||||||
|
This document explains how the LEDMatrix project uses a multi-root workspace to manage plugins as separate Git repositories.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The LEDMatrix project has been migrated from a git submodule implementation to a **multi-root workspace** implementation for managing plugins. This allows:
|
||||||
|
|
||||||
|
- ✅ Plugins to exist as independent Git repositories
|
||||||
|
- ✅ Updates to plugins without modifying the LEDMatrix project
|
||||||
|
- ✅ Easy development workflow with all repos in one workspace
|
||||||
|
- ✅ Plugin system discovers plugins via symlinks in `plugin-repos/`
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```text
|
||||||
|
/home/chuck/Github/
|
||||||
|
├── LEDMatrix/ # Main project
|
||||||
|
│ ├── plugin-repos/ # Symlinks to actual repos (managed automatically)
|
||||||
|
│ │ ├── ledmatrix-clock-simple -> ../../ledmatrix-clock-simple
|
||||||
|
│ │ ├── ledmatrix-weather -> ../../ledmatrix-weather
|
||||||
|
│ │ └── ...
|
||||||
|
│ ├── LEDMatrix.code-workspace # Multi-root workspace configuration
|
||||||
|
│ └── ...
|
||||||
|
├── ledmatrix-clock-simple/ # Plugin repository (actual git repo)
|
||||||
|
├── ledmatrix-weather/ # Plugin repository (actual git repo)
|
||||||
|
├── ledmatrix-football-scoreboard/ # Plugin repository (actual git repo)
|
||||||
|
└── ... # Other plugin repos
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
### 1. Plugin Repositories
|
||||||
|
|
||||||
|
All plugin repositories are cloned to `/home/chuck/Github/` (parent directory of LEDMatrix) as regular Git repositories:
|
||||||
|
|
||||||
|
- `ledmatrix-clock-simple/`
|
||||||
|
- `ledmatrix-weather/`
|
||||||
|
- `ledmatrix-football-scoreboard/`
|
||||||
|
- etc.
|
||||||
|
|
||||||
|
### 2. Symlinks in plugin-repos/
|
||||||
|
|
||||||
|
The `LEDMatrix/plugin-repos/` directory contains symlinks pointing to the actual repositories in the parent directory. This allows the plugin system to discover plugins without modifying the project structure.
|
||||||
|
|
||||||
|
### 3. Multi-Root Workspace
|
||||||
|
|
||||||
|
The `LEDMatrix.code-workspace` file configures VS Code/Cursor to open all plugin repositories as separate workspace roots, allowing easy development across all repos.
|
||||||
|
|
||||||
|
## Setup Scripts
|
||||||
|
|
||||||
|
### Initial Setup
|
||||||
|
|
||||||
|
If you already have plugin repositories cloned, use the setup script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/chuck/Github/LEDMatrix
|
||||||
|
python3 scripts/setup_plugin_repos.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This script:
|
||||||
|
- Reads the workspace configuration
|
||||||
|
- Creates symlinks in `plugin-repos/` pointing to actual repos
|
||||||
|
- Verifies all links are created correctly
|
||||||
|
|
||||||
|
### Updating Plugins
|
||||||
|
|
||||||
|
To update all plugin repositories:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/chuck/Github/LEDMatrix
|
||||||
|
python3 scripts/update_plugin_repos.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This script:
|
||||||
|
- Finds all plugins in the workspace
|
||||||
|
- Runs `git pull` on each repository
|
||||||
|
- Reports which plugins were updated
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The plugin system is configured in `config/config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plugin_system": {
|
||||||
|
"plugins_directory": "plugin-repos",
|
||||||
|
"auto_discover": true,
|
||||||
|
"auto_load_enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The `plugins_directory` points to `plugin-repos/`, which contains symlinks to the actual repositories.
|
||||||
|
|
||||||
|
## Workflow
|
||||||
|
|
||||||
|
### Daily Development
|
||||||
|
|
||||||
|
1. **Open Workspace**: Open `LEDMatrix.code-workspace` in VS Code/Cursor
|
||||||
|
2. **All Repos Available**: All plugin repos appear as separate folders in the workspace
|
||||||
|
3. **Edit Plugins**: Edit plugin code directly in their repositories
|
||||||
|
4. **Update Plugins**: Run `update_plugin_repos.py` to pull latest changes
|
||||||
|
|
||||||
|
### Adding New Plugins
|
||||||
|
|
||||||
|
1. **Clone Repository**: Clone the new plugin repo to `/home/chuck/Github/`
|
||||||
|
2. **Add to Workspace**: Add the plugin folder to `LEDMatrix.code-workspace`
|
||||||
|
3. **Create Symlink**: Run `setup_plugin_repos.py` to create the symlink
|
||||||
|
|
||||||
|
### Updating Individual Plugins
|
||||||
|
|
||||||
|
Since plugins are regular Git repositories, you can update them individually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/chuck/Github/ledmatrix-weather
|
||||||
|
git pull origin master
|
||||||
|
```
|
||||||
|
|
||||||
|
Or update all at once:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/chuck/Github/LEDMatrix
|
||||||
|
python3 scripts/update_plugin_repos.py
|
||||||
|
```
|
||||||
|
|
||||||
|
## Benefits
|
||||||
|
|
||||||
|
1. **No Submodule Hassle**: No need to update `.gitmodules` or run `git submodule update`
|
||||||
|
2. **Independent Updates**: Update plugins independently without touching LEDMatrix
|
||||||
|
3. **Clean Separation**: Each plugin is a separate repository with its own history
|
||||||
|
4. **Easy Development**: Multi-root workspace makes it easy to work across repos
|
||||||
|
5. **Automatic Discovery**: Plugin system automatically discovers plugins via symlinks
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Symlinks Not Working
|
||||||
|
|
||||||
|
If plugins aren't being discovered:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /home/chuck/Github/LEDMatrix
|
||||||
|
python3 scripts/setup_plugin_repos.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This will recreate all symlinks.
|
||||||
|
|
||||||
|
### Missing Plugins
|
||||||
|
|
||||||
|
If a plugin is in the workspace but not found:
|
||||||
|
|
||||||
|
1. Check if the repo exists in `/home/chuck/Github/`
|
||||||
|
2. Check if the symlink exists in `plugin-repos/`
|
||||||
|
3. Run `setup_plugin_repos.py` to recreate symlinks
|
||||||
|
|
||||||
|
### Plugin Updates Not Showing
|
||||||
|
|
||||||
|
If changes to plugins aren't appearing:
|
||||||
|
|
||||||
|
1. Verify the symlink points to the correct directory: `ls -la plugin-repos/ledmatrix-weather`
|
||||||
|
2. Check that you're editing in the actual repo, not a copy
|
||||||
|
3. Restart the LEDMatrix service if running
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- The `plugin-repos/` directory is tracked in git, but only contains symlinks
|
||||||
|
- Actual plugin code lives in `/home/chuck/Github/ledmatrix-*/`
|
||||||
|
- Each plugin repo can be updated independently via `git pull`
|
||||||
|
- The LEDMatrix project doesn't need to be updated when plugins change
|
||||||
Submodule plugins/baseball-scoreboard deleted from 67e5596e52
Submodule plugins/basketball-scoreboard deleted from d626e538c0
Submodule plugins/calendar deleted from 940a045a3b
Submodule plugins/christmas-countdown deleted from a01bb1b8ff
Submodule plugins/clock-simple deleted from 228e1f2fcf
Submodule plugins/football-scoreboard deleted from 8d36803e3b
Submodule plugins/hockey-scoreboard deleted from 9fef0cd209
Submodule plugins/ledmatrix-flights deleted from 56eb42c30a
Submodule plugins/ledmatrix-leaderboard deleted from 6ecc6cd183
Submodule plugins/ledmatrix-music deleted from ded4fe91e4
Submodule plugins/ledmatrix-news deleted from cdc1b118ad
Submodule plugins/ledmatrix-of-the-day deleted from c06c4a3e4c
Submodule plugins/ledmatrix-stocks deleted from 39715c38e0
Submodule plugins/ledmatrix-weather deleted from 7be0ef7258
Submodule plugins/mqtt-notifications deleted from 5c23a842d8
Submodule plugins/odds-ticker deleted from b8c0e87748
Submodule plugins/olympics-countdown deleted from bf31bd8b4b
Submodule plugins/soccer-scoreboard deleted from e22a16da38
Submodule plugins/static-image deleted from 70e1b4b693
Submodule plugins/text-display deleted from b3a423beaf
Submodule plugins/youtube-stats deleted from a305591b06
151
scripts/setup_plugin_repos.py
Executable file
151
scripts/setup_plugin_repos.py
Executable file
@@ -0,0 +1,151 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Setup plugin repository references for multi-root workspace.
|
||||||
|
|
||||||
|
This script creates symlinks in plugin-repos/ pointing to the actual
|
||||||
|
plugin repositories in the parent directory, allowing the system to
|
||||||
|
find plugins without modifying the LEDMatrix project structure.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Paths
|
||||||
|
PROJECT_ROOT = Path(__file__).parent.parent
|
||||||
|
PLUGIN_REPOS_DIR = PROJECT_ROOT / "plugin-repos"
|
||||||
|
GITHUB_DIR = PROJECT_ROOT.parent
|
||||||
|
CONFIG_FILE = PROJECT_ROOT / "config" / "config.json"
|
||||||
|
|
||||||
|
|
||||||
|
def get_workspace_plugins():
|
||||||
|
"""Get list of plugins from workspace file."""
|
||||||
|
workspace_file = PROJECT_ROOT / "LEDMatrix.code-workspace"
|
||||||
|
if not workspace_file.exists():
|
||||||
|
return []
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(workspace_file, 'r') as f:
|
||||||
|
workspace = json.load(f)
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
print(f"Error: Failed to parse workspace file {workspace_file}: {e}")
|
||||||
|
print("Please check that the workspace file contains valid JSON.")
|
||||||
|
return []
|
||||||
|
|
||||||
|
plugins = []
|
||||||
|
for folder in workspace.get('folders', []):
|
||||||
|
path = folder.get('path', '')
|
||||||
|
if path.startswith('../') and path != '../ledmatrix-plugins':
|
||||||
|
plugin_name = path.replace('../', '')
|
||||||
|
plugins.append({
|
||||||
|
'name': plugin_name,
|
||||||
|
'workspace_path': path,
|
||||||
|
'actual_path': GITHUB_DIR / plugin_name,
|
||||||
|
'link_path': PLUGIN_REPOS_DIR / plugin_name
|
||||||
|
})
|
||||||
|
|
||||||
|
return plugins
|
||||||
|
|
||||||
|
|
||||||
|
def create_symlinks():
|
||||||
|
"""Create symlinks in plugin-repos/ pointing to actual repos."""
|
||||||
|
plugins = get_workspace_plugins()
|
||||||
|
|
||||||
|
if not plugins:
|
||||||
|
print("No plugins found in workspace configuration")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Ensure plugin-repos directory exists
|
||||||
|
PLUGIN_REPOS_DIR.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
created = 0
|
||||||
|
skipped = 0
|
||||||
|
errors = 0
|
||||||
|
|
||||||
|
print(f"Setting up plugin repository links...")
|
||||||
|
print(f" Source: {GITHUB_DIR}")
|
||||||
|
print(f" Links: {PLUGIN_REPOS_DIR}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
for plugin in plugins:
|
||||||
|
actual_path = plugin['actual_path']
|
||||||
|
link_path = plugin['link_path']
|
||||||
|
|
||||||
|
if not actual_path.exists():
|
||||||
|
print(f" ⚠️ {plugin['name']} - source not found: {actual_path}")
|
||||||
|
errors += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Remove existing link/file if it exists
|
||||||
|
if link_path.exists() or link_path.is_symlink():
|
||||||
|
if link_path.is_symlink():
|
||||||
|
# Check if it points to the right place
|
||||||
|
try:
|
||||||
|
if link_path.resolve() == actual_path.resolve():
|
||||||
|
print(f" ✓ {plugin['name']} - link already exists")
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
# Remove old symlink pointing elsewhere
|
||||||
|
link_path.unlink()
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ⚠️ {plugin['name']} - error checking link: {e}")
|
||||||
|
link_path.unlink()
|
||||||
|
else:
|
||||||
|
# It's a directory/file, not a symlink
|
||||||
|
print(f" ⚠️ {plugin['name']} - {link_path.name} exists but is not a symlink")
|
||||||
|
print(f" Skipping (manual cleanup required)")
|
||||||
|
skipped += 1
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Create symlink
|
||||||
|
try:
|
||||||
|
# Use relative path for symlink portability
|
||||||
|
relative_path = os.path.relpath(actual_path, link_path.parent)
|
||||||
|
link_path.symlink_to(relative_path)
|
||||||
|
print(f" ✓ {plugin['name']} - linked")
|
||||||
|
created += 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ {plugin['name']} - failed to create link: {e}")
|
||||||
|
errors += 1
|
||||||
|
|
||||||
|
print()
|
||||||
|
print(f"✅ Created {created} links, skipped {skipped}, errors {errors}")
|
||||||
|
|
||||||
|
return errors == 0
|
||||||
|
|
||||||
|
|
||||||
|
def update_config_path():
|
||||||
|
"""Update config to use absolute path to parent directory (alternative approach)."""
|
||||||
|
# This is an alternative - set plugins_directory to absolute path
|
||||||
|
# Currently not implemented as symlinks are preferred
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Main function."""
|
||||||
|
print("🔗 Setting up plugin repository symlinks...")
|
||||||
|
print()
|
||||||
|
|
||||||
|
if not GITHUB_DIR.exists():
|
||||||
|
print(f"Error: GitHub directory not found: {GITHUB_DIR}")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
success = create_symlinks()
|
||||||
|
|
||||||
|
if success:
|
||||||
|
print()
|
||||||
|
print("✅ Plugin repository setup complete!")
|
||||||
|
print()
|
||||||
|
print("Plugins are now accessible via symlinks in plugin-repos/")
|
||||||
|
print("You can update plugins independently in their git repos.")
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
print()
|
||||||
|
print("⚠️ Setup completed with some errors. Check output above.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
sys.exit(main())
|
||||||
Reference in New Issue
Block a user