mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-08-06 11:18:06 +00:00
chore: second-pass cleanup — dead installer branch, broken script, orphaned JS, misfiled test deps
- first_time_install.sh: removed the pip fallback branch that installed from requirements_web_v2.txt — a file that has not existed since the v2 web interface was removed (the branch always printed its own 'not found; skipping' warning). - scripts/remove_plugin_backups.sh deleted: its PROJECT_ROOT resolved to the repo's PARENT directory, and its verify_submodules() checks for plugin submodules from an era before plugins moved to the store — it could never have worked from its current location. - plugins_manager.js: removed three functions with zero call sites anywhere (addKeyValuePair, formatCommit, togglePasswordVisibility) — verified against all templates, all JS, and the dynamic window[name] dispatch sites, which resolve widget-registry keys only. Also replaced base.html's misleading 'Legacy ... during migration' label: the file is deliberately loaded last and provides the LIVE implementations of seven window.* plugin actions that shadow same-named definitions in app.js/app-shell.js. - pytest/pytest-cov/pytest-mock moved from runtime requirements.txt to requirements-test.txt (CI already installs both files; the installer's line-by-line loop simply installs three fewer packages on devices; no store plugin declares pytest). HOW_TO_RUN_TESTS.md updated. - scripts/add_defaults_to_schemas.py and analyze_plugin_schemas.py scanned the empty legacy plugins/ dir — now scan plugin-repos/. Verified: fresh venv installs all four requirements files with pip check clean and pytest available; bash -n on the installer; node --check on the JS; widget/cache guard tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SXb4mKcAkVaxkeTb3YnAdr
This commit is contained in:
@@ -10,10 +10,7 @@ Make sure you have the testing packages installed:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install all dependencies including test packages
|
# Install all dependencies including test packages
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt -r requirements-test.txt
|
||||||
|
|
||||||
# Or install just the test dependencies
|
|
||||||
pip install pytest pytest-cov pytest-mock
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Set Environment Variables
|
### 2. Set Environment Variables
|
||||||
@@ -302,7 +299,7 @@ If tests fail due to missing packages:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install all dependencies
|
# Install all dependencies
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt -r requirements-test.txt
|
||||||
|
|
||||||
# Or install specific missing package
|
# Or install specific missing package
|
||||||
pip install <package-name>
|
pip install <package-name>
|
||||||
|
|||||||
@@ -1189,14 +1189,7 @@ else
|
|||||||
# real time and in order relative to this script's own echo statements
|
# real time and in order relative to this script's own echo statements
|
||||||
python3 -u "$PROJECT_ROOT_DIR/scripts/install_dependencies_apt.py"
|
python3 -u "$PROJECT_ROOT_DIR/scripts/install_dependencies_apt.py"
|
||||||
else
|
else
|
||||||
echo "Using pip to install dependencies..."
|
echo "Web dependencies already installed from web_interface/requirements.txt in Step 5"
|
||||||
if [ -f "$PROJECT_ROOT_DIR/requirements_web_v2.txt" ]; then
|
|
||||||
# --ignore-installed: see the Step 5 web_interface/requirements.txt
|
|
||||||
# install above — same apt/pip RECORD-file conflict applies here.
|
|
||||||
python3 -m pip install --break-system-packages --prefer-binary --ignore-installed -r requirements_web_v2.txt
|
|
||||||
else
|
|
||||||
echo "⚠ requirements_web_v2.txt not found; skipping web dependency install"
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create marker file to indicate dependencies are installed
|
# Create marker file to indicate dependencies are installed
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
# Test-only dependencies for the plugin safety harness and pytest suite.
|
# Test/dev-only dependencies (not needed on a running display).
|
||||||
# Install alongside requirements.txt: pip install -r requirements.txt -r requirements-test.txt
|
# Install alongside requirements.txt: pip install -r requirements.txt -r requirements-test.txt
|
||||||
#
|
pytest>=9.0.3,<10.0.0
|
||||||
# pytest, pytest-cov, pytest-mock, and jsonschema are already pinned (with
|
pytest-cov>=4.1.0,<5.0.0
|
||||||
# major-version caps) in requirements.txt, so they are intentionally NOT
|
pytest-mock>=3.11.0,<4.0.0
|
||||||
# repeated here — re-pinning pytest to <9 collided with requirements.txt's
|
|
||||||
# pytest>=9.0.3,<10 and made the two files impossible to install together.
|
|
||||||
# Only declare what requirements.txt doesn't already provide.
|
|
||||||
freezegun>=1.2,<2 # deterministic time for golden-image tests
|
freezegun>=1.2,<2 # deterministic time for golden-image tests
|
||||||
mypy>=1.5.0,<2.0.0 # static type checking (also pinned in .pre-commit-config.yaml)
|
mypy>=1.5.0,<2.0.0 # static type checking (also pinned in .pre-commit-config.yaml)
|
||||||
|
|||||||
+2
-4
@@ -34,10 +34,8 @@ jsonschema>=4.20.0,<5.0.0
|
|||||||
# Requirement specifier parsing (plugin dependency satisfaction checks)
|
# Requirement specifier parsing (plugin dependency satisfaction checks)
|
||||||
packaging>=23.0,<27.0
|
packaging>=23.0,<27.0
|
||||||
|
|
||||||
# Testing dependencies
|
# Testing dependencies live in requirements-test.txt:
|
||||||
pytest>=9.0.3,<10.0.0
|
# pip install -r requirements.txt -r requirements-test.txt
|
||||||
pytest-cov>=4.1.0,<5.0.0
|
|
||||||
pytest-mock>=3.11.0,<4.0.0
|
|
||||||
|
|
||||||
# ───────────────────────────────────────────────────────────────────────
|
# ───────────────────────────────────────────────────────────────────────
|
||||||
# Optional dependencies — the code imports these inside try/except
|
# Optional dependencies — the code imports these inside try/except
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ def process_schema_file(schema_path: Path) -> bool:
|
|||||||
def main():
|
def main():
|
||||||
"""Main entry point."""
|
"""Main entry point."""
|
||||||
project_root = Path(__file__).parent.parent
|
project_root = Path(__file__).parent.parent
|
||||||
plugins_dir = project_root / 'plugins'
|
plugins_dir = project_root / 'plugin-repos'
|
||||||
|
|
||||||
if not plugins_dir.exists():
|
if not plugins_dir.exists():
|
||||||
print(f"Error: Plugins directory not found: {plugins_dir}")
|
print(f"Error: Plugins directory not found: {plugins_dir}")
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ def analyze_schema(schema_path: Path) -> Dict[str, Any]:
|
|||||||
def main():
|
def main():
|
||||||
"""Main analysis function."""
|
"""Main analysis function."""
|
||||||
project_root = Path(__file__).parent.parent
|
project_root = Path(__file__).parent.parent
|
||||||
plugins_dir = project_root / "plugins"
|
plugins_dir = project_root / "plugin-repos"
|
||||||
|
|
||||||
if not plugins_dir.exists():
|
if not plugins_dir.exists():
|
||||||
print(f"Plugins directory not found: {plugins_dir}")
|
print(f"Plugins directory not found: {plugins_dir}")
|
||||||
|
|||||||
@@ -1,117 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Script to safely remove plugin backup directories
|
|
||||||
# These were created during the plugin-to-submodule conversion
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
||||||
PLUGINS_DIR="$PROJECT_ROOT/plugins"
|
|
||||||
|
|
||||||
# Colors
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
RED='\033[0;31m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
log_info() {
|
|
||||||
echo -e "${GREEN}[INFO]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_warn() {
|
|
||||||
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_error() {
|
|
||||||
echo -e "${RED}[ERROR]${NC} $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Verify submodules are working
|
|
||||||
verify_submodules() {
|
|
||||||
log_info "Verifying submodules are working..."
|
|
||||||
local issues=0
|
|
||||||
|
|
||||||
for submod in football-scoreboard hockey-scoreboard ledmatrix-flights \
|
|
||||||
ledmatrix-leaderboard ledmatrix-stocks ledmatrix-weather \
|
|
||||||
mqtt-notifications; do
|
|
||||||
if [ ! -d "$PLUGINS_DIR/$submod" ]; then
|
|
||||||
log_error "Submodule directory missing: $submod"
|
|
||||||
issues=$((issues + 1))
|
|
||||||
elif [ ! -f "$PLUGINS_DIR/$submod/.git" ]; then
|
|
||||||
log_error "Submodule .git file missing: $submod"
|
|
||||||
issues=$((issues + 1))
|
|
||||||
elif [ ! -f "$PLUGINS_DIR/$submod/manifest.json" ]; then
|
|
||||||
log_warn "Submodule manifest missing: $submod (may be OK)"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ $issues -eq 0 ]; then
|
|
||||||
log_info "All submodules verified ✓"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
log_error "Found $issues issues with submodules"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Remove backup directories
|
|
||||||
remove_backups() {
|
|
||||||
log_info "Removing backup directories..."
|
|
||||||
|
|
||||||
local removed=0
|
|
||||||
local total_size=0
|
|
||||||
|
|
||||||
for backup in "$PLUGINS_DIR"/*.backup*; do
|
|
||||||
if [ -d "$backup" ]; then
|
|
||||||
local name=$(basename "$backup")
|
|
||||||
local size=$(du -sb "$backup" 2>/dev/null | awk '{print $1}')
|
|
||||||
total_size=$((total_size + size))
|
|
||||||
|
|
||||||
log_info "Removing: $name"
|
|
||||||
rm -rf "$backup"
|
|
||||||
removed=$((removed + 1))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [ $removed -gt 0 ]; then
|
|
||||||
log_info "Removed $removed backup directory(ies)"
|
|
||||||
log_info "Freed approximately $(numfmt --to=iec-i --suffix=B $total_size 2>/dev/null || echo "$total_size bytes")"
|
|
||||||
else
|
|
||||||
log_info "No backup directories found"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main
|
|
||||||
main() {
|
|
||||||
cd "$PROJECT_ROOT"
|
|
||||||
|
|
||||||
echo "=== Plugin Backup Removal Script ==="
|
|
||||||
echo
|
|
||||||
|
|
||||||
# Verify submodules first
|
|
||||||
if ! verify_submodules; then
|
|
||||||
log_error "Submodule verification failed. Not removing backups."
|
|
||||||
log_warn "Please fix submodule issues before removing backups."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo
|
|
||||||
log_warn "This will permanently delete backup directories:"
|
|
||||||
ls -1d "$PLUGINS_DIR"/*.backup* 2>/dev/null | sed 's|.*/| - |' || echo " (none found)"
|
|
||||||
echo
|
|
||||||
|
|
||||||
read -p "Continue? (y/N): " -n 1 -r
|
|
||||||
echo
|
|
||||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
||||||
log_info "Aborted"
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
remove_backups
|
|
||||||
|
|
||||||
log_info "Done!"
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
|
|
||||||
@@ -2299,56 +2299,6 @@ function renderArrayObjectItem(fieldId, fullKey, itemProperties, itemValue, inde
|
|||||||
|
|
||||||
|
|
||||||
// Functions to handle patternProperties key-value pairs
|
// Functions to handle patternProperties key-value pairs
|
||||||
window.addKeyValuePair = function(fieldId, fullKey, maxProperties) {
|
|
||||||
const pairsContainer = document.getElementById(fieldId + '_pairs');
|
|
||||||
if (!pairsContainer) return;
|
|
||||||
|
|
||||||
const currentPairs = pairsContainer.querySelectorAll('.key-value-pair');
|
|
||||||
if (currentPairs.length >= maxProperties) {
|
|
||||||
alert(`Maximum ${maxProperties} entries allowed`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newIndex = currentPairs.length;
|
|
||||||
const valueType = 'string'; // Default to string, could be determined from schema
|
|
||||||
|
|
||||||
const pairHtml = `
|
|
||||||
<div class="flex items-center gap-2 key-value-pair" data-index="${newIndex}">
|
|
||||||
<input type="text"
|
|
||||||
name="${fullKey}[key_${newIndex}]"
|
|
||||||
value=""
|
|
||||||
placeholder="Key"
|
|
||||||
class="flex-1 px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
|
||||||
data-key-index="${newIndex}"
|
|
||||||
onchange="updateKeyValuePairData('${fieldId}', '${fullKey}')">
|
|
||||||
<input type="text"
|
|
||||||
name="${fullKey}[value_${newIndex}]"
|
|
||||||
value=""
|
|
||||||
placeholder="Value"
|
|
||||||
class="flex-1 px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500 sm:text-sm"
|
|
||||||
data-value-index="${newIndex}"
|
|
||||||
onchange="updateKeyValuePairData('${fieldId}', '${fullKey}')">
|
|
||||||
<button type="button"
|
|
||||||
onclick="removeKeyValuePair('${fieldId}', ${newIndex})"
|
|
||||||
class="px-3 py-2 text-red-600 hover:text-red-800 hover:bg-red-50 rounded-md transition-colors"
|
|
||||||
title="Remove">
|
|
||||||
<i class="fas fa-trash"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
pairsContainer.insertAdjacentHTML('beforeend', pairHtml);
|
|
||||||
updateKeyValuePairData(fieldId, fullKey);
|
|
||||||
|
|
||||||
// Update add button state
|
|
||||||
const addButton = pairsContainer.nextElementSibling;
|
|
||||||
if (addButton && currentPairs.length + 1 >= maxProperties) {
|
|
||||||
addButton.disabled = true;
|
|
||||||
addButton.style.opacity = '0.5';
|
|
||||||
addButton.style.cursor = 'not-allowed';
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
window.removeKeyValuePair = function(fieldId, index) {
|
window.removeKeyValuePair = function(fieldId, index) {
|
||||||
const pairsContainer = document.getElementById(fieldId + '_pairs');
|
const pairsContainer = document.getElementById(fieldId + '_pairs');
|
||||||
if (!pairsContainer) return;
|
if (!pairsContainer) return;
|
||||||
@@ -4559,23 +4509,6 @@ function formatDate(dateString) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatCommit(commit, branch) {
|
|
||||||
const shortCommit = commit ? String(commit).substring(0, 7) : '';
|
|
||||||
const branchText = branch ? String(branch) : '';
|
|
||||||
|
|
||||||
if (branchText && shortCommit) {
|
|
||||||
return `${branchText} · ${shortCommit}`;
|
|
||||||
}
|
|
||||||
if (branchText) {
|
|
||||||
return branchText;
|
|
||||||
}
|
|
||||||
if (shortCommit) {
|
|
||||||
return shortCommit;
|
|
||||||
}
|
|
||||||
return 'Latest';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if plugin is new (updated within last 7 days)
|
|
||||||
function isNewPlugin(lastUpdated) {
|
function isNewPlugin(lastUpdated) {
|
||||||
if (!lastUpdated) return false;
|
if (!lastUpdated) return false;
|
||||||
|
|
||||||
@@ -4605,26 +4538,6 @@ function debounce(func, wait) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Toggle password visibility for secret fields
|
// Toggle password visibility for secret fields
|
||||||
function togglePasswordVisibility(fieldId) {
|
|
||||||
const input = document.getElementById(fieldId);
|
|
||||||
const icon = document.getElementById(fieldId + '-icon');
|
|
||||||
|
|
||||||
if (input && icon) {
|
|
||||||
if (input.type === 'password') {
|
|
||||||
input.type = 'text';
|
|
||||||
icon.classList.remove('fa-eye');
|
|
||||||
icon.classList.add('fa-eye-slash');
|
|
||||||
} else {
|
|
||||||
input.type = 'password';
|
|
||||||
icon.classList.remove('fa-eye-slash');
|
|
||||||
icon.classList.add('fa-eye');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GitHub Token Configuration Functions
|
|
||||||
// Open GitHub Token Settings panel (only opens, doesn't close)
|
|
||||||
// Used when user clicks "Configure Token" link
|
|
||||||
window.openGithubTokenSettings = function() {
|
window.openGithubTokenSettings = function() {
|
||||||
const settings = document.getElementById('github-token-settings');
|
const settings = document.getElementById('github-token-settings');
|
||||||
const warning = document.getElementById('github-auth-warning');
|
const warning = document.getElementById('github-auth-warning');
|
||||||
|
|||||||
@@ -1013,7 +1013,11 @@
|
|||||||
<!-- Reusable JSON file manager widget (used by of-the-day and others via x-widget: json-file-manager) -->
|
<!-- Reusable JSON file manager widget (used by of-the-day and others via x-widget: json-file-manager) -->
|
||||||
<script src="{{ url_for('static', filename='v3/js/widgets/json-file-manager.js') }}" defer></script>
|
<script src="{{ url_for('static', filename='v3/js/widgets/json-file-manager.js') }}" defer></script>
|
||||||
|
|
||||||
<!-- Legacy plugins_manager.js (for backward compatibility during migration) -->
|
<!-- plugins_manager.js: loaded LAST on purpose — it defines the live
|
||||||
|
window.* implementations for several plugin actions (installPlugin,
|
||||||
|
uninstallPlugin, executePluginAction, ...) and intentionally wins
|
||||||
|
over same-named definitions in js/app.js and js/app-shell.js. Do not
|
||||||
|
remove or reorder without resolving that overlap first. -->
|
||||||
<script src="{{ url_for('static', filename='v3/plugins_manager.js') }}?v=20260307" defer></script>
|
<script src="{{ url_for('static', filename='v3/plugins_manager.js') }}?v=20260307" defer></script>
|
||||||
|
|
||||||
<!-- Custom feeds table helpers live in js/widgets/custom-feeds.js (the
|
<!-- Custom feeds table helpers live in js/widgets/custom-feeds.js (the
|
||||||
|
|||||||
Reference in New Issue
Block a user