feat(catalog): publish per-plugin release history for older API levels
This commit is contained in:
@@ -15,6 +15,11 @@ REQUIRED_FIELDS = ("id", "name", "version", "author", "plugin_api", "tags")
|
|||||||
OPTIONAL_STRING_FIELDS = ("license", "icon", "description")
|
OPTIONAL_STRING_FIELDS = ("license", "icon", "description")
|
||||||
OPTIONAL_BOOL_FIELDS = ("deprecated",)
|
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:
|
def git_commit_time(path: Path, *extra_args: str) -> int | None:
|
||||||
"""Commit time in Unix seconds, or None when `path` has no matching commit.
|
"""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
|
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:
|
def load_plugin_manifest(path: Path) -> dict:
|
||||||
with path.open("rb") as handle:
|
with path.open("rb") as handle:
|
||||||
manifest = tomllib.load(handle)
|
manifest = tomllib.load(handle)
|
||||||
@@ -91,6 +141,7 @@ def discover_plugins() -> list[dict]:
|
|||||||
directory = manifest_path.parent.name
|
directory = manifest_path.parent.name
|
||||||
manifest["_directory"] = directory
|
manifest["_directory"] = directory
|
||||||
manifest["_order"] = order.get(manifest["id"], len(order))
|
manifest["_order"] = order.get(manifest["id"], len(order))
|
||||||
|
manifest["releases"] = release_history(directory, manifest["plugin_api"])
|
||||||
plugins.append(manifest)
|
plugins.append(manifest)
|
||||||
|
|
||||||
plugins.sort(key=lambda plugin: (plugin["_order"], plugin["_directory"]))
|
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,",
|
"# 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",
|
"# 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.",
|
"# 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"
|
return "\n".join(lines) + "\n"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user