Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4.9 KiB
Using this setup in another plugin
Everything here was built to be lifted into the next WordPress plugin. Nothing
under ci/ knows this plugin's name except ci/plugin.conf.
There is no shared repository and no reusable workflow to uses:. That is
deliberate: these repositories live on two forges, and a cross-forge
workflow_call resolves on GitHub and fails on Gitea. Copying six files and
editing one is a smaller cost than a dependency that works in half the places
it runs.
The port
1. Copy these, unchanged:
ci/ every script; plugin.conf is the only one you edit
.github/workflows/ ci.yml, compat.yml, release.yml, auto-merge.yml
.gitea/workflows/ ci.yml, compat.yml, release.yml, README.md
.distignore
2. Rewrite ci/plugin.conf — five values, all commented in the file:
PLUGIN_SLUG=your-plugin
PLUGIN_MAIN=your-plugin.php
TEXT_DOMAIN=your-plugin
NEEDS_WOOCOMMERCE=yes # no, for a plugin that does not need it
SITE_TESTS="tests/fixtures.php tests/smoke.php"
3. Adjust .distignore for that plugin's internal documents. Everything
left in wp-content/plugins/ can in principle be fetched over HTTP from a live
shop, so notes and tooling stay out of the zip.
4. Write tests/fixtures.php and tests/smoke.php. These are the only
genuinely new work. Use this plugin's as the shape: wc_edm_ok() /
wc_edm_is() accumulate failures so one run reports all of them, and the file
exits non-zero at the end. They run under wp eval-file, so WordPress and
WooCommerce are fully loaded and every API is the real one.
5. Copy includes/class-updater.php and rename the class, the TRANSIENT
constant, the plugin-file constant and the user-agent string. Then add to the
plugin header:
Update URI: https://gitea.nolimit.dk/kw/your-plugin
and instantiate the updater before any "is WooCommerce active" gate — a shop with WooCommerce switched off for an afternoon must not quietly stop receiving fixes.
6. Make the Gitea repository public, or point Update URI at a separate
public releases-only repository. Every shop reads that feed without
credentials; ci/publish-release.sh fails the release with this instruction if
it cannot.
What you get
ci/static.sh— syntax on the oldest and newest PHP, the direct-access guard on every shipped file, no debug leftovers, no short tags, text domain on every translation call, one version stated once across header, constant and readme, and a warning when the compatibility headers have gone stale.ci/site-test.sh— a real WordPress + WooCommerce at any versions you name, includingnightlyandbeta, failing on any deprecation from your own code.ci/build-zip.sh— an installable zip, checked for the main file, for a single top-level directory named for the slug, and for leaked tooling.compat.yml— the weekly run against the next WordPress and the next WooCommerce, and a pull request that bumps the compatibility headers once that run is green.release.yml— tag, test, build, publish, then read the feed back with no credentials to prove a shop can download it.ci/update-smoke.sh— install the previous version into a throwaway shop and let WordPress update it by itself. The only check that proves the whole chain.ci/workflows-in-sync.sh— fails when the two workflow roots drift, which is the only way to notice that a workflow stopped running on Gitea.
Two guards worth understanding before you move them
.gitea/workflows/ hides .github/workflows/ completely. Gitea uses the
former if it exists and never merges the two. Adding one Gitea-only workflow
switches off CI for the whole repository there, with no error and no run to
click on — it looks exactly like a runner that has not picked the job up. Every
workflow that must run on Gitea has to be in that directory.
The release reads its own feed anonymously afterwards. A private repository and a release with no zip attached both produce a page that looks perfect to anyone logged in, and an update that no shop is ever offered. A check that sends a token is the false positive that hides exactly that.
A forge's own download URL is not the address your users can reach.
browser_download_url is built from the forge's configured root URL;
gitea.nolimit.dk reports http://57.129.89.117:25001/... there. The updater
rebuilds the address from the Update URI header and keeps only the path. This
one hides particularly well: a CI job on the same network reaches that raw IP,
so following the reported URL goes green and only a real client outside fails.
Candidates already in the fleet
spool and subscriptions are both WooCommerce plugins with some of this
already: spool has an earlier version of the throwaway-site script, and
subscriptions has the hygiene checks and a .distignore. Neither has the
version matrix, the compatibility schedule or the updater. Porting them is
steps 1–5 above.