NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
Packagist · #2738 most downloaded on Packagist
Centralized dev-dependencies, configs, and CI tooling for Netresearch TYPO3 extensions
Last release 1 months ago
01 Sep 2026
Release timing varies
gaps range from 2 weeks to 3 months
Most releases are documented
notes for 19 of 27 stable releases
Nothing withdrawn
no release was ever pulled
7 months old
27 releases · first in 2026
One column per month.
Three custom PHP-CS-Fixer rules, all registered by the shared config factory and none of them enabled.
Three custom PHP-CS-Fixer rules, all registered by the shared config factory and none of them enabled.
Line breaking and blank lines are the two things no shipped fixer does, and both stop being invisible the moment code is written back from a syntax tree rather than typed: a printer reproduces whatever the rules say, so every decision no rule describes is lost on the first pass.
Netresearch/break_long_method_chain (#238) puts every call of a chain on its own line from minimum_links calls on, default 3. Nothing among php-cs-fixer's 303 fixers has a line-width concept and method_chaining_indentation only indents a chain somebody already broke, so a chain written on one line stays on one line however long it grows.
Netresearch/blank_line_after_control_structure (#239) separates a control structure from the statement after it. blank_line_before_statement writes in front of a statement; nothing writes after a block. Measured over a 48-class extension, that is the rule the code keeps most consistently — a closing brace inside a method body is followed by a blank line in 232 of 247 places, 93 %, against 37 % for the blank line before an if that the shipped rule does enforce.
Netresearch/blank_line_before_comment (#241) separates a comment on a line of its own from the code above it — 274 of 309 places, 89 %. Inside an array or an argument list the rule is off, which is where all the counter-examples sit.
All three are registered but not enabled: reflowing a code base is a decision each project makes in a commit of its own, not something that arrives with a dependency update. The config factory takes a third argument for project rules, and the README's Code Style section documents each rule and shows how to switch one on.
Also in this release: the chain fixer leaves indentation to statement_indentation rather than writing and comparing it, which is what kept a chain nested in another chain's argument from ever settling (#242); its indent walk now runs only for a chain that is going to break. The Rector cap lifted after typo3-rector 3.15.1 shipped the container fix (#236), and the usual dependency updates (#228, #231, #232, #233, #234, #235, #240) from @renovate.
Two fixes, both about a job that never starts.
Two fixes, both about a job that never starts.
rector/rector 2.6.4, published 2026-08-27, removed RectorConfig::bind(). ssch/typo3-rector still calls that method in its own config/config.php and admits the release through its ^2.5.2 constraint, so every consuming extension lost its Rector job the moment CI re-resolved: Call to undefined method Rector\Config\RectorConfig::bind(), raised while Rector boots, before a single file is analysed. No consumer pins Rector and none ships a composer.lock, so the break arrived without a change of their own — reproduced on an unmodified checkout of netresearch/t3x-nr-vault's main, observed the same day in netresearch/t3x-nr-llm.
The constraint is now ^2.0 <2.6.4. It is temporary and goes once typo3-rector ships a release that no longer calls bind(); the incompatibility is tracked upstream at sabbelasichon/typo3-rector#4922. Thanks @CybotTM.
"Smoke test rector config factory" asserted that config/rector/rector.php returns a callable. It still does under 2.6.4 — the breakage sits one layer further in, at container build — so this repository stayed green through a release that took down every consumer.
The added step runs Rector against a throwaway extension fixture with a TYPO3 set applied, because that set is what pulls typo3-rector's own config into the run; without one the boot never reaches the code that broke. Exit 2 passes, since a dry run finding changes in the fixture is not a boot failure, but anything other than 0 or 2 fails, so a missing binary cannot report OK. Measured in both directions before shipping: exit 1 on the bind() error under 2.6.4, green under 2.6.3.
fuzz.yml's "Install dependencies" ran a plain composer install, unlike every job in ci.yml, which first runs composer config --no-plugins audit.block-insecure false. On a maintenance branch pinned to an EOL TYPO3 minor, every publicly installable core version can carry an advisory whose real fix ships only via ELTS and never reaches public Packagist. Composer's solver then refuses to resolve at all — a hard failure, not a warning — and no version bump gets a caller out of it.
Confirmed against netresearch/t3x-nr-image-optimize's TYPO3_12 branch, where fuzz / Fuzz Tests failed on "Your requirements could not be resolved to an installable set of packages" while the same branch's ci job installed the identical typo3/cms-core ^12.0 constraint. Both "Install dependencies" steps in the file are fixed, fuzz-tests and mutation-testing. security.yml's audit job is untouched and still reports the same advisories. Thanks @aseemann.
Four runner fixes, all of the same class: a green result standing in for a run that never happened.
Four runner fixes, all of the same class: a green result standing in for a run that never happened.
A fractor suite (#218, #223, @CybotTM).
Fractor is rector's sibling for the file types rector does not read -- TypoScript, Fluid, YAML, TCA. Six extensions on this runner carry a fractor script and had to leave the shell for half their upgrade tooling. -s fractor writes, -s fractor -n checks, exactly like cgl and rector. The standard config location mirrors rector's (Build/fractor/fractor.php); a flat Build/fractor.php is found and reported as non-standard.
The check does not trust fractor's exit code, because that code lies: fractor process --dry-run exits 0 while reporting "[OK] 46 files would have been changed", where rector exits 2. --output-format=json is worse -- on the same invocation it reports "changed_files": 0 against the console's 46. The check therefore parses the console line, which is the only signal that tells the truth. A repository with pending fractor changes now fails -s fractor -n instead of passing.
An honest lint (#217, #223, @CybotTM).
-s lint ran find Classes Configuration Tests .... A repository missing one of those directories got a find error on stderr, a discarded exit status, and a green suite that had opened two paths out of three -- and root-level PHP (ext_localconf.php, ext_tables.php, ext_emconf.php, Build/*.php) was never opened at all, though a syntax error in ext_localconf.php takes down the whole installation. Measured by breaking ext_localconf.php on purpose: the old runner said SUCCESS, exit 0.
The suite now lints everything the repository ships and prunes what it generates (.git, vendor, .Build, .build, node_modules, var, public, Documentation-GENERATED-temp), and prints the file count -- a lint that says nothing is indistinguishable from a lint that looked at nothing.
-s integration runs its own config, or refuses (#212, #224, @CybotTM).
The suite selected a testsuite NAME inside the already-detected unit config; an extension keeping its integration tests in a config of their own got the unit suite re-run under an integration banner. On t3x-cowriter that was 614 green unit tests standing in for five integration classes that had never run; with the config detected (Build/phpunit/IntegrationTests.xml and friends) the same call runs the 30 integration tests. Where neither a config nor an "integration" testsuite exists, the suite now refuses with the searched paths listed instead of green-running the wrong file.
Tool arguments pass through (#211, #224, @CybotTM).
The reusable workflow appends arguments to the composer scripts (composer ci:test:php:unit -- --coverage-clover=...), and composer strips its own -- before invoking the script -- so the runner received the bare form and died on "illegal option". Six checks failed on t3x-nr-passkeys-fe this way while every local call passed. The runner has only short options, so an argument starting with -- can never be its own: everything from an explicit -- or the first --long argument on now reaches the tool. A mistyped short option still fails loudly. For the extensions that branched around this in their composer scripts (t3x-nr-llm, t3x-nr-passkeys-fe), the LOCAL half of those scripts now accepts appended tool arguments; the branching itself stays — its CI half selects the matrix cell's host PHP, which the runner's own containers would override (#189 tracks the mode that would retire it).
Every consumer on a ^1.x constraint receives this on its next composer update; no manifest change is needed. All four fixes carry cases in check-runner-detection.sh that were seen to fail against the unpatched runner before being accepted.
The e2e instance's TYPO3 version no longer drags in -t ( #222 , @CybotTM ).
The e2e instance's TYPO3 version no longer drags in -t (#222, @CybotTM).
e2e-provision.sh read the major from TYPO3_VERSION, which is only set by -t. That flag is not suite-gated: it also rewrites the extension's own composer.json and runs a full composer require typo3/cms-core:<constraint> before the suite starts. For an e2e run the require does nothing -- the instance is installed separately, from scratch -- and a resolve that fails there kills the job before provisioning even begins, leaving a narrowed composer.json behind in the checkout.
E2E_TYPO3_VERSION from the environment now wins, then TYPO3_VERSION, then 13. The first is what the reusable e2e workflow already passes to a scripted consumer, as a constraint like "^14.3", so a CI wrapper can select the instance's version without touching the manifest at all. A constraint is reduced to its major; anything unparseable falls through to 13 rather than being guessed at.
Only v1.10.0 is affected, and only a consumer that uses the provisioning introduced there. -t itself is unchanged: it still narrows composer.json for unit and functional, which is what those suites want.
-s e2e can build the TYPO3 it tests ( #220 , @CybotTM ).
-s e2e can build the TYPO3 it tests (#220, @CybotTM).
Until now the suite needed an address: an already-running instance named by TYPO3_BASE_URL, by e2e_target() or by E2E_BASE_URL. The half that BUILDS such an instance lived in one extension's 1775-line e2e.sh, which is why t3x-rte_ckeditor_image was the only repository in the fleet with e2e at all.
assets/Build/Scripts/e2e-provision.sh is that half with nothing extension-specific in it: MariaDB, a composer-installed TYPO3, the site configuration and root template, PHP-FPM, Apache, and a check after each that it actually came up. It is sourced as a sibling of runTests.sh.
Four optional hooks in runTests.conf carry what is specific to an extension -- e2e_provision_packages, e2e_provision_typoscript, e2e_provision_site_dependencies and e2e_provision_seed. Defining any of them opts a repository in. An extension that only points at a running instance is untouched, and one that defines none is unaffected by this release.
Putting a real extension through that path found four gaps in the existing suite, all fixed here:
npm ci refuses without a lockfile, so a consumer that gitignores it got EUSAGE and no tests. It falls back to npm install --no-save and says that the Playwright version is then not pinned against the browser image.
E2E_TEST_DIR names the directory holding the suite's package.json, lockfile and Playwright config when it is not the repository root. Both the container working directory and the lockfile probe that selects the Playwright image follow it, so a suite in Tests/E2E resolves its own version instead of the fallback pin.
The Playwright container now also receives BASE_URL (what a Playwright config conventionally reads; TYPO3_BASE_URL remains the contract), TYPO3_BACKEND_PASSWORD, E2E_VARIANT and the effective TYPO3 major. Without the password every backend test fails at login, and when the run created the instance the password is known nowhere else.
wait_for takes an optional third argument, the timeout in seconds, default 10 as before. A MariaDB container initialising its data directory needs more.
The frontend check asserts the status code instead of printing it. A 403 used to scroll past, and the suite then reported a page of assertion failures for an instance that was never serving.
Verified by running t3x-rte_ckeditor_image's suite through the new path: 214 tests, 204 passed, 10 skipped, exit 0 -- the same result its own script produces. Both abort paths were exercised by injecting the defect rather than by reading the code: a nonexistent composer package and an unreachable frontend each stop the run with the cause named, and neither reaches Playwright.
Coverage runs are no longer silently empty (#216, #209, @CybotTM).
A PHPUnit config carrying makes coverage mandatory, but the suites passed -dxdebug.mode=off, so such a run ended at "No tests executed!". Both settings have to agree: Xdebug 3 lets the XDEBUG_MODE environment variable win over the ini flag, so setting only the flag changed nothing. The demand is now read from the config. Three extensions were blocked from migrating by this.
A repository whose PHPUnit config gains a block will see its runs get slower without asking, and the runner says so on each affected call.
Scripted e2e consumers are told about COMPOSER_RETRY (#219, @CybotTM).
Passing setup-script replaces the default pipeline, and with it the only step that applied composer_retry. The helper was exported all along but absent from the input's documented environment list, so every scripted consumer ran Composer unprotected -- and Composer has no git fallback for a failed dist download, so one HTTP 504 from api.github.com ends the job. Two e2e matrices in t3x-rte_ckeditor_image were ejected from its merge queue that way within three days, on two unrelated pull requests.
Also in this release: a conf can take its own e2e environment down again with e2e_teardown (#215), and the CodeQL action moved to v4.37.8 (#221).
Consumers referencing the reusable workflows with @main are unaffected by the runner changes; those ship in the Composer payload and reach an extension when it updates the package.
This repository releases itself ( #210 , #213 ).
This repository releases itself (#210, #213).
Ten tags between v1.4.0 and v1.8.2 carried no Release object, so releases/latest answered v1.3.4 from 2026-08-04. The cause was not a withheld decision: every workflow here that mentions tags: is a workflow_call reusable for consumers, and the only push trigger was self-ci on branches. release.yml is a path this repository offers, not one it took.
self-release.yml now fires on a signed tag push and runs the same chain the skill repos run: verify the tag is annotated and signed, build, checksum, Cosign sign-blob, SLSA attest, publish -- all in one job before the assets become public, so no window exists in which unattested artefacts are downloadable. The release notes are the annotated tag message, read with %(contents:body) so the SSH signature block stays out of them. The release is created with gh release create, not a third-party action.
The README gained a "Releasing This Repository" section, because the release path being knowledge nobody had written down is part of how the gap survived ten tags.
Read this before assuming the archives changed: they did not. git archive applies .gitattributes, whose export-ignore strips .github/, docs/, scripts/ and README.md -- and this release touches nothing else. The Composer payload in typo3-ci-workflows-v1.9.0.tar.gz is byte-identical to the one v1.8.2 would have produced, verified by extracting both and diffing.
That is deliberate rather than an oversight. The archive is the half worth attesting: uses: ...@main is resolved by GitHub from the git ref and never downloaded, while the runner IS installed, so a consumer can now check the copy in their .Build/vendor/netresearch/typo3-ci-workflows/ against a signed checksum. What this tag ships is the release path itself, and the first run of it.
Consumers referencing the workflows with @main are unaffected. Nothing about what a workflow does has changed -- only what a tag produces.
fix(runner): find a functional config at Build/phpunit.functional.xml by @CybotTM in #208
Full Changelog: v1.8.1...v1.8.2
fix(fuzz): select the suite that was detected, not the one that was configured by @CybotTM in #200
Full Changelog: v1.8.0...v1.8.1
fix(ci): fail a test job that has nothing to run, instead of reporting success by @CybotTM in #193
Full Changelog: v1.7.2...v1.8.0
fix(runtests): read the conf before deriving from it, and shard the same suite the serial run does by @CybotTM in #187
Full Changelog: v1.7.1...v1.7.2
fix(runtests): restore the values v1.7.0 dropped, and guard the class by @CybotTM in #184
Full Changelog: v1.7.0...v1.7.1
feat(runtests): find what the extension already declares by @CybotTM in #183
Full Changelog: v1.6.0...v1.7.0
feat(runtests): the suites the remaining forks still need by @CybotTM in #182
Full Changelog: v1.5.1...v1.6.0
fix(runtests): stop asking for what the extension already declares by @CybotTM in #181
Full Changelog: v1.5.0...v1.5.1
fix(ci): run test jobs with zend.assertions=1 by @CybotTM in #159
Full Changelog: v1.4.0...v1.5.0
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
Nothing published for this version
v1.1 Simplified Chinese translation.
v1.1 Norwegian Bokmål translation.
/data/links.json so they can be updated easily.Changelog inconsistency section in Bad Practices.
Rewrite "Ignoring Deprecations" sub-section to clarify the ideal scenario.
Your coding agent can read these notes before it upgrades. Set up the MCP server →