diff --git a/manager/cli/Application.php b/manager/cli/Application.php new file mode 100644 index 000000000..6661b308d --- /dev/null +++ b/manager/cli/Application.php @@ -0,0 +1,75 @@ + + */ + private $commands = []; + + private ConsoleOutput $output; + + /** + * @param CommandInterface[] $commands + */ + public function __construct(ConsoleOutput $output, array $commands) + { + $this->output = $output; + foreach ($commands as $command) { + $this->register($command); + } + } + + public function run(array $argv): int + { + [$arguments, $options] = $this->separateOptions(array_slice($argv, 1)); + $commandName = array_shift($arguments) ?: ''; + + if (!$commandName) { + $this->output->error('コマンドを指定してください。'); + return ExitCode::INVALID; + } + + if (!isset($this->commands[$commandName])) { + $this->output->error("未対応のコマンドです: {$commandName}"); + return ExitCode::INVALID; + } + + $command = $this->commands[$commandName]; + return $command->execute($arguments, $options); + } + + private function register(CommandInterface $command): void + { + $this->commands[$command->name()] = $command; + } + + /** + * @return array{0: array, 1: array} + */ + private function separateOptions(array $arguments): array + { + $options = []; + $positionals = []; + + foreach ($arguments as $argument) { + if (strpos($argument, '--') === 0) { + $optionName = substr($argument, 2); + if ($optionName !== '') { + $options[$optionName] = true; + } + continue; + } + $positionals[] = $argument; + } + + return [$positionals, $options]; + } +} diff --git a/manager/cli/Command/CommandInterface.php b/manager/cli/Command/CommandInterface.php new file mode 100644 index 000000000..cbeafb5cf --- /dev/null +++ b/manager/cli/Command/CommandInterface.php @@ -0,0 +1,17 @@ + $arguments + * @param array $options + */ + public function execute(array $arguments, array $options): int; +} diff --git a/manager/cli/Command/StatusCommand.php b/manager/cli/Command/StatusCommand.php new file mode 100644 index 000000000..a4790cd01 --- /dev/null +++ b/manager/cli/Command/StatusCommand.php @@ -0,0 +1,91 @@ +service = $service; + $this->output = $output; + } + + public function name(): string + { + return 'status'; + } + + public function description(): string + { + return 'CMSの状態を表示する'; + } + + /** + * @param array $arguments + * @param array $options + */ + public function execute(array $arguments, array $options): int + { + $status = $this->service->getStatus(); + + if (isset($options['json'])) { + $this->output->json($status); + return ExitCode::SUCCESS; + } + + $this->renderHumanReadable($status); + return ExitCode::SUCCESS; + } + + /** + * @param array $status + */ + private function renderHumanReadable(array $status): void + { + $this->output->writeln('システムステータス'); + $this->output->writeln(sprintf( + '- CMS: %s (%s / %s)', + $status['cms']['full_name'] ?: $status['cms']['version'], + $status['cms']['branch'], + $status['cms']['release_date'] + )); + $this->output->writeln(sprintf('- PHP: %s', $status['php']['version'])); + $this->output->writeln(sprintf( + '- データベース: %s%s', + $status['database']['version'] ?: '不明', + $status['database']['connected'] ? '' : ' (未接続)' + )); + $this->output->writeln(sprintf( + '- キャッシュ: %s (書き込み%s)', + $status['cache']['path'], + $status['cache']['writable'] ? '可能' : '不可' + )); + $this->output->writeln(sprintf( + ' ファイル数: %d / 合計サイズ: %s', + $status['cache']['files'], + $this->humanReadableSize((int) $status['cache']['total_size']) + )); + } + + private function humanReadableSize(int $bytes): string + { + if ($bytes === 0) { + return '0 B'; + } + + $units = ['B', 'KB', 'MB', 'GB', 'TB']; + $position = (int) floor(log($bytes, 1024)); + $position = min($position, count($units) - 1); + + $value = $bytes / pow(1024, $position); + return sprintf('%.1f %s', $value, $units[$position]); + } +} diff --git a/manager/cli/Infrastructure/CacheStore.php b/manager/cli/Infrastructure/CacheStore.php new file mode 100644 index 000000000..07ba3a05f --- /dev/null +++ b/manager/cli/Infrastructure/CacheStore.php @@ -0,0 +1,81 @@ +cachePath = rtrim($cachePath, '/') . '/'; + } + + /** + * @return array + */ + public function summarize(): array + { + $this->ensureCacheDirectory(); + + if (!is_dir($this->cachePath)) { + return [ + 'path' => $this->cachePath, + 'writable' => false, + 'files' => 0, + 'total_size' => 0, + ]; + } + + [$files, $size] = $this->countFilesAndSize(); + + return [ + 'path' => $this->cachePath, + 'writable' => is_writable($this->cachePath), + 'files' => $files, + 'total_size' => $size, + ]; + } + + private function ensureCacheDirectory(): void + { + if (is_dir($this->cachePath)) { + return; + } + mkdir($this->cachePath, 0777, true); + } + + /** + * @return array{0: int, 1: int} + */ + private function countFilesAndSize(): array + { + $count = 0; + $bytes = 0; + + try { + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator( + $this->cachePath, + FilesystemIterator::SKIP_DOTS + ) + ); + + foreach ($iterator as $file) { + if ($file->isFile()) { + $count++; + $bytes += $file->getSize(); + } + } + } catch (\Throwable $throwable) { + return [0, 0]; + } + + return [$count, $bytes]; + } +} diff --git a/manager/cli/Service/SystemStatusService.php b/manager/cli/Service/SystemStatusService.php new file mode 100644 index 000000000..ce591127f --- /dev/null +++ b/manager/cli/Service/SystemStatusService.php @@ -0,0 +1,56 @@ +cacheStore = $cacheStore; + } + + /** + * @return array + */ + public function getStatus(): array + { + $version = evo()->getVersionData(); + + return [ + 'cms' => [ + 'version' => $version['version'] ?? '', + 'branch' => $version['branch'] ?? '', + 'release_date' => $version['release_date'] ?? '', + 'full_name' => $version['full_appname'] ?? '', + ], + 'php' => [ + 'version' => PHP_VERSION, + ], + 'database' => $this->getDatabaseStatus(), + 'cache' => $this->cacheStore->summarize(), + ]; + } + + /** + * @return array + */ + private function getDatabaseStatus(): array + { + $connected = db()->isConnected(); + if (!$connected) { + $connected = db()->connect(); + } + + $version = $connected ? db()->getVersion() : null; + + return [ + 'connected' => $connected, + 'version' => $version ?: null, + ]; + } +} diff --git a/manager/cli/Support/ConsoleOutput.php b/manager/cli/Support/ConsoleOutput.php new file mode 100644 index 000000000..152e88469 --- /dev/null +++ b/manager/cli/Support/ConsoleOutput.php @@ -0,0 +1,27 @@ +writeln( + json_encode( + $payload, + JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT + ) ?: '{}' + ); + } +} diff --git a/manager/cli/Support/Environment.php b/manager/cli/Support/Environment.php new file mode 100644 index 000000000..4a29657cd --- /dev/null +++ b/manager/cli/Support/Environment.php @@ -0,0 +1,71 @@ +getSettings(); + return $modx; + } + + private static function defineBaseConstants(): void + { + if (!defined('MODX_BASE_PATH')) { + include dirname(__DIR__, 3) . '/define-path.php'; + } + } + + private static function defineApiFlags(): void + { + if (!defined('MODX_API_MODE')) { + define('MODX_API_MODE', true); + } + if (!defined('IN_MANAGER_MODE')) { + define('IN_MANAGER_MODE', false); + } + if (!defined('IN_PARSER_MODE')) { + define('IN_PARSER_MODE', 'true'); + } + } + + private static function ensureAutoload(string $basePath): void + { + $autoloadPath = $basePath . '/vendor/autoload.php'; + if (is_file($autoloadPath)) { + require_once $autoloadPath; + } + } + + private static function seedServerGlobals(): void + { + $defaults = [ + 'REQUEST_METHOD' => 'CLI', + 'SERVER_NAME' => 'localhost', + 'HTTP_HOST' => 'localhost', + 'SCRIPT_NAME' => '/manager/cli/bin/cms', + 'REQUEST_URI' => '/manager/cli/bin/cms', + ]; + + foreach ($defaults as $key => $value) { + if (serverv($key)) { + continue; + } + array_set($_SERVER, $key, $value); + } + } +} diff --git a/manager/cli/Support/ExitCode.php b/manager/cli/Support/ExitCode.php new file mode 100644 index 000000000..667660dc7 --- /dev/null +++ b/manager/cli/Support/ExitCode.php @@ -0,0 +1,11 @@ +run($argv)); diff --git a/manager/cli/bootstrap.php b/manager/cli/bootstrap.php new file mode 100644 index 000000000..b9c298cea --- /dev/null +++ b/manager/cli/bootstrap.php @@ -0,0 +1,30 @@ +