Proposal:
- packages.json ← atomic pointer (build-id + URLs)
+ packages.json ← static
- p/wp-plugin/akismet$abc123.json ← shared, uploaded once ever (immutable)
- p/providers-week$def456.json ← shared, uploaded once ever (immutable)
p2/wp-plugin/akismet.json ← shared, overwritten when changed
+ p2/wp-plugin/akismet~dev.json ← shared, overwritten when changed
- releases/20260314-150405/packages.json ← per-release snapshot (immutable)
- releases/20260314-150405/manifest.json ← per-release (immutable)
The new static packages.json:
{
"available-package-patterns": [
"wp-plugin/*",
"wp-theme/*"
],
"metadata-url": "/p2/%package%.json",
"notify-batch": "https://wp-composer.com/downloads"
}
Composer v1
packagist.org has dropped Composer v1 support since September 2025.
If anyone is still using v1, they must be paying for private packagist or self-hosting a mirror.
The hashed providers urls are for Composer v1 only.
Without those hashed providers urls, /p2/wp-plugin/akismet.json is the only storage we need to update (except the DB, details later).
Tagged Versions & Feature Branches
Composer v2 looks for tagged versions (including unstable ones, e.g: patch, beta, etc) in /p2/wp-plugin/akismet.json and looks for feature branches in /p2/wp-plugin/akismet~dev.json.
Mapping this to wp.org SVN:
https://plugins.svn.wordpress.org/akismet/tags/ --> /p2/wp-plugin/akismet.json
https://plugins.svn.wordpress.org/akismet/branches/ --> /p2/wp-plugin/akismet~dev.json
https://plugins.svn.wordpress.org/akismet/trunk/ --> /p2/wp-plugin/akismet~dev.json (merging trunk with branches)
Tagged JSON (/p2/wp-plugin/akismet.json) contains both dist and source whereas feature branches JSON /p2/wp-plugin/akismet~dev.json contains source only. trunk does not contains dist to avoid caching issues.
Problem with trunk
{
"version": "8.1.21",
"source": {
"type": "svn",
"url": "https://plugins.svn.wordpress.org/activecampaign-subscription-forms/",
"reference": "trunk"
},
"dist": {
"type": "zip",
"url": "https://downloads.wordpress.org/plugin/activecampaign-subscription-forms.zip?timestamp=1763105557"
}
}
Composer expects immutability. The above example is wrong:
trunk is not forever v8.1.21
- The zip is not forever
v8.1.21
Thus, the whole JSON example is wrong.
By providing only source on trunk. Composer infers the SVN revision number and properly lock the version.
If plugin developers never tag the plugin, users should only use dev-trunk and let Composer lock its SVN revision number.
Souring
Instead of sourcing from https://api.wordpress.org/plugins/info/1.2/, we will need to change to crawl from https://plugins.svn.wordpress.org instead.
Database
With support for Composer v1 dropped, /p2/%package%.json could become the single source of truth.
This is sufficient for normal users.
However, we want to mimic other packagist endpoints, enrich meta info and have a frontend for marketing. We will need to save into the database.
Maybe a feature flag for normal users to skip the DB and use /p2/ as the single source of truth. But leave that for the future.
Proposal:
/p2/wp-plugin/akismet~dev.json(Split dev-trunk into ~dev.json for proper SVN revision locking #65)trunkhandlingThe new static
packages.json:{ "available-package-patterns": [ "wp-plugin/*", "wp-theme/*" ], "metadata-url": "/p2/%package%.json", "notify-batch": "https://wp-composer.com/downloads" }Composer v1
packagist.org has dropped Composer v1 support since September 2025.
If anyone is still using v1, they must be paying for private packagist or self-hosting a mirror.
The hashed providers urls are for Composer v1 only.
Without those hashed providers urls,
/p2/wp-plugin/akismet.jsonis the only storage we need to update (except the DB, details later).Tagged Versions & Feature Branches
Composer v2 looks for tagged versions (including unstable ones, e.g:
patch,beta, etc) in/p2/wp-plugin/akismet.jsonand looks for feature branches in/p2/wp-plugin/akismet~dev.json.Mapping this to wp.org SVN:
https://plugins.svn.wordpress.org/akismet/tags/-->/p2/wp-plugin/akismet.jsonhttps://plugins.svn.wordpress.org/akismet/branches/-->/p2/wp-plugin/akismet~dev.jsonhttps://plugins.svn.wordpress.org/akismet/trunk/-->/p2/wp-plugin/akismet~dev.json(mergingtrunkwith branches)Tagged JSON (
/p2/wp-plugin/akismet.json) contains bothdistandsourcewhereas feature branches JSON/p2/wp-plugin/akismet~dev.jsoncontainssourceonly.trunkdoes not containsdistto avoid caching issues.Problem with
trunk{ "version": "8.1.21", "source": { "type": "svn", "url": "https://plugins.svn.wordpress.org/activecampaign-subscription-forms/", "reference": "trunk" }, "dist": { "type": "zip", "url": "https://downloads.wordpress.org/plugin/activecampaign-subscription-forms.zip?timestamp=1763105557" } }Composer expects immutability. The above example is wrong:
trunkis not foreverv8.1.21v8.1.21Thus, the whole JSON example is wrong.
By providing only
sourceontrunk. Composer infers the SVN revision number and properly lock the version.If plugin developers never tag the plugin, users should only use
dev-trunkand let Composer lock its SVN revision number.Souring
Instead of sourcing from
https://api.wordpress.org/plugins/info/1.2/, we will need to change to crawl fromhttps://plugins.svn.wordpress.orginstead.Database
With support for Composer v1 dropped,
/p2/%package%.jsoncould become the single source of truth.This is sufficient for normal users.
However, we want to mimic other packagist endpoints, enrich meta info and have a frontend for marketing. We will need to save into the database.
Maybe a feature flag for normal users to skip the DB and use
/p2/as the single source of truth. But leave that for the future.