Last active
February 12, 2025 12:47
-
-
Save maikschneider/7a3df2a210b54900e21dee361dea64cb to your computer and use it in GitHub Desktop.
Check if all TYPO3 extensions are required in sitepackage's composer.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env php | |
| <?php | |
| /** | |
| * TYPO3 Sitepackage Requirement Checker | |
| * This script checks if all TYPO3 extensions (type: typo3-cms-extension) from the root composer.json | |
| * are properly required in a sitepackage's composer.json file. | |
| * Usage: | |
| * ./sitepackage-req-checker.php packages/xima_sitepackage/composer.json | |
| * ./sitepackage-req-checker.php packages/xima_sitepackage/composer.json --ci=reports/junit.xml | |
| */ | |
| class RequirementChecker | |
| { | |
| private const ERROR_COLOR = "\e[0;31m"; | |
| private const SUCCESS_COLOR = "\e[0;32m"; | |
| private const INFO_COLOR = "\e[0;36m"; | |
| private const RESET_COLOR = "\e[0m"; | |
| private string $rootDir; | |
| private string $rootComposerFile; | |
| private string $vendorDir; | |
| private ?string $sitePackageComposerJson; | |
| private ?string $ciReportPath; | |
| private array $errors = []; | |
| private array $warnings = []; | |
| public function __construct() | |
| { | |
| $this->rootDir = getcwd(); | |
| $this->rootComposerFile = $this->rootDir . "/composer.json"; | |
| $this->vendorDir = $this->rootDir . "/vendor"; | |
| $this->sitePackageComposerJson = null; | |
| $this->ciReportPath = null; | |
| } | |
| /** | |
| * Validate environment and input parameters | |
| */ | |
| public function validateEnvironment(array $argv): bool | |
| { | |
| if (!$this->parseArguments($argv)) { | |
| return false; | |
| } | |
| // Check if composer.json exists | |
| if (!file_exists($this->rootComposerFile)) { | |
| $this->addError("Root composer.json is not present in the current directory."); | |
| return false; | |
| } | |
| // Check if vendor directory exists | |
| if (!is_dir($this->vendorDir)) { | |
| $this->addError("Vendor directory not present in the current directory."); | |
| return false; | |
| } | |
| // Validate sitepackage composer.json path | |
| if (!file_exists($this->sitePackageComposerJson)) { | |
| $this->addError("'{$this->sitePackageComposerJson}' not found."); | |
| return false; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Parse command line arguments | |
| */ | |
| private function parseArguments(array $argv): bool | |
| { | |
| // Check for sitepackage path | |
| if (!isset($argv[1])) { | |
| $this->addError("Provide relative path to composer.json of sitepackage as the first argument."); | |
| $this->printMessage("Usage: './sitepackage-req-checker.php packages/xima_sitepackage/composer.json [--ci=reports/junit.xml]'", | |
| self::INFO_COLOR); | |
| return false; | |
| } | |
| $this->sitePackageComposerJson = $this->rootDir . "/" . $argv[1]; | |
| // Check for CI report argument | |
| if (isset($argv[2])) { | |
| if (!str_starts_with($argv[2], '--ci')) { | |
| $this->addError("Invalid argument format. Use --ci=path/to/report.xml"); | |
| return false; | |
| } | |
| $this->ciReportPath = substr($argv[2], 5); // Remove '--ci=' prefix | |
| if (empty($this->ciReportPath)) { | |
| $this->ciReportPath = 'junit.xml'; // Default filename if no path provided | |
| } | |
| // Ensure directory exists | |
| $reportDir = dirname($this->ciReportPath); | |
| if (!is_dir($reportDir) && !mkdir($reportDir, 0777, true) && !is_dir($reportDir)) { | |
| $this->addError("Could not create directory for CI report: {$reportDir}"); | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| /** | |
| * Add error message to collection | |
| */ | |
| private function addError(string $message): void | |
| { | |
| $this->errors[] = $message; | |
| $this->printMessage("Error: " . $message, self::ERROR_COLOR); | |
| } | |
| /** | |
| * Print colored message to console | |
| */ | |
| private function printMessage(string $message, string $color = self::INFO_COLOR): void | |
| { | |
| printf($color . $message . self::RESET_COLOR . PHP_EOL); | |
| } | |
| /** | |
| * Check requirements between root composer.json and sitepackage | |
| */ | |
| public function checkRequirements(): bool | |
| { | |
| try { | |
| // Load composer files | |
| $rootComposerData = $this->loadJsonFile($this->rootComposerFile); | |
| $sitePackageComposerData = $this->loadJsonFile($this->sitePackageComposerJson); | |
| // Validate sitepackage composer.json structure | |
| if (!isset($sitePackageComposerData['require'])) { | |
| $this->addError("'require' object is missing in sitepackage composer.json."); | |
| return false; | |
| } | |
| if (!isset($sitePackageComposerData['name'])) { | |
| $this->addError("'name' is missing in sitepackage composer.json."); | |
| return false; | |
| } | |
| // Get all dependencies excluding the sitepackage itself | |
| $sitePackageName = $sitePackageComposerData['name']; | |
| $requires = array_keys(array_diff_key($rootComposerData['require'], [$sitePackageName => null])); | |
| // Check for missing TYPO3 extensions | |
| $packagesMissing = $this->findMissingPackages($requires, $sitePackageComposerData['require']); | |
| if (!empty($packagesMissing)) { | |
| $this->printMessage("The following TYPO3 extensions are missing in the sitepackage:", self::ERROR_COLOR); | |
| foreach ($packagesMissing as $missing) { | |
| $this->printMessage(" - {$missing}", self::ERROR_COLOR); | |
| } | |
| $this->printMessage("\nPlease add them to '{$this->sitePackageComposerJson}'", self::ERROR_COLOR); | |
| // Generate CI report if requested | |
| $this->generateJUnitReport($packagesMissing); | |
| return false; | |
| } | |
| // Generate CI report even when successful | |
| $this->generateJUnitReport([]); | |
| $this->printMessage("Success: All required TYPO3 extensions are properly included!", self::SUCCESS_COLOR); | |
| return true; | |
| } catch (Exception $e) { | |
| $this->addError($e->getMessage()); | |
| $this->generateJUnitReport([]); | |
| return false; | |
| } | |
| } | |
| /** | |
| * Load and decode JSON file | |
| */ | |
| private function loadJsonFile(string $path): array | |
| { | |
| $content = file_get_contents($path); | |
| if ($content === false) { | |
| throw new Exception("Failed to read file: {$path}"); | |
| } | |
| $data = json_decode($content, true); | |
| if (json_last_error() !== JSON_ERROR_NONE) { | |
| throw new Exception("Invalid JSON in file: {$path} - " . json_last_error_msg()); | |
| } | |
| return $data; | |
| } | |
| /** | |
| * Find missing TYPO3 extensions | |
| */ | |
| private function findMissingPackages(array $requires, array $sitePackageRequires): array | |
| { | |
| $packagesMissing = []; | |
| foreach ($requires as $require) { | |
| $composerJsonPath = $this->vendorDir . '/' . $require . '/composer.json'; | |
| if (file_exists($composerJsonPath)) { | |
| try { | |
| $packageData = $this->loadJsonFile($composerJsonPath); | |
| if (isset($packageData['type']) && $packageData['type'] === 'typo3-cms-extension') { | |
| if (!isset($sitePackageRequires[$require])) { | |
| $packagesMissing[] = $require; | |
| } | |
| } | |
| } catch (Exception $e) { | |
| $this->addWarning("Could not process {$require}: " . $e->getMessage()); | |
| } | |
| } | |
| } | |
| return array_unique($packagesMissing); | |
| } | |
| /** | |
| * Add warning message to collection | |
| */ | |
| private function addWarning(string $message): void | |
| { | |
| $this->warnings[] = $message; | |
| $this->printMessage("Warning: " . $message, self::INFO_COLOR); | |
| } | |
| /** | |
| * Generate JUnit XML report | |
| */ | |
| private function generateJUnitReport(array $packagesMissing): void | |
| { | |
| if (!$this->ciReportPath) { | |
| return; | |
| } | |
| $timestamp = date('c'); | |
| $testCount = count($packagesMissing) + count($this->warnings); | |
| $failures = count($packagesMissing); | |
| $errors = count($this->errors); | |
| $xml = new SimpleXMLElement('<?xml version="1.0" encoding="UTF-8"?><testsuites/>'); | |
| $testsuite = $xml->addChild('testsuite'); | |
| $testsuite->addAttribute('name', 'TYPO3 Sitepackage Requirements'); | |
| $testsuite->addAttribute('tests', $testCount); | |
| $testsuite->addAttribute('failures', $failures); | |
| $testsuite->addAttribute('errors', $errors); | |
| $testsuite->addAttribute('timestamp', $timestamp); | |
| // Add missing packages as test failures | |
| foreach ($packagesMissing as $package) { | |
| $testcase = $testsuite->addChild('testcase'); | |
| $testcase->addAttribute('name', "Check requirement: {$package}"); | |
| $testcase->addAttribute('classname', 'RequirementChecker'); | |
| $failure = $testcase->addChild('failure'); | |
| $failure->addAttribute('type', 'MissingRequirement'); | |
| $failure->addAttribute('message', "Package {$package} is missing in sitepackage requirements"); | |
| } | |
| // Add warnings as successful tests with system-out | |
| foreach ($this->warnings as $warning) { | |
| $testcase = $testsuite->addChild('testcase'); | |
| $testcase->addAttribute('name', "Warning: " . substr($warning, 0, 50) . "..."); | |
| $testcase->addAttribute('classname', 'RequirementChecker'); | |
| $systemOut = $testcase->addChild('system-out'); | |
| $systemOut[0] = $warning; | |
| } | |
| // Save the XML file | |
| $dom = new DOMDocument('1.0'); | |
| $dom->preserveWhiteSpace = false; | |
| $dom->formatOutput = true; | |
| $dom->loadXML($xml->asXML() ?: ''); | |
| if (!$dom->save($this->ciReportPath)) { | |
| $this->addError("Failed to write CI report to {$this->ciReportPath}"); | |
| } else { | |
| $this->printMessage("JUnit report generated: {$this->ciReportPath}", self::SUCCESS_COLOR); | |
| } | |
| } | |
| } | |
| // Run the checker | |
| $checker = new RequirementChecker(); | |
| if ($checker->validateEnvironment($argv)) { | |
| exit($checker->checkRequirements() ? 0 : 1); | |
| } | |
| exit(1); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment