Skip to content

v2.5.3

Choose a tag to compare

@github-actions github-actions released this 23 Jul 14:59
· 139 commits to main since this release
Immutable release. Only release title and notes can be modified.
254d06c

Fixed

  • Self-updater ResponseReceived listeners hit "Stream is detached" (#224) — 2.5.2 sink'd the release archive straight to disk via Http::sink() and closed the underlying stream in a response middleware to keep listeners from copying the archive back into memory (#219). But closing the LazyOpenStream detaches it, so any ResponseReceived listener that called $response->body() — Herd Pro's HttpClientWatcher, Telescope, Debugbar, custom monitoring — threw "Stream is detached" from GuzzleHttp\Psr7\LazyOpenStream::eof(). The whole update pipeline succeeded, then died during trailing event fanout and got rolled back. The shared StreamsDownloadsToDisk middleware now swaps the closed body for a fresh in-memory empty stream via Utils::streamFor('') before returning, so body() returns '' safely for any observer. The three source-test observer-safety assertions have been replaced with a real ResponseReceived listener that calls ->body() — the exact path Herd/Telescope/Debugbar take — so future regressions get caught.
  • Composer discovery on hosts where PHP-FPM's PATH doesn't include composer, plus actionable rollback errors (#225) — The self-updater's runComposerInstall() ran the bare command composer install ... via /bin/sh -c, which inherits PHP-FPM's PATH. On Laravel Herd and many Nginx/PHP-FPM production setups, that PATH doesn'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 new resolveComposerCommand() now walks (1) the COMPOSER_BINARY env var (absolute path), (2) cms.updates.composer_install_command when 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) bare composer 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's PATH never has to resolve composer's #!/usr/bin/env php shebang, and Herd's ~/Library/Application Support/Herd/bin/php-* path with its embedded space stays intact. Before rollback invokes composer install, verifyComposerBinaryAvailable() now runs {PHP_BINARY} {binary} --version with a 10s timeout; when it fails we throw UpdateException::composerBinaryNotFound($searchedPaths) — an actionable message naming the paths inspected and the COMPOSER_BINARY override — 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. New docs/self-updater.md documents the discovery precedence, env var, escape hatch, and rollback diagnostics; the config comment on composer_install_command now describes the full chain.
  • UpdateInfo::hasUpdate() stale after out-of-band version bumps (#226) — UpdateChecker::checkForUpdate() cached the resolved UpdateInfo value object for cms.updates.cache_ttl seconds (default 12h). The cached object froze both latestVersion (from the feed) AND currentVersion (a snapshot of config('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 (manual composer install on 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() and UpdateInfo::toArray() now route through resolveCurrentVersion(), which reads config('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 set app.version. The container check goes through Illuminate\Container\Container::getInstance()->bound('config') so we never touch an unbootstrapped container. The same helper is now used by PerformUpdateCommand, CheckForUpdateCommand, and CheckForUpdateScheduled so console output and log lines stay consistent with hasUpdate(). Belt-and-suspenders: UpdateChecker::cacheIsStale() reads config('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 set app.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 in docs/self-updater.md documents both mechanisms.