addArgument('package', InputArgument::REQUIRED, 'Package name to inspect'); $this->addOption('json', null, InputOption::VALUE_NONE, 'Output as JSON instead of colored terminal display'); } public function handle(): int { $packageName = $this->getArgument('package'); if (!PackageConfig::isPackageExists($packageName)) { $this->output->writeln("Package '{$packageName}' not found."); return static::USER_ERROR; } $pkgConfig = PackageConfig::get($packageName); $artifactConfig = ArtifactConfig::get($packageName); $pkgInfo = Registry::getPackageConfigInfo($packageName); $artifactInfo = Registry::getArtifactConfigInfo($packageName); if ($this->getOption('json')) { return $this->outputJson($packageName, $pkgConfig, $artifactConfig, $pkgInfo, $artifactInfo); } return $this->outputTerminal($packageName, $pkgConfig, $artifactConfig, $pkgInfo, $artifactInfo); } private function outputJson(string $name, array $pkgConfig, ?array $artifactConfig, ?array $pkgInfo, ?array $artifactInfo): int { $data = [ 'name' => $name, 'registry' => $pkgInfo['registry'] ?? null, 'package_config_file' => $pkgInfo ? $this->toRelativePath($pkgInfo['config']) : null, 'package' => $pkgConfig, ]; if ($artifactConfig !== null) { $data['artifact_config_file'] = $artifactInfo ? $this->toRelativePath($artifactInfo['config']) : null; $data['artifact'] = $this->splitArtifactConfig($artifactConfig); } $this->output->writeln(json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); return static::SUCCESS; } private function outputTerminal(string $name, array $pkgConfig, ?array $artifactConfig, ?array $pkgInfo, ?array $artifactInfo): int { $type = $pkgConfig['type'] ?? 'unknown'; $registry = $pkgInfo['registry'] ?? 'unknown'; $pkgFile = $pkgInfo ? $this->toRelativePath($pkgInfo['config']) : 'unknown'; // Header $this->output->writeln(''); $this->output->writeln("Package: {$name} Type: {$type} Registry: {$registry}"); $this->output->writeln("Config file: {$pkgFile}"); $this->output->writeln(''); // Package config fields (excluding type and artifact which are shown separately) $pkgFields = array_diff_key($pkgConfig, array_flip(['type', 'artifact'])); if (!empty($pkgFields)) { $this->output->writeln('── Package Config ──'); $this->printYamlBlock($pkgFields, 0); $this->output->writeln(''); } // Artifact config if ($artifactConfig !== null) { $artifactFile = $artifactInfo ? $this->toRelativePath($artifactInfo['config']) : 'unknown'; $this->output->writeln("── Artifact Config ── file: {$artifactFile}"); // Check if artifact config is inline (embedded in pkg config) or separate $inlineArtifact = $pkgConfig['artifact'] ?? null; if (is_array($inlineArtifact)) { $this->output->writeln(' (inline in package config)'); } $split = $this->splitArtifactConfig($artifactConfig); foreach ($split as $section => $value) { $this->output->writeln(''); $this->output->writeln(" [{$section}]"); $this->printYamlBlock($value, 4); } $this->output->writeln(''); } else { $this->output->writeln('── Artifact Config ── (none)'); $this->output->writeln(''); } return static::SUCCESS; } /** * Split artifact config into logical sections for cleaner display. * * @return array */ private function splitArtifactConfig(array $config): array { $sections = []; $sectionOrder = ['source', 'source-mirror', 'binary', 'binary-mirror', 'metadata']; foreach ($sectionOrder as $key) { if (array_key_exists($key, $config)) { $sections[$key] = $config[$key]; } } // Any remaining unknown keys foreach ($config as $k => $v) { if (!array_key_exists($k, $sections)) { $sections[$k] = $v; } } return $sections; } /** * Print a value as indented YAML-style output with Symfony Console color tags. */ private function printYamlBlock(mixed $value, int $indent): void { $pad = str_repeat(' ', $indent); if (!is_array($value)) { $this->output->writeln($pad . $this->colorScalar($value)); return; } $isList = array_is_list($value); foreach ($value as $k => $v) { if ($isList) { if (is_array($v)) { $this->output->writeln($pad . '- '); $this->printYamlBlock($v, $indent + 2); } else { $this->output->writeln($pad . '- ' . $this->colorScalar($v)); } } else { if (is_array($v)) { $this->output->writeln($pad . "{$k}:"); $this->printYamlBlock($v, $indent + 2); } else { $this->output->writeln($pad . "{$k}: " . $this->colorScalar($v)); } } } } private function colorScalar(mixed $v): string { if (is_bool($v)) { return '' . ($v ? 'true' : 'false') . ''; } if (is_int($v) || is_float($v)) { return '' . $v . ''; } if ($v === null) { return 'null'; } // Strings that look like URLs if (is_string($v) && (str_starts_with($v, 'http://') || str_starts_with($v, 'https://'))) { return '' . $v . ''; } return '' . $v . ''; } private function toRelativePath(string $absolutePath): string { $normalized = realpath($absolutePath) ?: $absolutePath; $root = rtrim(ROOT_DIR, '/') . '/'; if (str_starts_with($normalized, $root)) { return substr($normalized, strlen($root)); } return $normalized; } }