Auto-update the plugin from tagged Gitea releases #66

Merged
thatguygriff merged 1 commits from feature/plugin-self-update into main 2026-07-18 14:22:06 +00:00
Owner

Closes #65

What & why

Deploys currently mean downloading a CI artifact zip and uploading it into wp-admin by hand. After this change, tagging vX.Y.Z in Gitea (or creating a release in the UI) is the deploy: the release appears in the sidebar with the built plugin zip attached, and every installed site picks the update up through WordPress's normal update mechanism — one click, or unattended with auto-updates enabled.

How it works

Site side

  • New Update URI: https://git.unsupervised.ca/Unsupervised/unsupervised-scheduler plugin header. Since WP 5.8 this blocks wordpress.org from ever serving a same-slug update and makes core fire the update_plugins_git.unsupervised.ca filter during update checks.
  • New Update\UpdateChecker (wired in Plugin::boot()) answers that filter: fetches the Gitea releases/latest API (which excludes drafts and pre-releases, so -rc builds are never offered), caches the lookup — including failures — in a transient for 6 hours, strips the v from the tag, and offers the release's .zip asset when version_compare says it's newer than USC_VERSION. Any API failure, malformed body, or asset-less release degrades silently to "no update". Core handles everything downstream (Plugins-screen notice, one-click update, WP-Cron auto-updates).
  • Transient cleaned up in uninstall.php.

Release side

  • New .gitea/workflows/release.yml fires on v* tag pushes, including tags created through Gitea's "New Release" UI (it reuses that release instead of erroring). It fails fast if the tag doesn't match the plugin Version: header — a mismatch would make sites see a phantom update forever or never see a real one — then runs the tests, builds the zip via composer build, and attaches it as a release asset. The asset matters: Gitea's auto-generated source archives have the wrong top-level folder and no vendor/, so WordPress couldn't install them. Hyphenated versions (1.2.3-rc.1) are flagged pre-release. Uses the runner's auto-injected GITHUB_TOKEN; no secret setup needed.

Release checklist (documented in docs/features/plugin-self-update.md)

  1. Bump Version: header and USC_VERSION in unsupervised-schedular.php, merge to main.
  2. Tag the merge commit vX.Y.Z.
  3. Sites see the update within ~12 h (or immediately via Dashboard → Updates → Check Again).

Tests

tests/Unit/Update/UpdateCheckerTest.php — 9 tests covering newer/equal/older versions, v-prefix stripping, other-plugins passthrough, transient caching (including negative caching of API failures), non-200 responses, malformed JSON, zip-asset selection, and asset-less releases.

  • composer test — 361 tests pass
  • composer lint — PHPStan level 10 clean
  • composer cs — PHPCS clean

No local WordPress runtime exists in this repo (tests stub WP via Brain\Monkey), and the release workflow can only truly run on a real tag push. Suggested smoke test after merge: tag v1.0.0-rc.3, confirm the workflow attaches the zip and flags it pre-release, then install the current rc on a staging site and tag a full v1.0.0 to watch the update appear.

🤖 Generated with Claude Code

Closes #65 ## What & why Deploys currently mean downloading a CI artifact zip and uploading it into wp-admin by hand. After this change, tagging `vX.Y.Z` in Gitea (or creating a release in the UI) **is** the deploy: the release appears in the sidebar with the built plugin zip attached, and every installed site picks the update up through WordPress's normal update mechanism — one click, or unattended with auto-updates enabled. ## How it works **Site side** - New `Update URI: https://git.unsupervised.ca/Unsupervised/unsupervised-scheduler` plugin header. Since WP 5.8 this blocks wordpress.org from ever serving a same-slug update and makes core fire the `update_plugins_git.unsupervised.ca` filter during update checks. - New `Update\UpdateChecker` (wired in `Plugin::boot()`) answers that filter: fetches the Gitea `releases/latest` API (which excludes drafts and pre-releases, so `-rc` builds are never offered), caches the lookup — including failures — in a transient for 6 hours, strips the `v` from the tag, and offers the release's `.zip` asset when `version_compare` says it's newer than `USC_VERSION`. Any API failure, malformed body, or asset-less release degrades silently to "no update". Core handles everything downstream (Plugins-screen notice, one-click update, WP-Cron auto-updates). - Transient cleaned up in `uninstall.php`. **Release side** - New `.gitea/workflows/release.yml` fires on `v*` tag pushes, including tags created through Gitea's "New Release" UI (it reuses that release instead of erroring). It fails fast if the tag doesn't match the plugin `Version:` header — a mismatch would make sites see a phantom update forever or never see a real one — then runs the tests, builds the zip via `composer build`, and attaches it as a release asset. The asset matters: Gitea's auto-generated source archives have the wrong top-level folder and no `vendor/`, so WordPress couldn't install them. Hyphenated versions (`1.2.3-rc.1`) are flagged pre-release. Uses the runner's auto-injected `GITHUB_TOKEN`; no secret setup needed. ## Release checklist (documented in `docs/features/plugin-self-update.md`) 1. Bump `Version:` header and `USC_VERSION` in `unsupervised-schedular.php`, merge to `main`. 2. Tag the merge commit `vX.Y.Z`. 3. Sites see the update within ~12 h (or immediately via Dashboard → Updates → Check Again). ## Tests `tests/Unit/Update/UpdateCheckerTest.php` — 9 tests covering newer/equal/older versions, `v`-prefix stripping, other-plugins passthrough, transient caching (including negative caching of API failures), non-200 responses, malformed JSON, zip-asset selection, and asset-less releases. - `composer test` — 361 tests pass - `composer lint` — PHPStan level 10 clean - `composer cs` — PHPCS clean > No local WordPress runtime exists in this repo (tests stub WP via Brain\Monkey), and the release workflow can only truly run on a real tag push. Suggested smoke test after merge: tag `v1.0.0-rc.3`, confirm the workflow attaches the zip and flags it pre-release, then install the current rc on a staging site and tag a full `v1.0.0` to watch the update appear. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
thatguygriff added 1 commit 2026-07-18 14:10:39 +00:00
Serve plugin updates from tagged Gitea releases
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
ab055c7a0c
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]>
thatguygriff merged commit 8d79fbdb1f into main 2026-07-18 14:22:06 +00:00
thatguygriff deleted branch feature/plugin-self-update 2026-07-18 14:22:06 +00:00
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Unsupervised/unsupervised-scheduler#66