Rewrite plugin installer as a declarative manifest/engine framework for reliable mix-and-match plugin installs, with backup/restore support and assorted plugin fixes (Hardcover file API, Bookerly/Hardcover SettingAction registration, GitHubSync include path, Lockscreen render timing)

This commit is contained in:
Sterling Archer
2026-07-16 15:39:10 -07:00
parent 3ad7a7ab39
commit fec251f4d1
23 changed files with 2079 additions and 3243 deletions
+38
View File
@@ -0,0 +1,38 @@
import importlib.util
import os
from .engine import SelectedPlugin
def discover_all(plugins_dir):
"""Return {name: (plugin_dir, get_manifest_callable)} for every plugin
directory that has a plugin.py exposing get_manifest(ctx)."""
found = {}
for name in sorted(os.listdir(plugins_dir)):
plugin_dir = os.path.join(plugins_dir, name)
manifest_path = os.path.join(plugin_dir, "plugin.py")
if not os.path.isdir(plugin_dir) or not os.path.exists(manifest_path):
continue
spec = importlib.util.spec_from_file_location(f"xteink_plugin_{name}", manifest_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
if not hasattr(module, "get_manifest"):
continue
found[name] = (plugin_dir, module.get_manifest)
return found
def build_selected(names, plugins_dir, ctx):
"""Build a deterministic, sorted-by-name list of SelectedPlugin for the
given plugin names. Order doesn't affect correctness (the engine derives
shared files from the whole set at once) but keeping it stable makes
output/diffs reproducible."""
found = discover_all(plugins_dir)
selected = []
for name in sorted(names):
if name not in found:
raise KeyError(f"Unknown plugin: {name} (available: {', '.join(sorted(found))})")
plugin_dir, get_manifest = found[name]
manifest = get_manifest(ctx)
selected.append(SelectedPlugin(name=name, plugin_dir=plugin_dir, manifest=manifest))
return selected