This satis exposes Composer packages for plugins we use across Genero projects. There are two distinct mechanisms depending on how upstream distributes the plugin:
Genero-owned fork (default) — a
generoi/<slug>repo holds a curatedcomposer.jsondeclaringtype: wordpress-pluginplus a CI workflow that polls upstream, commits new versions, tags, and triggers a satis rebuild. Listed underrepositoriesinsatis.jsonastype: vcs. The release zip URL is rewritten post-build byrewrite-dist-urls.jswhen entries are present inrelease-dist.json.Direct GitHub-release mirror — for plugins that already publish signed, prebuilt release zips and don't need a Genero fork (typically open-distribution but closed-source). Listed in
release-packages.json;generate-release-packages.jsruns before satis build, queries the GitHub API for the source repo's releases, and synthesises apackage-type entry per stable release withdist.urlpointing at the asset on GitHub. The Satis Build workflow runs daily so new upstream releases get picked up automatically.
Create repository with a basic
composer.json{ "name": "generoi/<plugin-slug>", "type": "wordpress-plugin", "description": "Plugin name", "homepage": "https://woocommerce.com/products/..." }Add
.github/workflows/build.ymlwhich checks for plugin updates, makes releases and triggers packagist update (generoi/github-action-update-pluginsREADME has morere examples).name: Buildon: workflow_dispatch: schedule: - cron: '5 4 * * *'jobs: build: uses: generoi/github-action-update-plugins/.github/workflows/wccom-update.yml@mastersecrets: ACCESS_TOKEN: ${{ secrets.WCCOM_ACCESS_TOKEN }}ACCESS_TOKEN_SECRET: ${{ secrets.WCCOM_ACCESS_TOKEN_SECRET }}with: slug: 'woocommerce-subscriptions'changelog_extract: "'/[0-9\\-]+ - version/ { if (p) { exit }; if ($4 == ver) { p=1; next } } p && NF' changelog.txt"update-satis: needs: buildif: needs.build.outputs.updated == 'true'uses: generoi/packagist/.github/workflows/update.yml@mastersecrets: token: ${{ secrets.PACKAGIST_UPDATE_PAT }}
Add
@generoi/deployas a collaborator withreadaccess. Do NOT grant write access.Nothing else is needed for the satis build itself:
genero-composer-botis installed on all repositories, so a new mirror is covered the moment it exists. If that installation is ever narrowed to selected repositories, the build fails naming the repo up front, rather than partway through the satis run.The collaborator step is still required because consuming projects authenticate Composer with
PACKAGIST_GITHUB_TOKEN, a PAT on that machine user. It goes away once site repos move onto the app too.Optional. Change the
PACKAGIST_UPDATE_PATsecret permissions to be allowed to be used by the repository. This only buys an immediate satis rebuild after a release — skip it and the plugin still gets indexed by the next scheduled build (every 3h). When the secret isn't granted,update.ymlemits a warning and exits green rather than failing the plugin repo's build.Add the plugin to
satis.jsonin this repository.Trigger an initial build which will download the latest plugin version, commit it, tag it, push it, release it and if successful trigger a rebuild in this repository.
Whenever a new version is found using the cron schedule, the plugin will be updated, released and finally a rebuild of this repository will be once again triggered.
Two independent paths, so no single missing credential can strand a release:
- Push-based (fast, best-effort) — the plugin repo's
update-satisjob callsupdate.yml, which POSTs arepository_dispatchhere and rebuilds within a minute. NeedsPACKAGIST_UPDATE_PAT(step 4 above). - Scheduled (slower, guaranteed) —
satis.ymlruns every 3h and re-reads the tags of everytype: vcsentry plus every upstream release inrelease-packages.json. Requires no per-repo config.
Need it indexed right now and the push path isn't wired up? Just run the build
manually: gh workflow run satis.yml --repo generoi/packagist.
A build that reads every mirror successfully can still come out short. Composer
records a 404 on an individual tag's composer.json and carries on — only 401,
403, 429 and 5xx are rethrown — so one flaky request drops one version while the
build stays green. Publishing wipes p2/ and copies the build over it, so that
short result would replace good metadata, and downstream it reads as an
unresolvable plugin rather than as a failed build.
check-index-shrink.js compares the build against
what is already published and fails before the publish step if any package or
tagged version went missing. ~dev files are ignored; branches come and go.
- A transient 404. Just re-run the build. It normally passes.
- A package you removed from
satis.json. Not a failure — the check reads the source repository off the published versions, sees it is no longer declared, and lets it through. - A package renamed in its own
composer.json. Reported as disappeared, because from here it is indistinguishable from a mirror that stopped resolving. Confirm the rename, then re-run.
Use this when upstream already publishes a versioned release zip on
GitHub and we don't need a Genero-owned fork (no license key to inject,
no proprietary metadata, no custom composer.json).
Verify the upstream repo publishes a stable asset filename on every release (e.g.
altcha.zipforaltcha-org/altcha-wordpress-next). Inspect a recent release page to confirm.Add an entry to
release-packages.json:{ "altcha-org/altcha": { "source": "altcha-org/altcha-wordpress-next", "asset": "altcha.zip", "type": "wordpress-plugin" } }Optional fields:
homepage,require(applied uniformly to every synthesised version — only useful if upstream's dependency set is stable).typedefaults towordpress-plugin.Trigger a Satis Build (manually via the Actions tab or just wait for the daily cron at 04:30 UTC). The script enumerates the upstream's releases, synthesises one
packageentry per stable release, and exposes them through this satis under the chosen package name.In the consumer project (Bedrock site, etc.) add the satis as a
composerrepository (already done in agency projects) andcomposer require <name>:^<version>— Composer-installers will route it intoweb/app/plugins/<slug>/via Bedrock'sinstaller-paths.
genero-composer-bot, a GitHub App with Contents (read) and Metadata (read), installed on all repositories in the org.satis.ymlmints a token from it per run to read plugin tags and contents, and checks the installation still covers everytype: vcsentry insatis.jsonbefore building. Note thatrelease-packages.jsonsources must be public repositories — that script authenticates with the job's ownGITHUB_TOKEN, which no app installation backs. Its client id is inlined in the workflow (it is not a secret, same as the watchdog's); the only credential is theCOMPOSER_BOT_PRIVATE_KEYorg secret, scoped to this repository.Replaced
GENEROI_DEPLOY_PAT, a PAT on thegeneroi-deploymachine user. Same reasoning asgenero-watchdog-botinwatchdog.yml: app tokens are minted per run, expire in an hour, never need rotating, and are scoped by the installation rather than by whatever the machine user has accumulated invitations to.PACKAGIST_UPDATE_PATaction secret containg a Personal Access Token of a user with write access to this repository. The token is limited to only this repository with Contents (write) access. (settings link)WCCOM_ACCESS_TOKENandWCCOM_ACCESS_TOKEN_SECRETaction secrets which contain the OAuth2 tokens stored in wp_options of the store which is connected to WooCommerce.LICENSE_KEYis added as a Repository Secret to each relevant plugin repository. The secret contains the license key.