From 794d92c9d804fbbaedd9a09df833bd40ef9aef83 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 10 Dec 2025 12:54:04 +0800 Subject: [PATCH 1/2] Add early validation for package build and installation requirements --- src/StaticPHP/Package/PackageInstaller.php | 23 ++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/StaticPHP/Package/PackageInstaller.php b/src/StaticPHP/Package/PackageInstaller.php index 1e5e27e8..53000397 100644 --- a/src/StaticPHP/Package/PackageInstaller.php +++ b/src/StaticPHP/Package/PackageInstaller.php @@ -12,6 +12,7 @@ use StaticPHP\Artifact\DownloaderOptions; use StaticPHP\DI\ApplicationContext; use StaticPHP\Exception\WrongUsageException; use StaticPHP\Registry\PackageLoader; +use StaticPHP\Runtime\SystemTarget; use StaticPHP\Util\DependencyResolver; use StaticPHP\Util\FileSystem; use StaticPHP\Util\InteractiveTerm; @@ -133,6 +134,9 @@ class PackageInstaller echo PHP_EOL; } + // Early validation: check if packages can be built or installed before downloading + $this->validatePackagesBeforeBuild(); + // check download if ($this->download) { $downloaderOptions = DownloaderOptions::extractFromConsoleOptions($this->options, 'dl'); @@ -199,8 +203,6 @@ class PackageInstaller if ($interactive) { InteractiveTerm::finish('Built package: ' . ConsoleColor::green($package->getName()) . ($status === SPC_STATUS_ALREADY_BUILT ? ' (already built, skipped)' : '')); } - } elseif ($package->getType() === 'library') { - throw new WrongUsageException("Package '{$package->getName()}' cannot be installed: no build stage defined and no binary artifact available for current OS."); } } } @@ -535,6 +537,23 @@ class PackageInstaller } } + private function validatePackagesBeforeBuild(): void + { + foreach ($this->packages as $package) { + if ($package->getType() !== 'library') { + continue; + } + $is_to_build = $this->isBuildPackage($package); + $has_build_stage = $package instanceof LibraryPackage && $package->hasStage('build'); + $should_use_binary = $package instanceof LibraryPackage && ($package->getArtifact()?->shouldUseBinary() ?? false); + + // Check if package can neither be built nor installed + if (!$is_to_build && !$should_use_binary && !$has_build_stage) { + throw new WrongUsageException("Package '{$package->getName()}' cannot be installed: no build stage defined and no binary artifact available for current OS: " . SystemTarget::getCurrentPlatformString()); + } + } + } + private function performAfterInstallActions(Package $package): void { // ----------- perform post-install actions from extracted .package.{pkg_name}.postinstall.json ----------- From 2901d32ba77a6d1d36f8c4187409b1c44ac66d1e Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Wed, 10 Dec 2025 13:17:15 +0800 Subject: [PATCH 2/2] Update ApplicationContext and InteractiveTerm to handle null outputs gracefully --- src/StaticPHP/DI/ApplicationContext.php | 2 +- src/StaticPHP/Util/InteractiveTerm.php | 31 +++++++++++++------------ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/StaticPHP/DI/ApplicationContext.php b/src/StaticPHP/DI/ApplicationContext.php index a845fba2..73ff9f2d 100644 --- a/src/StaticPHP/DI/ApplicationContext.php +++ b/src/StaticPHP/DI/ApplicationContext.php @@ -83,7 +83,7 @@ class ApplicationContext * * @param class-string $id Service identifier * - * @return T + * @return null|T */ public static function get(string $id): mixed { diff --git a/src/StaticPHP/Util/InteractiveTerm.php b/src/StaticPHP/Util/InteractiveTerm.php index 47932763..ede87a64 100644 --- a/src/StaticPHP/Util/InteractiveTerm.php +++ b/src/StaticPHP/Util/InteractiveTerm.php @@ -7,6 +7,7 @@ namespace StaticPHP\Util; use StaticPHP\DI\ApplicationContext; use Symfony\Component\Console\Helper\ProgressIndicator; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Output\ConsoleOutput; use Symfony\Component\Console\Output\OutputInterface; use ZM\Logger\ConsoleColor; @@ -16,8 +17,8 @@ class InteractiveTerm public static function notice(string $message, bool $indent = false): void { - $no_ansi = ApplicationContext::get(InputInterface::class)->getOption('no-ansi') ?? false; - $output = ApplicationContext::get(OutputInterface::class); + $no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false; + $output = ApplicationContext::get(OutputInterface::class) ?? new ConsoleOutput(); if ($output->isVerbose()) { logger()->notice(strip_ansi_colors($message)); } else { @@ -27,8 +28,8 @@ class InteractiveTerm public static function success(string $message, bool $indent = false): void { - $no_ansi = ApplicationContext::get(InputInterface::class)->getOption('no-ansi') ?? false; - $output = ApplicationContext::get(OutputInterface::class); + $no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false; + $output = ApplicationContext::get(OutputInterface::class) ?? new ConsoleOutput(); if ($output->isVerbose()) { logger()->info(strip_ansi_colors($message)); } else { @@ -38,8 +39,8 @@ class InteractiveTerm public static function plain(string $message): void { - $no_ansi = ApplicationContext::get(InputInterface::class)->getOption('no-ansi') ?? false; - $output = ApplicationContext::get(OutputInterface::class); + $no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false; + $output = ApplicationContext::get(OutputInterface::class) ?? new ConsoleOutput(); if ($output->isVerbose()) { logger()->info(strip_ansi_colors($message)); } else { @@ -49,8 +50,8 @@ class InteractiveTerm public static function info(string $message): void { - $no_ansi = ApplicationContext::get(InputInterface::class)->getOption('no-ansi') ?? false; - $output = ApplicationContext::get(OutputInterface::class); + $no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false; + $output = ApplicationContext::get(OutputInterface::class) ?? new ConsoleOutput(); if (!$output->isVerbose()) { $output->writeln(($no_ansi ? 'strip_ansi_colors' : 'strval')(ConsoleColor::green('▶ ') . $message)); } @@ -59,8 +60,8 @@ class InteractiveTerm public static function error(string $message, bool $indent = true): void { - $no_ansi = ApplicationContext::get(InputInterface::class)->getOption('no-ansi') ?? false; - $output = ApplicationContext::get(OutputInterface::class); + $no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false; + $output = ApplicationContext::get(OutputInterface::class) ?? new ConsoleOutput(); if ($output->isVerbose()) { logger()->error(strip_ansi_colors($message)); } else { @@ -75,15 +76,15 @@ class InteractiveTerm public static function setMessage(string $message): void { - $no_ansi = ApplicationContext::get(InputInterface::class)->getOption('no-ansi') ?? false; + $no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false; self::$indicator?->setMessage(($no_ansi ? 'strip_ansi_colors' : 'strval')($message)); } public static function finish(string $message, bool $status = true): void { - $no_ansi = ApplicationContext::get(InputInterface::class)->getOption('no-ansi') ?? false; + $no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false; $message = $no_ansi ? strip_ansi_colors($message) : $message; - $output = ApplicationContext::get(OutputInterface::class); + $output = ApplicationContext::get(OutputInterface::class) ?? new ConsoleOutput(); if ($output->isVerbose()) { if ($status) { logger()->info($message); @@ -104,8 +105,8 @@ class InteractiveTerm public static function indicateProgress(string $message): void { - $no_ansi = ApplicationContext::get(InputInterface::class)->getOption('no-ansi') ?? false; - $output = ApplicationContext::get(OutputInterface::class); + $no_ansi = ApplicationContext::get(InputInterface::class)?->getOption('no-ansi') ?? false; + $output = ApplicationContext::get(OutputInterface::class) ?? new ConsoleOutput(); if ($output->isVerbose()) { logger()->info(strip_ansi_colors($message)); return;