#!/usr/bin/env php * * This file is part of a Moko Consulting project. * * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION * DEFGROUP: MokoStandards.Scripts.Validate * INGROUP: MokoStandards * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /validate/check_composer_deps.php * BRIEF: Validate composer.json enterprise dependency across all governed repos * * USAGE * php validate/check_composer_deps.php --repo MokoCRM # Single repo * php validate/check_composer_deps.php --all # All repos * php validate/check_composer_deps.php --all --json # JSON output */ declare(strict_types=1); $allMode = in_array('--all', $argv); $jsonOut = in_array('--json', $argv); $org = 'mokoconsulting-tech'; $repoName = null; foreach ($argv as $i => $arg) { if ($arg === '--repo' && isset($argv[$i + 1])) { $repoName = $argv[$i + 1]; } if ($arg === '--org' && isset($argv[$i + 1])) { $org = $argv[$i + 1]; } } if (!$repoName && !$allMode) { fwrite(STDERR, "Usage: php check_composer_deps.php --repo | --all [--json]\n"); exit(2); } $config = \MokoEnterprise\Config::load(); try { $_adapter = \MokoEnterprise\PlatformAdapterFactory::create($config); $_api = $_adapter->getApiClient(); } catch (\Exception $e) { fwrite(STDERR, "Platform init failed: " . $e->getMessage() . "\n"); exit(1); } $token = $config->getString('platform', 'gitea') === 'gitea' ? $config->getString('gitea.token', '') : $config->getString('github.token', ''); $EXPECTED_VERSION = '04.02.30'; $EXPECTED_DEP = "dev-version/{$EXPECTED_VERSION}"; $ENTERPRISE_PKG = 'mokoconsulting-tech/enterprise'; $ALWAYS_EXCLUDE = ['MokoStandards', '.github-private']; /** * GitHub REST API GET helper. * * @return array{int, array} */ function apiGet(string $path, string $token): array { global $_api; try { $result = $_api->get("/{$path}"); return [200, $result]; } catch (\Exception $e) { return [500, ['message' => $e->getMessage()]]; } } /** * Fetch and parse composer.json from a repo. * * @return array|null */ function fetchComposer(string $org, string $repo, string $token): ?array { [$status, $data] = apiGet("repos/{$org}/{$repo}/contents/composer.json", $token); if ($status !== 200 || empty($data['content'])) { return null; } return json_decode(base64_decode($data['content']), true); } // ── Build repo list ───────────────────────────────────────────────────── $repos = []; if ($allMode) { echo "Fetching repositories from {$org}...\n"; $page = 1; do { [$_, $batch] = apiGet("orgs/{$org}/repos?per_page=100&page={$page}&type=all", $token); foreach ($batch as $r) { if (!($r['archived'] ?? false) && !in_array($r['name'], $ALWAYS_EXCLUDE, true)) { $repos[] = $r['name']; } } $page++; } while (count($batch) === 100); sort($repos); echo "Found " . count($repos) . " repositories\n\n"; } else { $repos = [$repoName]; } // ── Check each repo ───────────────────────────────────────────────────── $results = []; $issueCount = 0; foreach ($repos as $repo) { $result = [ 'repo' => $repo, 'has_composer' => false, 'has_enterprise' => false, 'version' => null, 'version_ok' => false, 'has_lock' => false, 'issues' => [], ]; $composer = fetchComposer($org, $repo, $token); if ($composer === null) { $result['issues'][] = 'No composer.json found'; if (!$jsonOut) { echo "{$repo}: no composer.json\n"; } $results[] = $result; continue; } $result['has_composer'] = true; // Check for enterprise dependency $allDeps = array_merge($composer['require'] ?? [], $composer['require-dev'] ?? []); if (isset($allDeps[$ENTERPRISE_PKG])) { $result['has_enterprise'] = true; $result['version'] = $allDeps[$ENTERPRISE_PKG]; if ($allDeps[$ENTERPRISE_PKG] === $EXPECTED_DEP) { $result['version_ok'] = true; } else { $result['issues'][] = "Version mismatch: {$allDeps[$ENTERPRISE_PKG]} (expected {$EXPECTED_DEP})"; if ($allDeps[$ENTERPRISE_PKG] === 'dev-main') { $result['issues'][] = 'STALE: pointing to dev-main instead of version branch'; } } } else { $result['issues'][] = 'Enterprise dependency not in require/require-dev'; } // Check for composer.lock [$lockStatus] = apiGet("repos/{$org}/{$repo}/contents/composer.lock", $token); $result['has_lock'] = ($lockStatus === 200); if (!$result['has_lock']) { $result['issues'][] = 'No composer.lock committed'; } if (!$jsonOut) { if (empty($result['issues'])) { echo "{$repo}: OK ({$result['version']})\n"; } else { foreach ($result['issues'] as $issue) { echo "{$repo}: {$issue}\n"; $issueCount++; } } } $results[] = $result; } // ── Output ────────────────────────────────────────────────────────────── if ($jsonOut) { echo json_encode($results, JSON_PRETTY_PRINT) . "\n"; } else { echo "\n" . str_repeat('-', 50) . "\n"; $total = count($results); $withDep = count(array_filter($results, fn($r) => $r['has_enterprise'])); $ok = count(array_filter($results, fn($r) => $r['version_ok'])); $stale = count(array_filter($results, fn($r) => $r['version'] === 'dev-main')); echo "Total: {$total} | With enterprise dep: {$withDep} | Correct version: {$ok}"; if ($stale > 0) { echo " | Stale dev-main: {$stale}"; } echo " | Issues: {$issueCount}\n"; } exit($issueCount > 0 ? 1 : 0);