Skip to content

Release process

Nils Lehnen edited this page Sep 6, 2026 · 1 revision

Release process

For whoever cuts a release. The authority is docs/RELEASING.md in the repository; this page is the same procedure for somebody reading it before they open the tree.

A release is published by pushing a tag. Nothing is created by hand.

The tag

X.Y.Z-stable or X.Y.Z.W-stable. The numeric part is the plugin version a Jellyfin server installs, and it must be exactly the version in build.yaml, written the same way with the same number of parts. The -stable suffix lives only in the tag and in the release name.

Cutting one

  1. Raise version in build.yaml on the release branch and merge it. The changelog entry moves under its heading in the same change: the newest ## X.Y.Z.W heading in CHANGELOG.md is what the support matrix is checked against, so a release is a heading before it is a tag.

  2. Check that the commit you want to release is on that branch.

  3. Read the pages, as below, and write down what was read.

  4. Push the tag for that commit, one at a time, waiting for each run to finish.

    git tag 0.1.1.0-stable <commit>
    git push origin 0.1.1.0-stable
    

The publish workflow takes it from there.

Reading the pages

Step 3 is the one thing in this process no run does. The suite never opens a browser.

What to open is derived rather than listed, because a list drifts against the project file that decides it: everything declared as an embedded page asset is served to somebody's browser by a running server, so everything it names is read. Build the plugin from the commit about to be tagged, install that build on a server, open each of those pages there, and write what was opened and which server it was opened on into the pull request that raised the version.

Nothing checks that any of this happened. No workflow reads it, no check looks for the record in that pull request, and a tag pushed by somebody who skipped it produces a release that looks exactly like one where the pages were read. The step is held by whoever cuts the release and by nothing else.

What the run produces

The workflow builds from the tagged commit, creates the GitHub release, and attaches five files: the plugin archive, the packaging metadata beside it, one .md5, one .sha256, and one .cdx.json bill of materials. The .md5 is the value a Jellyfin catalogue serves as the plugin checksum, and there is exactly one per release so that no generator can pair a checksum with the wrong file. A release short of one of the five is not a state this route can reach.

A separate job signs a build provenance statement for the archive, so a downloaded archive can be checked against the workflow that produced it. See Support matrix and releases.

Nothing here writes a plugin catalogue. A GitHub release is the whole output.

What fails the run, before anything is published

  • The tag does not end in -stable, or the run was started from something other than a tag.
  • The numeric part of the tag differs from version in build.yaml.
  • build.yaml is missing a required field, or version, targetAbi, framework or guid has the wrong shape.
  • framework names a target the plugin project is not built for.
  • A packaging manifest that shadows build.yaml is present.
  • build.yaml declares an image file that is not in the repository.
  • The tagged commit is not on a release branch, or the tag moved after the run started.
  • There is no packages.lock.json beside the plugin project.
  • The version stamped into the assembly is not the version in build.yaml.
  • The build produced no archive, more than one, or no packaging metadata.
  • The bill of materials job produced no document, or the release job downloaded none.
  • A release already exists for the tag.

Re-running

A release that exists is not touched again. The release job stops if one exists for the tag, and the upload step will not replace an asset of the same name. Replacing the bytes of a version people have already installed is the failure that prevents, and it is worth more than the convenience of a re-run.

So a release that went out with the wrong contents is not fixed in place: fix the problem, raise the version, push a new tag.

A run that failed before the release was created leaves the tag clean. Fix the cause and re-run, or delete and re-push the tag. A run that failed after the release was created is an incomplete release, and a re-run refuses it; what is possible then depends on whether immutable releases are on.

Clone this wiki locally