fix: make the release workflow build and package a real Jellyfin plugin
All checks were successful
Build Plugin / build (push) Successful in 4m29s
Release Plugin / release (push) Successful in 1m7s

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:
2026-07-29 15:18:40 +02:00
parent 10ea812a3c
commit 4b827941ee
8 changed files with 476 additions and 44 deletions

113
scripts/update_manifest.py Executable file
View 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())