fix: make the release workflow build and package a real Jellyfin plugin
The release workflow was copied from another project and could not succeed on any tag: it installed the .NET 8 SDK for a net9.0 project, published a JellyfinSso.csproj that does not exist, called a missing update_manifest.py, and zipped the entire publish tree instead of the three DLLs declared in build.yaml. Replace it with a tag-driven release: - Build with the .NET 9 SDK, injecting the tag version via -p:Version, -p:AssemblyVersion and -p:FileVersion so the assembly no longer carries a hardcoded 1.0.3.0. - Package via scripts/build_plugin.py, which emits the JPRM layout Jellyfin expects: the build.yaml artifacts plus a generated meta.json. The previous zips shipped without meta.json, which only worked for repository installs because Jellyfin synthesises one from the folder name. - Update manifest.json via scripts/update_manifest.py, keeping build.yaml as the single source of truth for plugin metadata. - Commit the zip and manifest to main and attach the zip to the Gitea release. Pushing uses the injected token, falling back to a RELEASE_TOKEN secret when the built-in one lacks write access. Also fix the published download URLs. manifest.json and the README pointed at pascallinxweiler/jellyfinplugin, which 404s; the repository is jellyfinsso, so the plugin repository could not be added to Jellyfin and no version could be downloaded. The workflow now derives the URL from GITHUB_REPOSITORY. Add a build workflow so main and pull requests are compiled and packaged. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
113
scripts/update_manifest.py
Executable file
113
scripts/update_manifest.py
Executable file
@@ -0,0 +1,113 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Add a version entry to the Jellyfin plugin repository manifest.
|
||||
|
||||
manifest.json is what Jellyfin fetches when the repository URL is added under
|
||||
Dashboard -> Plugins -> Repositories. Plugin metadata comes from build.yaml so
|
||||
there is a single source of truth.
|
||||
|
||||
Usage:
|
||||
scripts/update_manifest.py --version 1.0.4.0 --checksum <md5> --source-url <url>
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import datetime
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
BUILD_YAML = REPO_ROOT / "build.yaml"
|
||||
MANIFEST = REPO_ROOT / "manifest.json"
|
||||
|
||||
|
||||
def version_key(version):
|
||||
return tuple(int(part) for part in re.findall(r"\d+", version))
|
||||
|
||||
|
||||
def load_manifest():
|
||||
if not MANIFEST.is_file():
|
||||
return []
|
||||
with MANIFEST.open(encoding="utf-8") as handle:
|
||||
return json.load(handle)
|
||||
|
||||
|
||||
def find_plugin(manifest, guid):
|
||||
for plugin in manifest:
|
||||
if plugin.get("guid", "").lower() == guid.lower():
|
||||
return plugin
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--version", required=True)
|
||||
parser.add_argument("--checksum", required=True, help="md5 of the zip")
|
||||
parser.add_argument("--source-url", required=True, help="download URL for the zip")
|
||||
parser.add_argument("--changelog", default="")
|
||||
parser.add_argument("--timestamp", default="", help="ISO 8601 UTC; defaults to now")
|
||||
args = parser.parse_args()
|
||||
|
||||
with BUILD_YAML.open(encoding="utf-8") as handle:
|
||||
cfg = yaml.safe_load(handle)
|
||||
|
||||
timestamp = args.timestamp or (
|
||||
datetime.datetime.now(datetime.timezone.utc)
|
||||
.replace(tzinfo=None)
|
||||
.isoformat(timespec="seconds")
|
||||
+ "Z"
|
||||
)
|
||||
|
||||
manifest = load_manifest()
|
||||
plugin = find_plugin(manifest, cfg["guid"])
|
||||
if plugin is None:
|
||||
plugin = {"guid": cfg["guid"], "versions": []}
|
||||
manifest.append(plugin)
|
||||
|
||||
# Refresh the descriptive fields so build.yaml stays authoritative.
|
||||
plugin["name"] = cfg["name"]
|
||||
plugin["description"] = cfg["description"].strip()
|
||||
plugin["overview"] = cfg["overview"].strip()
|
||||
plugin["owner"] = cfg["owner"]
|
||||
plugin["category"] = cfg["category"]
|
||||
|
||||
entry = {
|
||||
"version": args.version,
|
||||
"changelog": (args.changelog or cfg.get("changelog", "")).strip(),
|
||||
"targetAbi": cfg["targetAbi"],
|
||||
"sourceUrl": args.source_url,
|
||||
"checksum": args.checksum,
|
||||
"timestamp": timestamp,
|
||||
}
|
||||
|
||||
versions = [v for v in plugin.get("versions", []) if v.get("version") != args.version]
|
||||
versions.append(entry)
|
||||
versions.sort(key=lambda v: version_key(v["version"]), reverse=True)
|
||||
plugin["versions"] = versions
|
||||
|
||||
# Key order matters only for readability; Jellyfin reads by name.
|
||||
ordered = [
|
||||
{
|
||||
key: plugin[key]
|
||||
for key in (
|
||||
"guid",
|
||||
"name",
|
||||
"description",
|
||||
"overview",
|
||||
"owner",
|
||||
"category",
|
||||
"versions",
|
||||
)
|
||||
if key in plugin
|
||||
}
|
||||
for plugin in manifest
|
||||
]
|
||||
|
||||
MANIFEST.write_text(json.dumps(ordered, indent=4) + "\n", encoding="utf-8")
|
||||
print(f"manifest.json now lists {args.version} at {args.source_url}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user