From 06171d25ea5a60cd50d2c3d246347ea7f3d20fa8 Mon Sep 17 00:00:00 2001 From: Tony Hegyes Date: Fri, 30 Oct 2026 12:17:42 +0100 Subject: [PATCH 1/3] Update packages The Composer lock trailed the latest releases. It moves to PHPStan 2.3.4, composer/composer 2.11.0, php-code-coverage 14.5.0, roave/security-advisories and Symfony patch releases; theseer/tokenizer drops out, as no package requires it any more. symfony/filesystem and symfony/finder stay on 7.4, which humbug/php-scoper 0.18 caps. The npm lock stays as it is: under the host's seven-day release-age cooldown, npm update would move 38 packages down, such as @typescript-eslint 8.72.1 to 8.72.0, and none up. No code change was needed. Assisted-by: Claude Code:claude-opus-5-5 --- composer.lock | 242 ++++++++++++++++++++------------------------------ 1 file changed, 96 insertions(+), 146 deletions(-) diff --git a/composer.lock b/composer.lock index 72eb1a2..d9a5e6e 100644 --- a/composer.lock +++ b/composer.lock @@ -884,11 +884,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.3.3", + "version": "2.3.4", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/4b57dc8ca7456b635d1661845f44bc060b02c6b0", - "reference": "4b57dc8ca7456b635d1661845f44bc060b02c6b0", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f20ce6f3c82556cfcfc086b152445a12d1d64fff", + "reference": "f20ce6f3c82556cfcfc086b152445a12d1d64fff", "shasum": "" }, "require": { @@ -944,7 +944,7 @@ "type": "github" } ], - "time": "2026-10-27T14:06:26+00:00" + "time": "2026-10-28T22:38:53+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -1055,12 +1055,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "5a6a29a8eb7efbfca408ef929bf076748ac1bf9e" + "reference": "7fd2aa4f9a2f0db3e04724273d5121c1d16a6c66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/5a6a29a8eb7efbfca408ef929bf076748ac1bf9e", - "reference": "5a6a29a8eb7efbfca408ef929bf076748ac1bf9e", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/7fd2aa4f9a2f0db3e04724273d5121c1d16a6c66", + "reference": "7fd2aa4f9a2f0db3e04724273d5121c1d16a6c66", "shasum": "" }, "conflict": { @@ -1740,7 +1740,7 @@ "phpoffice/common": "<0.2.9", "phpoffice/math": "<=0.2", "phpoffice/phpexcel": "<=1.8.2", - "phpoffice/phpspreadsheet": "<=1.30.5|>=2,<=2.1.17|>=2.2,<=2.4.6|>=3,<=3.10.6|>=4,<=5.8", + "phpoffice/phpspreadsheet": "<=1.30.6|>=2,<=2.1.18|>=2.2,<=2.4.7|>=3,<=3.10.7|>=4,<=5.9", "phppgadmin/phppgadmin": "<=7.13", "phpseclib/phpseclib": "<3.0.57|>=4,<4.0.1", "phpservermon/phpservermon": "<3.6", @@ -1766,7 +1766,7 @@ "plank/laravel-mediable": "<7", "plotly/plotly.js": "<2.25.2", "pocketmine/bedrock-protocol": "<8.0.2", - "pocketmine/pocketmine-mp": "<5.42.1", + "pocketmine/pocketmine-mp": "<5.44.2", "pocketmine/raklib": ">=0.14,<0.14.6|>=0.15,<0.15.1", "pontedilana/php-weasyprint": "<=2.5.1", "poweradmin/poweradmin": "<4.2.5|>=4.3,<4.3.4", @@ -1856,6 +1856,7 @@ "silverstripe/framework": "<6.2.2", "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.8.2|>=4,<4.3.7|>=5,<5.1.3", "silverstripe/hybridsessions": ">=1,<2.4.1|>=2.5,<2.5.1", + "silverstripe/mfa": "<6", "silverstripe/recipe-cms": ">=4.5,<4.5.3", "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", "silverstripe/reports": "<5.2.3", @@ -1954,7 +1955,7 @@ "symfony/mailomat-mailer": ">=7.2,<7.4.13|>=8,<8.0.13", "symfony/mailtrap-mailer": ">=7.2,<7.4.12|>=8,<8.0.12", "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", - "symfony/mime": "<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", + "symfony/mime": "<5.4.55|>=6,<6.4.49|>=7,<7.4.22|>=8,<8.1.10", "symfony/monolog-bridge": "<5.4.52|>=6,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/polyfill": ">=1,<1.10|>=1.17.1,<1.38.1", @@ -1971,7 +1972,7 @@ "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", "symfony/security-http": "<5.4.53|>=6,<6.4.41|>=7,<7.4.13|>=8,<8.0.13", "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", - "symfony/symfony": "<5.4.54|>=6,<6.4.48|>=7,<7.4.21|>=8,<8.1.9", + "symfony/symfony": "<5.4.55|>=6,<6.4.49|>=7,<7.4.22|>=8,<8.1.10", "symfony/translation": ">=2,<2.0.17", "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8|>=6.4.24,<6.4.40", "symfony/twilio-notifier": ">=6.4,<6.4.40|>=7,<7.4.12|>=8,<8.0.12", @@ -2003,7 +2004,7 @@ "thorsten/phpmyfaq": "<4.2.0.0-alpha", "tikiwiki/tiki-manager": "<=17.1", "timber/timber": ">=0.16.6,<1.23.1|>=1.24,<1.24.1|>=2,<2.1", - "tinymce/tinymce": "<7.9.3|>=8,<8.5.1", + "tinymce/tinymce": "<7.9.3|==7.9.3|>=8,<8.9.3", "tinymighty/wiki-seo": "<1.2.2", "titon/framework": "<9.9.99", "tltneon/lgsl": "<7", @@ -2114,7 +2115,7 @@ "wpcloud/wp-stateless": "<3.2", "wpglobus/wpglobus": "<=1.9.6", "wpmetabox/meta-box": "<5.11.2", - "wwbn/avideo": "<=29", + "wwbn/avideo": "<29.1", "xataface/xataface": "<3", "xpressengine/xpressengine": "<3.0.15", "yab/quarx": "<2.4.5", @@ -2215,7 +2216,7 @@ "type": "tidelift" } ], - "time": "2026-10-27T14:00:31+00:00" + "time": "2026-10-29T16:37:57+00:00" }, { "name": "sniccowp/php-scoper-wordpress-excludes", @@ -2467,16 +2468,16 @@ }, { "name": "symfony/finder", - "version": "v7.4.20", + "version": "v7.4.22", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e" + "reference": "e13420a8e0a76bdde1e7be09f923e62ad13739f2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e", - "reference": "2dd31e5208db7a0fda7bfc998ad727b79c2e4d1e", + "url": "https://api.github.com/repos/symfony/finder/zipball/e13420a8e0a76bdde1e7be09f923e62ad13739f2", + "reference": "e13420a8e0a76bdde1e7be09f923e62ad13739f2", "shasum": "" }, "require": { @@ -2511,7 +2512,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.4.20" + "source": "https://github.com/symfony/finder/tree/v7.4.22" }, "funding": [ { @@ -2531,7 +2532,7 @@ "type": "tidelift" } ], - "time": "2026-09-26T06:31:10+00:00" + "time": "2026-10-19T16:56:54+00:00" }, { "name": "symfony/polyfill-ctype", @@ -2618,16 +2619,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.43.0", + "version": "v1.43.3", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72" + "reference": "122889ed24dc000165bcbfd77ec412f15cbdfc6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", - "reference": "4d5d331bf731e0aedbc6b2f5e61135b7b7996a72", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/122889ed24dc000165bcbfd77ec412f15cbdfc6a", + "reference": "122889ed24dc000165bcbfd77ec412f15cbdfc6a", "shasum": "" }, "require": { @@ -2679,7 +2680,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.43.3" }, "funding": [ { @@ -2699,7 +2700,7 @@ "type": "tidelift" } ], - "time": "2026-09-24T15:19:04+00:00" + "time": "2026-10-28T09:35:49+00:00" }, { "name": "szepeviktor/phpstan-wordpress", @@ -3005,16 +3006,16 @@ }, { "name": "composer/ca-bundle", - "version": "1.6.0", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "2c99cf2e92516985dea4914812b93d9daf01cdd6" + "reference": "0c64479fae6952ba4cf021a72c0ac530394c91d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/2c99cf2e92516985dea4914812b93d9daf01cdd6", - "reference": "2c99cf2e92516985dea4914812b93d9daf01cdd6", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/0c64479fae6952ba4cf021a72c0ac530394c91d4", + "reference": "0c64479fae6952ba4cf021a72c0ac530394c91d4", "shasum": "" }, "require": { @@ -3061,7 +3062,7 @@ "support": { "irc": "irc://irc.freenode.org/composer", "issues": "https://github.com/composer/ca-bundle/issues", - "source": "https://github.com/composer/ca-bundle/tree/1.6.0" + "source": "https://github.com/composer/ca-bundle/tree/1.6.1" }, "funding": [ { @@ -3073,25 +3074,25 @@ "type": "github" } ], - "time": "2026-10-20T20:14:05+00:00" + "time": "2026-10-29T14:10:42+00:00" }, { "name": "composer/composer", - "version": "2.10.3", + "version": "2.11.0", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "f0de0bf90226853b841672f086d8b58b02332504" + "reference": "e52c65bed1de6aa272cdde4b7725ca6653d7b06a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/f0de0bf90226853b841672f086d8b58b02332504", - "reference": "f0de0bf90226853b841672f086d8b58b02332504", + "url": "https://api.github.com/repos/composer/composer/zipball/e52c65bed1de6aa272cdde4b7725ca6653d7b06a", + "reference": "e52c65bed1de6aa272cdde4b7725ca6653d7b06a", "shasum": "" }, "require": { - "composer/ca-bundle": "^1.5", - "composer/class-map-generator": "^1.4.0", + "composer/ca-bundle": "^1.6", + "composer/class-map-generator": "^1.8.0", "composer/metadata-minifier": "^1.0", "composer/pcre": "^2.3 || ^3.3", "composer/semver": "^3.3", @@ -3100,7 +3101,7 @@ "ext-filter": "*", "ext-hash": "*", "ext-json": "*", - "justinrainbow/json-schema": "^6.5.1", + "justinrainbow/json-schema": "^6.6.3", "php": "^7.2.5 || ^8.0", "psr/log": "^1.0 || ^2.0 || ^3.0", "react/promise": "^3.3", @@ -3117,7 +3118,7 @@ "symfony/process": "^5.4.47 || ^6.4.25 || ^7.1.10 || ^8.0" }, "require-dev": { - "phpstan/phpstan": "^1.11.8", + "phpstan/phpstan": "1.12.33", "phpstan/phpstan-deprecation-rules": "^1.2.0", "phpstan/phpstan-phpunit": "^1.4.0", "phpstan/phpstan-strict-rules": "^1.6.0", @@ -3141,7 +3142,7 @@ ] }, "branch-alias": { - "dev-main": "2.10-dev" + "dev-main": "2.11-dev" } }, "autoload": { @@ -3176,7 +3177,7 @@ "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/composer/issues", "security": "https://github.com/composer/composer/security/policy", - "source": "https://github.com/composer/composer/tree/2.10.3" + "source": "https://github.com/composer/composer/tree/2.11.0" }, "funding": [ { @@ -3188,7 +3189,7 @@ "type": "github" } ], - "time": "2026-08-27T11:34:23+00:00" + "time": "2026-10-29T09:23:20+00:00" }, { "name": "composer/metadata-minifier", @@ -3257,16 +3258,16 @@ }, { "name": "composer/semver", - "version": "3.5.2", + "version": "3.6.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "9dfce66f1e97360ea5e6d3e38ab14277e4cc591e" + "reference": "d92543b7ede9c4ec6b59853cefb061e39866d2cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/9dfce66f1e97360ea5e6d3e38ab14277e4cc591e", - "reference": "9dfce66f1e97360ea5e6d3e38ab14277e4cc591e", + "url": "https://api.github.com/repos/composer/semver/zipball/d92543b7ede9c4ec6b59853cefb061e39866d2cf", + "reference": "d92543b7ede9c4ec6b59853cefb061e39866d2cf", "shasum": "" }, "require": { @@ -3318,7 +3319,7 @@ "support": { "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.5.2" + "source": "https://github.com/composer/semver/tree/3.6.0" }, "funding": [ { @@ -3330,7 +3331,7 @@ "type": "github" } ], - "time": "2026-10-22T15:37:26+00:00" + "time": "2026-10-29T09:13:34+00:00" }, { "name": "composer/spdx-licenses", @@ -5042,16 +5043,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "14.4.1", + "version": "14.5.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f1e54f06b6b4cea73cd6104ba3c0cfa0efa6d73f" + "reference": "1db2f40af7a6538742e4a634b71ee556d6219be7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f1e54f06b6b4cea73cd6104ba3c0cfa0efa6d73f", - "reference": "f1e54f06b6b4cea73cd6104ba3c0cfa0efa6d73f", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1db2f40af7a6538742e4a634b71ee556d6219be7", + "reference": "1db2f40af7a6538742e4a634b71ee556d6219be7", "shasum": "" }, "require": { @@ -5064,13 +5065,12 @@ "phpunit/php-text-template": "^6.0.1", "sebastian/complexity": "^6.0", "sebastian/environment": "^9.3.3", - "sebastian/git-state": "^1.0", + "sebastian/git-state": "^1.1.0", "sebastian/lines-of-code": "^5.0.2", - "sebastian/version": "^7.0", - "theseer/tokenizer": "^2.0.1" + "sebastian/version": "^7.0.1" }, "require-dev": { - "phpunit/phpunit": "^13.3.6" + "phpunit/phpunit": "^13.4.5" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -5079,7 +5079,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "14.4.x-dev" + "dev-main": "14.5.x-dev" } }, "autoload": { @@ -5108,7 +5108,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/14.4.1" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/14.5.0" }, "funding": [ { @@ -5128,7 +5128,7 @@ "type": "tidelift" } ], - "time": "2026-10-03T14:05:38+00:00" + "time": "2026-10-30T08:11:15+00:00" }, { "name": "phpunit/php-file-iterator", @@ -7473,16 +7473,16 @@ }, { "name": "symfony/console", - "version": "v7.4.21", + "version": "v7.4.22", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "21f1bc2296f7ff6043241a8dcbc09559b3732820" + "reference": "8012f842110b414fd1cc641adb0ed8423353b0bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/21f1bc2296f7ff6043241a8dcbc09559b3732820", - "reference": "21f1bc2296f7ff6043241a8dcbc09559b3732820", + "url": "https://api.github.com/repos/symfony/console/zipball/8012f842110b414fd1cc641adb0ed8423353b0bb", + "reference": "8012f842110b414fd1cc641adb0ed8423353b0bb", "shasum": "" }, "require": { @@ -7547,7 +7547,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.4.21" + "source": "https://github.com/symfony/console/tree/v7.4.22" }, "funding": [ { @@ -7567,7 +7567,7 @@ "type": "tidelift" } ], - "time": "2026-10-12T18:45:38+00:00" + "time": "2026-10-28T11:08:27+00:00" }, { "name": "symfony/deprecation-contracts", @@ -7806,16 +7806,16 @@ }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.43.0", + "version": "v1.43.3", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "5cac8f4db97208823aa353f59a01faad2da52c75" + "reference": "5a8f33c87aeecffce24e721b5064fab05f4becf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5cac8f4db97208823aa353f59a01faad2da52c75", - "reference": "5cac8f4db97208823aa353f59a01faad2da52c75", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/5a8f33c87aeecffce24e721b5064fab05f4becf8", + "reference": "5a8f33c87aeecffce24e721b5064fab05f4becf8", "shasum": "" }, "require": { @@ -7864,7 +7864,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.43.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.43.3" }, "funding": [ { @@ -7884,7 +7884,7 @@ "type": "tidelift" } ], - "time": "2026-09-25T15:46:47+00:00" + "time": "2026-10-15T09:18:53+00:00" }, { "name": "symfony/polyfill-intl-normalizer", @@ -8377,16 +8377,16 @@ }, { "name": "symfony/process", - "version": "v8.1.9", + "version": "v8.1.10", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "59a682fd049b2d4d37c444f5ac21b7d01b8c7189" + "reference": "08aa69fa0998bb65a089f9e148a2c0f8b391398e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/59a682fd049b2d4d37c444f5ac21b7d01b8c7189", - "reference": "59a682fd049b2d4d37c444f5ac21b7d01b8c7189", + "url": "https://api.github.com/repos/symfony/process/zipball/08aa69fa0998bb65a089f9e148a2c0f8b391398e", + "reference": "08aa69fa0998bb65a089f9e148a2c0f8b391398e", "shasum": "" }, "require": { @@ -8418,7 +8418,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v8.1.9" + "source": "https://github.com/symfony/process/tree/v8.1.10" }, "funding": [ { @@ -8438,7 +8438,7 @@ "type": "tidelift" } ], - "time": "2026-10-08T16:57:43+00:00" + "time": "2026-10-25T14:40:25+00:00" }, { "name": "symfony/service-contracts", @@ -8529,16 +8529,16 @@ }, { "name": "symfony/string", - "version": "v8.1.9", + "version": "v8.1.10", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "07fb208558f941eab50ec49c89c2a6a93fb11f01" + "reference": "6c6110979c0150393bbb336170e286d51ab4d76c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/07fb208558f941eab50ec49c89c2a6a93fb11f01", - "reference": "07fb208558f941eab50ec49c89c2a6a93fb11f01", + "url": "https://api.github.com/repos/symfony/string/zipball/6c6110979c0150393bbb336170e286d51ab4d76c", + "reference": "6c6110979c0150393bbb336170e286d51ab4d76c", "shasum": "" }, "require": { @@ -8595,7 +8595,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v8.1.9" + "source": "https://github.com/symfony/string/tree/v8.1.10" }, "funding": [ { @@ -8615,20 +8615,20 @@ "type": "tidelift" } ], - "time": "2026-10-03T15:16:03+00:00" + "time": "2026-10-23T17:06:53+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.4.21", + "version": "v7.4.22", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "55d06b02f49eca9bff61828548988e6f8763e8a6" + "reference": "6e6ff33ea8cf5df27f00a5561ebd5ee9bd7f1618" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/55d06b02f49eca9bff61828548988e6f8763e8a6", - "reference": "55d06b02f49eca9bff61828548988e6f8763e8a6", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/6e6ff33ea8cf5df27f00a5561ebd5ee9bd7f1618", + "reference": "6e6ff33ea8cf5df27f00a5561ebd5ee9bd7f1618", "shasum": "" }, "require": { @@ -8682,7 +8682,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.4.21" + "source": "https://github.com/symfony/var-dumper/tree/v7.4.22" }, "funding": [ { @@ -8702,20 +8702,20 @@ "type": "tidelift" } ], - "time": "2026-10-13T05:48:33+00:00" + "time": "2026-10-25T21:04:04+00:00" }, { "name": "symfony/yaml", - "version": "v8.1.9", + "version": "v8.1.10", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "6107ee26a25215c3df3af0a3315e56bb4fd0b7db" + "reference": "9de4ff9874dd18b738b9f248398c3e36ef15a1d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/6107ee26a25215c3df3af0a3315e56bb4fd0b7db", - "reference": "6107ee26a25215c3df3af0a3315e56bb4fd0b7db", + "url": "https://api.github.com/repos/symfony/yaml/zipball/9de4ff9874dd18b738b9f248398c3e36ef15a1d2", + "reference": "9de4ff9874dd18b738b9f248398c3e36ef15a1d2", "shasum": "" }, "require": { @@ -8758,7 +8758,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v8.1.9" + "source": "https://github.com/symfony/yaml/tree/v8.1.10" }, "funding": [ { @@ -8778,7 +8778,7 @@ "type": "tidelift" } ], - "time": "2026-10-12T12:37:13+00:00" + "time": "2026-10-18T14:56:39+00:00" }, { "name": "thecodingmachine/safe", @@ -8923,56 +8923,6 @@ ], "time": "2026-02-04T18:08:13+00:00" }, - { - "name": "theseer/tokenizer", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/theseer/tokenizer.git", - "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/7989e43bf381af0eac72e4f0ca5bcbfa81658be4", - "reference": "7989e43bf381af0eac72e4f0ca5bcbfa81658be4", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": "^8.1" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" - } - ], - "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "support": { - "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/2.0.1" - }, - "funding": [ - { - "url": "https://github.com/theseer", - "type": "github" - } - ], - "time": "2025-12-08T11:19:18+00:00" - }, { "name": "webmozart/assert", "version": "1.12.1", From 4750122f836db4692983c61e19acd0d7e670db9d Mon Sep 17 00:00:00 2001 From: Tony Hegyes Date: Fri, 30 Oct 2026 12:19:41 +0100 Subject: [PATCH 2/3] Test that the release finds the main plugin file by its header The release learns the main plugin file from an entry-file input that defaults to .php, and can rename it in the archive through archive-entry-file. No caller passes either input. WordPress needs neither: it reads the first 8 KB of each PHP file at the plugin's root and takes the one with a Plugin Name header. Rows cover a build step that finds the file the same way: one header file, named after the slug or not, is exported as ENTRY_FILE; none, two, one only in a subfolder or one past the first 8 KB fail and name what was found. The required-files rows keep the entry file under its own name, and the rows for both inputs' validation and the rename step go. Assisted-by: Claude Code:claude-opus-5-5 --- tests/Unit/ReleaseWorkflowTest.php | 122 ++++++++++------------------- 1 file changed, 43 insertions(+), 79 deletions(-) diff --git a/tests/Unit/ReleaseWorkflowTest.php b/tests/Unit/ReleaseWorkflowTest.php index 472013d..5edfc47 100644 --- a/tests/Unit/ReleaseWorkflowTest.php +++ b/tests/Unit/ReleaseWorkflowTest.php @@ -11,58 +11,54 @@ final class ReleaseWorkflowTest extends TestCase { use RunsWorkflowSteps; /** - * @return array + * @return array, ?string, string}> */ - public static function provide_entry_files(): array { - return array( - 'root-file' => array( 'main.php', 0 ), - 'dot-prefixed' => array( './main.php', 1 ), - 'in-a-subfolder' => array( 'src/main.php', 1 ), - ); - } + public static function provide_main_plugin_files(): array { + $header = self::plugin_header( array() ); - #[DataProvider( 'provide_entry_files' )] - public function test_entry_file_must_be_a_file_at_the_project_root( string $entry_file, int $expected_status ): void { - $result = $this->run_build_step( - 'Enforce release inputs', - array( - 'ENTRY_FILE' => $entry_file, - 'REF_TYPE' => 'branch', - 'REF_NAME' => 'trunk', - 'PUBLISH' => 'false', - 'PROJECT_PATH' => '.', - ) + return array( + 'named-after-the-slug' => array( + array( + 'acme-plugin.php' => $header, + 'uninstall.php' => " array( array( 'main.php' => $header ), 'main.php', '' ), + 'no-header' => array( array( 'uninstall.php' => " array( + array( + 'main.php' => $header, + 'other.php' => $header, + ), + null, + 'main.php other.php', + ), + 'header-in-a-subfolder' => array( array( 'src/main.php' => $header ), null, 'none' ), + 'header-past-8-kb' => array( array( 'main.php' => " + * @param array $files */ - public static function provide_archive_entry_files(): array { - return array( - 'unset' => array( '', 0 ), - 'root-file' => array( 'bootstrap.php', 0 ), - 'in-a-subfolder' => array( 'src/bootstrap.php', 1 ), - 'not-php' => array( 'bootstrap', 1 ), - ); - } + #[DataProvider( 'provide_main_plugin_files' )] + public function test_the_main_plugin_file_is_the_one_root_php_file_with_a_plugin_header( array $files, ?string $expected, string $named ): void { + $this->write_project( $files ); - #[DataProvider( 'provide_archive_entry_files' )] - public function test_archive_entry_file_must_be_a_php_file_name( string $archive_entry_file, int $expected_status ): void { - $result = $this->run_build_step( - 'Enforce release inputs', - array( - 'ARCHIVE_ENTRY_FILE' => $archive_entry_file, - 'REF_TYPE' => 'branch', - 'REF_NAME' => 'trunk', - 'PUBLISH' => 'false', - 'PROJECT_PATH' => '.', - ) - ); + $result = $this->run_build_step( 'Find the main plugin file', array() ); + $env = \is_file( $this->work_dir . '/github-env' ) ? (string) \file_get_contents( $this->work_dir . '/github-env' ) : ''; - self::assertSame( $expected_status, $result['status'], $result['output'] ); + if ( \is_null( $expected ) ) { + self::assertSame( 1, $result['status'], $result['output'] ); + self::assertMatchesRegularExpression( '/^::error::.*' . \preg_quote( $named, '/' ) . '/m', $result['output'] ); + self::assertSame( '', $env ); + return; + } + + self::assertSame( 0, $result['status'], $result['output'] ); + self::assertSame( "ENTRY_FILE=$expected\n", $env ); } /** @@ -480,13 +476,13 @@ public function test_required_files_are_found_in_a_large_archive(): void { */ public static function provide_archived_entry_files(): array { return array( - 'under-the-archive-name' => array( 'bootstrap.php', 0 ), - 'under-the-source-name' => array( 'main.php', 1 ), + 'entry-file-shipped' => array( 'main.php', 0 ), + 'entry-file-missing' => array( 'other.php', 1 ), ); } #[DataProvider( 'provide_archived_entry_files' )] - public function test_the_archive_must_ship_the_entry_file_under_the_archive_name( string $archived, int $expected_status ): void { + public function test_the_archive_must_ship_the_entry_file( string $archived, int $expected_status ): void { $stage = $this->work_dir . '/stage/acme-plugin'; \mkdir( $stage, 0777, true ); \file_put_contents( "$stage/$archived", "run_build_step( - 'Assert required files present', - array( - 'WP_ORG' => 'false', - 'ARCHIVE_ENTRY_FILE' => 'bootstrap.php', - ) - ); + $result = $this->run_build_step( 'Assert required files present', array( 'WP_ORG' => 'false' ) ); self::assertSame( $expected_status, $result['status'], $result['output'] ); } @@ -543,32 +533,6 @@ public function test_the_archive_command_runs_from_the_release_tools(): void { self::assertSame( array( 'wp dist-archive . ./acme-plugin.zip --plugin-dirname=acme-plugin' ), \file( $this->work_dir . '/calls', \FILE_IGNORE_NEW_LINES ) ); } - public function test_the_entry_file_takes_the_archive_name_before_the_archive_is_built(): void { - $this->write_project( array( 'main.php' => self::plugin_header( array() ) ) ); - - $result = $this->run_build_step( 'Name the entry file for the archive', array( 'ARCHIVE_ENTRY_FILE' => 'bootstrap.php' ) ); - - self::assertSame( 0, $result['status'], $result['output'] ); - self::assertFileDoesNotExist( $this->work_dir . '/project/main.php' ); - self::assertSame( self::plugin_header( array() ), \file_get_contents( $this->work_dir . '/project/bootstrap.php' ) ); - } - - public function test_the_archive_name_must_not_replace_a_file_the_project_ships(): void { - $this->write_project( - array( - 'main.php' => self::plugin_header( array() ), - 'bootstrap.php' => "run_build_step( 'Name the entry file for the archive', array( 'ARCHIVE_ENTRY_FILE' => 'bootstrap.php' ) ); - - self::assertSame( 1, $result['status'], $result['output'] ); - self::assertMatchesRegularExpression( '/^::error::.*bootstrap\.php/m', $result['output'] ); - self::assertFileExists( $this->work_dir . '/project/main.php' ); - self::assertSame( "work_dir . '/project/bootstrap.php' ) ); - } - /** * @return array */ From 18ac989ca9c6e1728eba91bde6cbcfdaa1e4e7db Mon Sep 17 00:00:00 2001 From: Tony Hegyes Date: Fri, 30 Oct 2026 12:22:11 +0100 Subject: [PATCH 3/3] Find the main plugin file by its header, as WordPress does The release took the main plugin file from an entry-file input that defaulted to .php, and could ship it under another name through archive-entry-file. Both inputs asked the caller for something the project already says: WordPress takes a plugin's main file to be the PHP file at its root whose first 8 KB carry a Plugin Name header. No caller passes either input, so dropping them now breaks no one. A build step before the version check reads the root's PHP files the same way and exports the one it finds as ENTRY_FILE, which the header, floor and required-files checks read as before; none or several fail the release and name what was found. The inputs, their validation, the rename step and its fallback in the required-files check go, and docs/workflows.md and docs/scripts-contract.md describe the main plugin file as WordPress finds it. The required-files rows take neutral names. Assisted-by: Claude Code:claude-opus-5-5 --- .github/workflows/reusable-release.yml | 54 ++++++++------------------ docs/scripts-contract.md | 2 +- docs/workflows.md | 12 +++--- tests/Unit/ReleaseWorkflowTest.php | 10 ++--- 4 files changed, 28 insertions(+), 50 deletions(-) diff --git a/.github/workflows/reusable-release.yml b/.github/workflows/reusable-release.yml index 9f309cf..48420a1 100644 --- a/.github/workflows/reusable-release.yml +++ b/.github/workflows/reusable-release.yml @@ -13,16 +13,6 @@ on: type: string required: false default: '.' - entry-file: - description: 'Main plugin file carrying the plugin headers, relative to project-path. Empty uses .php.' - type: string - required: false - default: '' - archive-entry-file: - description: 'File name the main plugin file ships under in the archive, for a plugin whose earlier releases shipped another one: WordPress identifies an active plugin by its folder and file, so a renamed main file deactivates it on update. Empty ships entry-file as named.' - type: string - required: false - default: '' php-version: description: 'PHP version for the build, Plugin Check and the built-artifact test. It must meet the Requires PHP header, or WordPress refuses to activate the artifact.' type: string @@ -82,8 +72,6 @@ jobs: # Plugin Check's action starts its own wp-env without a phpVersion, and WordPress refuses to activate a plugin whose Requires PHP the default image does not meet. WP_ENV_PHP_VERSION: ${{ inputs.php-version }} PLUGIN_SLUG: ${{ inputs.plugin-slug }} - ENTRY_FILE: ${{ inputs.entry-file != '' && inputs.entry-file || format('{0}.php', inputs.plugin-slug) }} - ARCHIVE_ENTRY_FILE: ${{ inputs.archive-entry-file }} WP_ORG: ${{ inputs.wp-org }} outputs: digest: ${{ steps.archive-digest.outputs.digest }} @@ -141,15 +129,6 @@ jobs: exit 1 fi validate_path_input 'project-path' "$PROJECT_PATH" - # WordPress reads plugin headers only from files at the plugin's root. - if ! printf '%s' "$ENTRY_FILE" | grep -Eq '^[A-Za-z0-9._-]+\.php$'; then - echo "::error::entry-file must be a PHP file name at the project root — got '$ENTRY_FILE'." - exit 1 - fi - if [ -n "$ARCHIVE_ENTRY_FILE" ] && ! printf '%s' "$ARCHIVE_ENTRY_FILE" | grep -Eq '^[A-Za-z0-9._-]+\.php$'; then - echo "::error::archive-entry-file must be a PHP file name — got '$ARCHIVE_ENTRY_FILE'." - exit 1 - fi echo "tag-version=$tag_version" >> "$GITHUB_OUTPUT" @@ -182,16 +161,28 @@ jobs: exit 1 fi + # WordPress takes a plugin's main file from the PHP files at its root, reading each one's headers from its first 8 KB. + - name: Find the main plugin file + working-directory: ${{ inputs.project-path }} + run: | + entry_files=() + for file in *.php; do + if [ -f "$file" ] && head -c 8192 -- "$file" | grep -Eiq '^[[:space:]]*(<\?(php)?)?[[:space:]/*#@]*Plugin Name:[[:space:]]*[^[:space:]]'; then + entry_files+=("$file") + fi + done + if [ "${#entry_files[@]}" -ne 1 ]; then + echo "::error::The project root must hold exactly one PHP file with a Plugin Name header — found: ${entry_files[*]:-none}." + exit 1 + fi + echo "ENTRY_FILE=${entry_files[0]}" >> "$GITHUB_ENV" + - name: Verify version consistency id: versions working-directory: ${{ inputs.project-path }} env: TAG_VERSION: ${{ steps.release-inputs.outputs.tag-version }} run: | - if [ ! -f "$ENTRY_FILE" ]; then - echo "::error::The entry file $ENTRY_FILE does not exist — set entry-file to the main plugin file." - exit 1 - fi header_version=$(sed -nE 's|^[[:space:]/*#@]*[Vv]ersion:[[:space:]]*([^[:space:]]+).*$|\1|p' "$ENTRY_FILE" | head -n 1) stable_tag='' if [ -f readme.txt ]; then @@ -393,17 +384,6 @@ jobs: exit([] === $errors ? 0 : 1); PHP - # After the checks that read the source's headers, so every check on the archive sees the file as shipped. - - name: Name the entry file for the archive - if: inputs.archive-entry-file != '' - working-directory: ${{ inputs.project-path }} - run: | - if [ -e "$ARCHIVE_ENTRY_FILE" ]; then - echo "::error::archive-entry-file $ARCHIVE_ENTRY_FILE would replace a file the project ships — name a file that does not exist." - exit 1 - fi - mv -- "$ENTRY_FILE" "$ARCHIVE_ENTRY_FILE" - - name: Create plugin archive working-directory: ${{ inputs.project-path }} run: wp dist-archive . "./$PLUGIN_SLUG.zip" --plugin-dirname="$PLUGIN_SLUG" @@ -412,7 +392,7 @@ jobs: working-directory: ${{ inputs.project-path }} run: | entries=$(unzip -Z1 "$PLUGIN_SLUG.zip") - required=("$PLUGIN_SLUG/${ARCHIVE_ENTRY_FILE:-$ENTRY_FILE}") + required=("$PLUGIN_SLUG/$ENTRY_FILE") if [ "$WP_ORG" = "true" ]; then required+=("$PLUGIN_SLUG/readme.txt") fi diff --git a/docs/scripts-contract.md b/docs/scripts-contract.md index 8a23b5e..6b1147e 100644 --- a/docs/scripts-contract.md +++ b/docs/scripts-contract.md @@ -101,7 +101,7 @@ A project with a lower minimum WordPress version sets `WPCompat.requiresAtLeast` | File | Requirement | | --- | --- | -| `.php`, or the file `entry-file` names | The main plugin file at the project root. Its `Version` header equals the tag, its `Text Domain` equals `plugin-slug`, and its `Requires PHP`, `Requires at least` and `Requires Plugins` cover what the shipped packages need. The archive ships it under `archive-entry-file` when that is set. | +| The main plugin file | The one PHP file at the project root with a `Plugin Name` header in its first 8 KB, as WordPress finds it. Its `Version` header equals the tag, its `Text Domain` equals `plugin-slug`, and its `Requires PHP`, `Requires at least` and `Requires Plugins` cover what the shipped packages need. | | `readme.txt` | Required with `wp-org: true`. When present, its `Stable tag` equals the release version. | | `CHANGELOG.md` | Optional. When present, it has a section headed by the version, such as `## 1.2.3 - 2026-10-06` or `## [1.2.3] - 2026-10-06`, which becomes the release notes; a missing section fails the release. | | `.distignore` | Required; `wp dist-archive` builds the zip from it. Copy this package's baseline and extend it. | diff --git a/docs/workflows.md b/docs/workflows.md index 6f4b5cc..0eaa057 100644 --- a/docs/workflows.md +++ b/docs/workflows.md @@ -203,8 +203,6 @@ Builds a plugin's release zip, tests it, checks that the released commit's requi | --- | --- | --- | --- | --- | | `plugin-slug` | `string` | Yes | — | Plugin slug: the directory the zip unpacks to, the name of the archive and the POT, and the required Text Domain. | | `project-path` | `string` | No | `'.'` | Path to the project, relative to the repository root. | -| `entry-file` | `string` | No | `''` | Main plugin file, a PHP file name at the project root. Empty uses `.php`. | -| `archive-entry-file` | `string` | No | `''` | File name the main plugin file ships under in the archive, for a plugin whose earlier releases shipped another one: WordPress identifies an active plugin by its folder and file, so a renamed main file deactivates it on update. Empty ships `entry-file` as named. | | `php-version` | `string` | No | `'8.5'` | PHP for the build, Plugin Check and the artifact test; it must meet `Requires PHP`. | | `wp-env-config-file` | `string` | No | `''` | wp-env config file the artifact test starts. Empty uses `.wp-env.json`. | | `plugin-check` | `boolean` | No | `true` | Whether to run Plugin Check against the built zip. | @@ -216,7 +214,7 @@ Builds a plugin's release zip, tests it, checks that the released commit's requi The workflow runs five jobs: -- `build` (`contents: read`, no secrets) checks the release metadata, installs Composer and npm dependencies without restoring a dependency cache, installs WP-CLI and its `i18n` and `dist-archive` commands from `php/release/composer.lock` in the plugin's copy of this package, regenerates the POT, prunes to production dependencies, renames the entry file to `archive-entry-file` when set, builds the zip, checks its contents, runs Plugin Check, and outputs the version and the zip's SHA-256. +- `build` (`contents: read`, no secrets) finds the main plugin file as WordPress does, checks the release metadata, installs Composer and npm dependencies without restoring a dependency cache, installs WP-CLI and its `i18n` and `dist-archive` commands from `php/release/composer.lock` in the plugin's copy of this package, regenerates the POT, prunes to production dependencies, builds the zip, checks its contents, runs Plugin Check, and outputs the version and the zip's SHA-256. - `test` (`contents: read`, no secrets) mounts the extracted zip in the project's own wp-env, activates it with `wp plugin activate`, requests the home page, and runs `npm run test:e2e` when the project defines it, after installing Chromium when `@playwright/test` is installed. - `provenance` (`actions: read`) requires a successful push run of every file in `required-workflows` on the released commit, so a release reuses the suites proven there; tag a commit once they pass. - `release` (`contents: write`) verifies the zip's digest and creates the GitHub release with the zip attached. Its notes are the version's section of `CHANGELOG.md` when that file exists, and the job fails when that section is missing; without the file, the notes are GitHub's generated notes. @@ -230,13 +228,13 @@ The `build` job fails when: - the tag is not `v..` (no leading zeros), or `publish` is on without a tag; - the plugin does not require `ahegyes/wordpress-configs` under `require-dev`, so its development install holds no `php/release/composer.lock` to install WP-CLI from; -- the entry file's `Version:` header differs from the tag, or from the `Stable tag:` of `readme.txt` when that file exists; a wp.org release requires `readme.txt`; -- the entry file's `Text Domain:` header, or `extra.text-domain` in `composer.json` when declared, differs from `plugin-slug`; +- the project root holds no PHP file with a `Plugin Name:` header in its first 8 KB, or more than one; +- the main plugin file's `Version:` header differs from the tag, or from the `Stable tag:` of `readme.txt` when that file exists; a wp.org release requires `readme.txt`; +- the main plugin file's `Text Domain:` header, or `extra.text-domain` in `composer.json` when declared, differs from `plugin-slug`; - `vendor-prefixed/` carries strings in the plugin's text domain but the regenerated POT has none of them; - `Requires PHP` is below the PHP floor of a shipped package, the highest lower bound of the `require.php` constraints of the project's own `composer.json`, the production packages in `vendor/` and the scoped packages in `vendor-prefixed/`; - `Requires at least` is below the `extra.requires-wp` version a shipped package declares, or `Requires Plugins` lacks a slug listed in a shipped package's `extra.requires-plugins`; -- `archive-entry-file` names a file the project already has; -- the archive misses the entry file, under `archive-entry-file` when set, `readme.txt` for a wp.org release, or, when `composer.json` declares `extra.scoping-prefix`, `vendor-prefixed/scoper-autoload.php` and the files of every scoped package; +- the archive misses the main plugin file, `readme.txt` for a wp.org release, or, when `composer.json` declares `extra.scoping-prefix`, `vendor-prefixed/scoper-autoload.php` and the files of every scoped package; - the archive holds a development or secret file, such as a `phpcs*.xml*` ruleset, `composer.json` or `.env`. A strict `>` lower bound in a package's PHP constraint is read as inclusive. The version is taken from the tag and verified against the files, never stamped in. diff --git a/tests/Unit/ReleaseWorkflowTest.php b/tests/Unit/ReleaseWorkflowTest.php index 5edfc47..7a0d86c 100644 --- a/tests/Unit/ReleaseWorkflowTest.php +++ b/tests/Unit/ReleaseWorkflowTest.php @@ -474,15 +474,15 @@ public function test_required_files_are_found_in_a_large_archive(): void { /** * @return array */ - public static function provide_archived_entry_files(): array { + public static function provide_archived_main_files(): array { return array( - 'entry-file-shipped' => array( 'main.php', 0 ), - 'entry-file-missing' => array( 'other.php', 1 ), + 'main-file-shipped' => array( 'main.php', 0 ), + 'main-file-missing' => array( 'other.php', 1 ), ); } - #[DataProvider( 'provide_archived_entry_files' )] - public function test_the_archive_must_ship_the_entry_file( string $archived, int $expected_status ): void { + #[DataProvider( 'provide_archived_main_files' )] + public function test_the_archive_must_ship_the_main_plugin_file( string $archived, int $expected_status ): void { $stage = $this->work_dir . '/stage/acme-plugin'; \mkdir( $stage, 0777, true ); \file_put_contents( "$stage/$archived", "