v2.5.3
·
139 commits
to main
since this release
Immutable
release. Only release title and notes can be modified.
Fixed
- Self-updater
ResponseReceivedlisteners hit "Stream is detached" (#224) — 2.5.2 sink'd the release archive straight to disk viaHttp::sink()and closed the underlying stream in a response middleware to keep listeners from copying the archive back into memory (#219). But closing theLazyOpenStreamdetaches it, so anyResponseReceivedlistener that called$response->body()— Herd Pro'sHttpClientWatcher, Telescope, Debugbar, custom monitoring — threw"Stream is detached"fromGuzzleHttp\Psr7\LazyOpenStream::eof(). The whole update pipeline succeeded, then died during trailing event fanout and got rolled back. The sharedStreamsDownloadsToDiskmiddleware now swaps the closed body for a fresh in-memory empty stream viaUtils::streamFor('')before returning, sobody()returns''safely for any observer. The three source-test observer-safety assertions have been replaced with a realResponseReceivedlistener that calls->body()— the exact path Herd/Telescope/Debugbar take — so future regressions get caught. - Composer discovery on hosts where PHP-FPM's
PATHdoesn't include composer, plus actionable rollback errors (#225) — The self-updater'srunComposerInstall()ran the bare commandcomposer install ...via/bin/sh -c, which inherits PHP-FPM'sPATH. On Laravel Herd and many Nginx/PHP-FPM production setups, thatPATHdoesn't include composer's directory, so the update failed with"sh: composer: command not found". Rollback then re-ran the same failing command and reported"Rollback failed: … Manual intervention required."— masking the real cause (a resolvable "where does composer live?" problem) as an unfixable rollback failure. A newresolveComposerCommand()now walks (1) theCOMPOSER_BINARYenv var (absolute path), (2)cms.updates.composer_install_commandwhen it differs from the shipped default (backwards-compatible operator override), (3) auto-discovery across/usr/local/bin/composer,/opt/homebrew/bin/composer,~/.composer/vendor/bin/composer,~/.config/composer/vendor/bin/composer,/usr/bin/composer, and (4) barecomposer install ...(pre-2.5.3 behavior). When env or discovery wins, the command is built as{PHP_BINARY} {binary} install ...— both shell-escaped — so PHP-FPM'sPATHnever has to resolve composer's#!/usr/bin/env phpshebang, and Herd's~/Library/Application Support/Herd/bin/php-*path with its embedded space stays intact. Before rollback invokescomposer install,verifyComposerBinaryAvailable()now runs{PHP_BINARY} {binary} --versionwith a 10s timeout; when it fails we throwUpdateException::composerBinaryNotFound($searchedPaths)— an actionable message naming the paths inspected and theCOMPOSER_BINARYoverride — instead of"Manual intervention required."When rollback itself fails,UpdateException::rollbackAfterFailure()preserves both the original update-failure message and the rollback message so operators see why the update failed alongside the rollback failure. Newdocs/self-updater.mddocuments the discovery precedence, env var, escape hatch, and rollback diagnostics; the config comment oncomposer_install_commandnow describes the full chain. UpdateInfo::hasUpdate()stale after out-of-band version bumps (#226) —UpdateChecker::checkForUpdate()cached the resolvedUpdateInfovalue object forcms.updates.cache_ttlseconds (default 12h). The cached object froze bothlatestVersion(from the feed) ANDcurrentVersion(a snapshot ofconfig('app.version')at cache-populate time).hasUpdate()then compared those two frozen strings, so if the host's installed version moved forward out-of-band (manualcomposer installon a release zip, unzip-over-site, deploy script) between cache-populate and cache-render, the Updates admin page kept saying "Update available to X" for a site already on X — for up to 12h.UpdateInfo::hasUpdate()andUpdateInfo::toArray()now route throughresolveCurrentVersion(), which readsconfig('app.version')fresh at call time when the container is bootstrapped and the value is a non-empty string; falls back to the constructor snapshot for non-Laravel callers, tests, and hosts that never setapp.version. The container check goes throughIlluminate\Container\Container::getInstance()->bound('config')so we never touch an unbootstrapped container. The same helper is now used byPerformUpdateCommand,CheckForUpdateCommand, andCheckForUpdateScheduledso console output and log lines stay consistent withhasUpdate(). Belt-and-suspenders:UpdateChecker::cacheIsStale()readsconfig('app.version')without a default, treats null/empty as "no fresh version to compare against" (keeps the pre-2.5.3 cache-serving behavior on hosts that never setapp.version), and only evicts the cache when the fresh value genuinely differs from the cached snapshot. New "Cached update info and out-of-band version bumps" section indocs/self-updater.mddocuments both mechanisms.