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 <cursoragent@cursor.com>
This commit is contained in:
pleb
2026-07-05 18:03:29 -07:00
parent a94a1ed000
commit 8ff20bd4e4
3 changed files with 21 additions and 4 deletions
+14
View File
@@ -114,6 +114,20 @@ completed successfully.
When no config file is present on Windows, defaults are When no config file is present on Windows, defaults are
`~/BSManager/BSInstances` and `%LOCALAPPDATA%/plugin-helper`. `~/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 ## Commands
For normal use, run the Textual menu from the repo root: For normal use, run the Textual menu from the repo root:
+1
View File
@@ -50,6 +50,7 @@ def installed_plugins_report(
"files": files, "files": files,
} }
) )
plugins.sort(key=lambda plugin: (plugin["name"].casefold(), plugin["id"]))
return { return {
"instance": installed_state.get("instance", lockfile.instance), "instance": installed_state.get("instance", lockfile.instance),
"beatSaberVersion": installed_state.get("beatSaberVersion", lockfile.beat_saber_version), "beatSaberVersion": installed_state.get("beatSaberVersion", lockfile.beat_saber_version),
+6 -4
View File
@@ -1541,17 +1541,19 @@ instance = "1.40.8"
self.assertEqual(report["plugins"][0]["asset"], "Example.dll") self.assertEqual(report["plugins"][0]["asset"], "Example.dll")
self.assertEqual(report["plugins"][0]["fileCount"], 1) 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( registry = Registry(
{ {
"alpha": RegistryPlugin(id="alpha", name="Alpha", repo="owner/alpha"), "alpha": RegistryPlugin(id="alpha", name="Alpha", repo="owner/alpha"),
"beta": RegistryPlugin(id="beta", name="Beta", repo="owner/beta"), "beta": RegistryPlugin(id="beta", name="Beta", repo="owner/beta"),
"zebra": RegistryPlugin(id="zebra", name="Zebra", repo="owner/zebra"),
} }
) )
lockfile = Lockfile( lockfile = Lockfile(
beat_saber_version="1.40.8", beat_saber_version="1.40.8",
instance="1.40.8", instance="1.40.8",
plugins=( 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="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"), 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, lockfile=lockfile,
) )
self.assertEqual([plugin["id"] for plugin in report["plugins"]], ["alpha", "beta"]) self.assertEqual([plugin["id"] for plugin in report["plugins"]], ["alpha", "beta", "zebra"])
self.assertEqual([plugin["status"] for plugin in report["plugins"]], ["disabled", "disabled"]) self.assertEqual([plugin["status"] for plugin in report["plugins"]], ["disabled", "disabled", "disabled"])
self.assertEqual([plugin["fileCount"] for plugin in report["plugins"]], [0, 0]) self.assertEqual([plugin["fileCount"] for plugin in report["plugins"]], [0, 0, 0])
self.assertEqual(report["plugins"][1]["version"], "v2.0.0") self.assertEqual(report["plugins"][1]["version"], "v2.0.0")
def test_update_check_reports_current_matching_asset(self) -> None: def test_update_check_reports_current_matching_asset(self) -> None: