# 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`