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>
114 lines
3.3 KiB
Python
Executable File
114 lines
3.3 KiB
Python
Executable File
#!/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())
|