feat(catalog): publish per-plugin release history for older API levels

This commit is contained in:
Lemmy
2026-07-25 11:07:01 -04:00
parent 360462f5d3
commit 88bacfe613
+63
View File
@@ -15,6 +15,11 @@ REQUIRED_FIELDS = ("id", "name", "version", "author", "plugin_api", "tags")
OPTIONAL_STRING_FIELDS = ("license", "icon", "description")
OPTIONAL_BOOL_FIELDS = ("deprecated",)
# Oldest plugin API any supported Noctalia accepts (kOldestSupportedPluginApiVersion in the
# shell's src/scripting/plugin_api.h). Release rows below it can never be installed, so the
# history walk stops there.
OLDEST_SUPPORTED_PLUGIN_API = 3
def git_commit_time(path: Path, *extra_args: str) -> int | None:
"""Commit time in Unix seconds, or None when `path` has no matching commit.
@@ -30,6 +35,51 @@ def git_commit_time(path: Path, *extra_args: str) -> int | None:
return int(stdout) if stdout else None
def git_output(*args: str) -> str:
return subprocess.run(
["git", *args], capture_output=True, text=True, check=True
).stdout
def release_history(subdir: str, tip_api: int) -> list[dict]:
"""Older revisions of a plugin, one per API level below the tip's, newest first.
A Noctalia below the tip's `plugin_api` has nothing to install unless the catalog names
a revision it can run. Walking `<subdir>/plugin.toml` newest-first and keeping only the
revisions that lower the API level yields a strictly decreasing sequence, so each row is
the newest revision at or below its own level -- exactly what the host resolves against.
"""
releases = []
lowest_api = tip_api
revisions = git_output(
"log", "--format=%H", "--", f"{subdir}/plugin.toml"
).split()
for revision in revisions:
if lowest_api <= OLDEST_SUPPORTED_PLUGIN_API:
break
try:
manifest = tomllib.loads(
git_output("show", f"{revision}:{subdir}/plugin.toml")
)
except (subprocess.CalledProcessError, tomllib.TOMLDecodeError):
continue # unreadable or pre-`plugin_api` history
plugin_api = manifest.get("plugin_api")
version = manifest.get("version")
if not isinstance(plugin_api, int) or isinstance(plugin_api, bool):
continue
if not isinstance(version, str) or not version:
continue
if plugin_api >= lowest_api or plugin_api < OLDEST_SUPPORTED_PLUGIN_API:
continue
releases.append({"plugin_api": plugin_api, "version": version, "rev": revision})
lowest_api = plugin_api
return releases
def load_plugin_manifest(path: Path) -> dict:
with path.open("rb") as handle:
manifest = tomllib.load(handle)
@@ -91,6 +141,7 @@ def discover_plugins() -> list[dict]:
directory = manifest_path.parent.name
manifest["_directory"] = directory
manifest["_order"] = order.get(manifest["id"], len(order))
manifest["releases"] = release_history(directory, manifest["plugin_api"])
plugins.append(manifest)
plugins.sort(key=lambda plugin: (plugin["_order"], plugin["_directory"]))
@@ -113,6 +164,8 @@ def render_catalog(plugins: list[dict]) -> str:
"# Index of every plugin this source ships: the minimum needed to render, search,",
"# and compat-check the list. The per-plugin plugin.toml stays authoritative; the",
"# host re-reads it on enable. Keep one [[plugin]] row per plugin subdirectory.",
"# A [[plugin.release]] row names an older revision for a Noctalia below the tip's",
"# plugin_api, so an older release stays installable instead of the plugin vanishing.",
"",
]
@@ -147,6 +200,16 @@ def render_catalog(plugins: list[dict]) -> str:
+ "]",
]
)
for release in plugin["releases"]:
lines.extend(
[
"",
"[[plugin.release]]",
f"plugin_api = {release['plugin_api']}",
f"version = {toml_string(release['version'])}",
f"rev = {toml_string(release['rev'])}",
]
)
return "\n".join(lines) + "\n"