From 7b66a88af10204208778de4f1783dfa6ab3b8550 Mon Sep 17 00:00:00 2001 From: crazywhalecc Date: Mon, 13 Apr 2026 10:47:34 +0800 Subject: [PATCH] Add dump-extensions command --- .../Command/DumpExtensionsCommand.php | 160 ++++++++++++++++++ src/StaticPHP/ConsoleApplication.php | 2 + 2 files changed, 162 insertions(+) create mode 100644 src/StaticPHP/Command/DumpExtensionsCommand.php diff --git a/src/StaticPHP/Command/DumpExtensionsCommand.php b/src/StaticPHP/Command/DumpExtensionsCommand.php new file mode 100644 index 00000000..4e1fb932 --- /dev/null +++ b/src/StaticPHP/Command/DumpExtensionsCommand.php @@ -0,0 +1,160 @@ +addArgument('path', InputArgument::OPTIONAL, 'Path to project root', '.'); + $this->addOption('format', 'F', InputOption::VALUE_REQUIRED, 'Parsed output format', 'default'); + // output zero extension replacement rather than exit as failure + $this->addOption('no-ext-output', 'N', InputOption::VALUE_REQUIRED, 'When no extensions found, output default combination (comma separated)'); + // no dev + $this->addOption('no-dev', null, null, 'Do not include dev dependencies'); + // no spc filter + $this->addOption('no-spc-filter', 'S', null, 'Do not use SPC filter to determine the required extensions'); + } + + public function handle(): int + { + $path = FileSystem::convertPath($this->getArgument('path')); + + $path_installed = FileSystem::convertPath(rtrim($path, '/\\') . '/vendor/composer/installed.json'); + $path_lock = FileSystem::convertPath(rtrim($path, '/\\') . '/composer.lock'); + + $ext_installed = $this->extractFromInstalledJson($path_installed, !$this->getOption('no-dev')); + if ($ext_installed === null) { + if ($this->getOption('format') === 'default') { + $this->output->writeln('vendor/composer/installed.json load failed, skipped'); + } + $ext_installed = []; + } + + $ext_lock = $this->extractFromComposerLock($path_lock, !$this->getOption('no-dev')); + if ($ext_lock === null) { + $this->output->writeln('composer.lock load failed'); + return static::FAILURE; + } + + $extensions = array_unique(array_merge($ext_installed, $ext_lock)); + sort($extensions); + + if (empty($extensions)) { + if ($this->getOption('no-ext-output')) { + $this->outputExtensions(explode(',', $this->getOption('no-ext-output'))); + return static::SUCCESS; + } + $this->output->writeln('No extensions found'); + return static::FAILURE; + } + + $this->outputExtensions($extensions); + return static::SUCCESS; + } + + private function filterExtensions(array $requirements): array + { + return array_map( + fn ($key) => substr($key, 4), + array_keys( + array_filter($requirements, function ($key) { + return str_starts_with($key, 'ext-'); + }, ARRAY_FILTER_USE_KEY) + ) + ); + } + + private function loadJson(string $file): array|bool + { + if (!file_exists($file)) { + return false; + } + + $data = json_decode(file_get_contents($file), true); + if (!$data) { + return false; + } + return $data; + } + + private function extractFromInstalledJson(string $file, bool $include_dev = true): ?array + { + if (!($data = $this->loadJson($file))) { + return null; + } + + $packages = $data['packages'] ?? []; + + if (!$include_dev) { + $packages = array_filter($packages, fn ($package) => !in_array($package['name'], $data['dev-package-names'] ?? [])); + } + + return array_merge( + ...array_map(fn ($x) => isset($x['require']) ? $this->filterExtensions($x['require']) : [], $packages) + ); + } + + private function extractFromComposerLock(string $file, bool $include_dev = true): ?array + { + if (!($data = $this->loadJson($file))) { + return null; + } + + // get packages ext + $packages = $data['packages'] ?? []; + $exts = array_merge( + ...array_map(fn ($package) => $this->filterExtensions($package['require'] ?? []), $packages) + ); + + // get dev packages ext + if ($include_dev) { + $packages = $data['packages-dev'] ?? []; + $exts = array_merge( + $exts, + ...array_map(fn ($package) => $this->filterExtensions($package['require'] ?? []), $packages) + ); + } + + // get require ext + $platform = $data['platform'] ?? []; + $exts = array_merge($exts, $this->filterExtensions($platform)); + + // get require-dev ext + if ($include_dev) { + $platform = $data['platform-dev'] ?? []; + $exts = array_merge($exts, $this->filterExtensions($platform)); + } + + return $exts; + } + + private function outputExtensions(array $extensions): void + { + if (!$this->getOption('no-spc-filter')) { + $extensions = parse_extension_list($extensions); + } + switch ($this->getOption('format')) { + case 'json': + $this->output->writeln(json_encode($extensions, JSON_PRETTY_PRINT)); + break; + case 'text': + $this->output->writeln(implode(',', $extensions)); + break; + default: + $this->output->writeln('Required PHP extensions' . ($this->getOption('no-dev') ? ' (without dev)' : '') . ':'); + $this->output->writeln(implode(',', $extensions)); + } + } +} diff --git a/src/StaticPHP/ConsoleApplication.php b/src/StaticPHP/ConsoleApplication.php index 21e3ee8b..1fced54c 100644 --- a/src/StaticPHP/ConsoleApplication.php +++ b/src/StaticPHP/ConsoleApplication.php @@ -18,6 +18,7 @@ use StaticPHP\Command\Dev\PackLibCommand; use StaticPHP\Command\Dev\ShellCommand; use StaticPHP\Command\DoctorCommand; use StaticPHP\Command\DownloadCommand; +use StaticPHP\Command\DumpExtensionsCommand; use StaticPHP\Command\DumpLicenseCommand; use StaticPHP\Command\ExtractCommand; use StaticPHP\Command\InstallPackageCommand; @@ -65,6 +66,7 @@ class ConsoleApplication extends Application new ExtractCommand(), new SPCConfigCommand(), new DumpLicenseCommand(), + new DumpExtensionsCommand(), new ResetCommand(), new CheckUpdateCommand(), new MicroCombineCommand(),