WordPress only renders the "Enable auto-updates" toggle for a plugin that appears in the update_plugins transient's response or no_update list, which is what sets core's update-supported flag. UpdateChecker only populated the response side (when a newer release existed), so between releases the plugin was absent from the transient and the toggle never showed. provideUpdate() now returns a no_update payload (installed version, empty package) whenever no newer release is offered — including when the release lookup fails — so the plugin stays in the transient and the toggle appears. The response path (one-click and unattended updates) is unchanged. Bumps to 1.1.1 so the fix ships to installed sites via the self-updater. Co-Authored-By: Claude Opus 4.8 <[email protected]>
3.7 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:
- 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. - Runs the test suite.
- Builds the distributable zip via
composer build(bin/build-zip.sh): a single top-levelunsupervised-schedular/folder with a production (no-dev) Composer autoloader. - 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:
- Fetches
GET /api/v1/repos/Unsupervised/unsupervised-scheduler/releases/latest(anonymous — the repo is public). The/latestendpoint excludes drafts and pre-releases, so-rcbuilds are never offered to sites. - Caches the result (including failures) in the
us_schedular_latest_releasetransient for 6 hours. - Strips the leading
vfrom the tag and compares againstUSC_VERSIONwithversion_compare; PHP orders1.0.0-rc.2 < 1.0.0correctly. - When newer, returns the release's first
.zipasset as the update package. Core takes over from there: Plugins-screen notice, one-click update, and WP-Cron auto-updates if enabled. - When not newer — the site is current, or the lookup failed — returns a
no_updatepayload (installed version, empty package). This keeps the plugin in core'supdate_pluginstransient so core'supdate-supportedflag stays set and the Enable auto-updates toggle shows on the Plugins screen. Without it, an off-directory plugin is absent from the transient between releases and the toggle never appears.
Any API failure, malformed response, or asset-less release degrades to
"no update available" (the no_update payload) — never an error surfaced
to the site, and never a lost auto-update toggle during a Gitea blip.
Cutting a Release
- Bump the version in
unsupervised-schedular.php(both theVersion:header and theUSC_VERSIONconstant) and merge tomain. - Tag the merge commit
vX.Y.Z— via Gitea's New Release UI orgit tag vX.Y.Z && git push origin vX.Y.Z. - 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