Files
unsupervised-scheduler/docs/features/plugin-self-update.md
T
thatguygriffandClaude Fable 5 ab055c7a0c
CI / Tests (PHP 8.1) (pull_request) Successful in 45s
CI / Tests (PHP 8.2) (pull_request) Successful in 44s
CI / No Debug Code (pull_request) Successful in 2s
CI / Coding Standards (pull_request) Successful in 2m43s
CI / PHPStan (pull_request) Successful in 2m51s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m34s
CI / Build Plugin Zip (pull_request) Skipped
Serve plugin updates from tagged Gitea releases
Closes #65

Declare an Update URI header and answer core's update_plugins_{hostname}
filter from a new Update\UpdateChecker that offers the latest published
Gitea release's zip asset when it is newer than the installed version,
with transient caching and silent degradation on API failures.

Add a release workflow that fires on v* tag pushes: verifies the tag
matches the plugin Version header, runs the tests, builds the plugin zip,
and attaches it to the release (reusing a UI-created release, flagging
hyphenated versions as pre-release so /releases/latest skips them).

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-07-18 11:09:50 -03:00

3.2 KiB

Feature: Plugin Self-Update from Gitea Releases

Overview

WordPress sites running this plugin receive updates directly from the Gitea repository's releases — no wordpress.org listing and no manual zip uploads. Publishing a release is the whole deploy: bump the version, merge to main, tag vX.Y.Z in Gitea. Every site sees the update on its next check and can install it with one click, or unattended if the site admin enables auto-updates for the plugin.

How It Works

Release side (.gitea/workflows/release.yml)

Pushing a v* tag (including tags created through Gitea's "New Release" UI) triggers the release workflow, which:

  1. Fails if the tag does not match the Version: plugin header — a mismatch would make sites see a phantom update forever, or never see a real one.
  2. Runs the test suite.
  3. Builds the distributable zip via composer build (bin/build-zip.sh): a single top-level unsupervised-schedular/ folder with a production (no-dev) Composer autoloader.
  4. Creates the release for the tag (or reuses one created via the UI) and attaches the zip as a release asset. Versions containing a hyphen (e.g. 1.2.3-rc.1) are flagged as pre-releases.

The attached asset — not Gitea's auto-generated source archive — is the update package. Source archives have the wrong top-level folder name and no vendor/ directory, so WordPress could not install them.

Site side (src/Update/UpdateChecker.php)

The plugin header declares:

Update URI: https://git.unsupervised.ca/Unsupervised/unsupervised-scheduler

Since WP 5.8 that header both blocks wordpress.org from ever serving an update for a same-slug plugin and makes core fire the update_plugins_git.unsupervised.ca filter during update checks. UpdateChecker (registered in Plugin::boot()) answers that filter:

  1. Fetches GET /api/v1/repos/Unsupervised/unsupervised-scheduler/releases/latest (anonymous — the repo is public). The /latest endpoint excludes drafts and pre-releases, so -rc builds are never offered to sites.
  2. Caches the result (including failures) in the us_schedular_latest_release transient for 6 hours.
  3. Strips the leading v from the tag and compares against USC_VERSION with version_compare; PHP orders 1.0.0-rc.2 < 1.0.0 correctly.
  4. When newer, returns the release's first .zip asset as the update package. Core takes over from there: Plugins-screen notice, one-click update, and WP-Cron auto-updates if enabled.

Any API failure, malformed response, or asset-less release degrades to "no update available" — never an error surfaced to the site.

Cutting a Release

  1. Bump the version in unsupervised-schedular.php (both the Version: header and the USC_VERSION constant) and merge to main.
  2. Tag the merge commit vX.Y.Z — via Gitea's New Release UI or git tag vX.Y.Z && git push origin vX.Y.Z.
  3. The release workflow attaches the zip; sites pick the update up on their next check (twice daily via cron, or immediately from Dashboard → Updates → Check again).

Classes

Class Responsibility
Update\UpdateChecker Answers core's update_plugins_{hostname} filter from the Gitea releases API

Tests

  • tests/Unit/Update/UpdateCheckerTest.php