From 8ff20bd4e431423340f1b4ce8e124ad00412a6f8 Mon Sep 17 00:00:00 2001 From: pleb Date: Sun, 5 Jul 2026 18:03:29 -0700 Subject: [PATCH] Sort plugin menu by name and document Windows build prerequisites List installed plugins alphabetically in the menu and installed report. Add a short README note on installing the .NET Framework 4.8 Developer Pack via winget. Co-authored-by: Cursor --- README.md | 14 ++++++++++++++ src/plugin_helper/reports.py | 1 + tests/test_plugin_helper.py | 10 ++++++---- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index be37b2b..b286de2 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,20 @@ completed successfully. When no config file is present on Windows, defaults are `~/BSManager/BSInstances` and `%LOCALAPPDATA%/plugin-helper`. +## Building plugins + +PC BSIPA plugins target .NET Framework (`net48`). On native Windows, install the +reference assemblies before `dotnet build`: + +```powershell +winget install -e --id Microsoft.DotNet.Framework.DeveloperPack_4 +``` + +Point `BeatSaberDir` at your BSManager instance and pass +`-p:DisableCopyToPlugins=True` so builds do not mutate the live install. See +[`.agents/skills/beatsaber-plugin-builder/`](.agents/skills/beatsaber-plugin-builder/) +for the full workflow. + ## Commands For normal use, run the Textual menu from the repo root: diff --git a/src/plugin_helper/reports.py b/src/plugin_helper/reports.py index 2788b1e..a3653f7 100644 --- a/src/plugin_helper/reports.py +++ b/src/plugin_helper/reports.py @@ -50,6 +50,7 @@ def installed_plugins_report( "files": files, } ) + plugins.sort(key=lambda plugin: (plugin["name"].casefold(), plugin["id"])) return { "instance": installed_state.get("instance", lockfile.instance), "beatSaberVersion": installed_state.get("beatSaberVersion", lockfile.beat_saber_version), diff --git a/tests/test_plugin_helper.py b/tests/test_plugin_helper.py index 6c5111a..8aa943c 100644 --- a/tests/test_plugin_helper.py +++ b/tests/test_plugin_helper.py @@ -1541,17 +1541,19 @@ instance = "1.40.8" self.assertEqual(report["plugins"][0]["asset"], "Example.dll") self.assertEqual(report["plugins"][0]["fileCount"], 1) - def test_installed_plugins_report_starts_from_version_lock(self) -> None: + def test_installed_plugins_report_sorts_by_name(self) -> None: registry = Registry( { "alpha": RegistryPlugin(id="alpha", name="Alpha", repo="owner/alpha"), "beta": RegistryPlugin(id="beta", name="Beta", repo="owner/beta"), + "zebra": RegistryPlugin(id="zebra", name="Zebra", repo="owner/zebra"), } ) lockfile = Lockfile( beat_saber_version="1.40.8", instance="1.40.8", plugins=( + LockedPlugin(id="zebra", repo="owner/zebra", tag="v3.0.0", asset="Zebra.dll", sha256="z"), LockedPlugin(id="alpha", repo="owner/alpha", tag="v1.0.0", asset="Alpha.dll", sha256="a"), LockedPlugin(id="beta", repo="owner/beta", tag="v2.0.0", asset="Beta.dll", sha256="b"), ), @@ -1563,9 +1565,9 @@ instance = "1.40.8" lockfile=lockfile, ) - self.assertEqual([plugin["id"] for plugin in report["plugins"]], ["alpha", "beta"]) - self.assertEqual([plugin["status"] for plugin in report["plugins"]], ["disabled", "disabled"]) - self.assertEqual([plugin["fileCount"] for plugin in report["plugins"]], [0, 0]) + self.assertEqual([plugin["id"] for plugin in report["plugins"]], ["alpha", "beta", "zebra"]) + self.assertEqual([plugin["status"] for plugin in report["plugins"]], ["disabled", "disabled", "disabled"]) + self.assertEqual([plugin["fileCount"] for plugin in report["plugins"]], [0, 0, 0]) self.assertEqual(report["plugins"][1]["version"], "v2.0.0") def test_update_check_reports_current_matching_asset(self) -> None: