#!/usr/bin/env php * * REQUIRED FILE: This file must be present in all MokoStandards-compliant repositories * * This file is part of a Moko Consulting project. * * SPDX-License-Identifier: GPL-3.0-or-later * * FILE INFORMATION * DEFGROUP: MokoStandards.Scripts.Maintenance * INGROUP: MokoStandards * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /maintenance/setup_labels.php * BRIEF: REQUIRED label deployment script for all MokoStandards-governed repositories */ declare(strict_types=1); require_once __DIR__ . '/../vendor/autoload.php'; use MokoEnterprise\CliFramework; use MokoEnterprise\Config; use MokoEnterprise\GitPlatformAdapter; use MokoEnterprise\PlatformAdapterFactory; /** * Deploys the standard set of repository labels required by MokoStandards. * * Uses the platform adapter (GitHub or Gitea) to create or update each label. * Supports --dry-run mode to preview without making changes. */ class SetupLabels extends CliFramework { private ?GitPlatformAdapter $adapter = null; /** * Label definitions — [name, hexColor (no #), description]. * * @var list */ private const LABELS = [ // Project Type ['joomla', '7F52FF', 'Joomla extension or component'], ['dolibarr', 'FF6B6B', 'Dolibarr module or extension'], ['generic', '808080', 'Generic project or library'], // Language ['php', '4F5D95', 'PHP code changes'], ['javascript', 'F7DF1E', 'JavaScript code changes'], ['typescript', '3178C6', 'TypeScript code changes'], ['python', '3776AB', 'Python code changes'], ['css', '1572B6', 'CSS/styling changes'], ['html', 'E34F26', 'HTML template changes'], // Component ['documentation', '0075CA', 'Documentation changes'], ['ci-cd', '000000', 'CI/CD pipeline changes'], ['docker', '2496ED', 'Docker configuration changes'], ['tests', '00FF00', 'Test suite changes'], ['security', 'FF0000', 'Security-related changes'], ['dependencies', '0366D6', 'Dependency updates'], ['config', 'F9D0C4', 'Configuration file changes'], ['build', 'FFA500', 'Build system changes'], // Workflow / Process ['automation', '8B4513', 'Automated processes or scripts'], ['mokostandards', 'B60205', 'MokoStandards compliance'], ['needs-review', 'FBCA04', 'Awaiting code review'], ['work-in-progress', 'D93F0B', 'Work in progress, not ready for merge'], ['breaking-change', 'D73A4A', 'Breaking API or functionality change'], // Priority ['priority: critical', 'B60205', 'Critical priority, must be addressed immediately'], ['priority: high', 'D93F0B', 'High priority'], ['priority: medium', 'FBCA04', 'Medium priority'], ['priority: low', '0E8A16', 'Low priority'], // Type ['type: bug', 'D73A4A', "Something isn't working"], ['type: feature', 'A2EEEF', 'New feature or request'], ['type: enhancement', '84B6EB', 'Enhancement to existing feature'], ['type: refactor', 'F9D0C4', 'Code refactoring'], ['type: chore', 'FEF2C0', 'Maintenance tasks'], // Status ['status: pending', 'FBCA04', 'Pending action or decision'], ['status: in-progress', '0E8A16', 'Currently being worked on'], ['status: blocked', 'B60205', 'Blocked by another issue or dependency'], ['status: on-hold', 'D4C5F9', 'Temporarily on hold'], ['status: wontfix', 'FFFFFF', 'This will not be worked on'], // Size ['size/xs', 'C5DEF5', 'Extra small change (1-10 lines)'], ['size/s', '6FD1E2', 'Small change (11-30 lines)'], ['size/m', 'F9DD72', 'Medium change (31-100 lines)'], ['size/l', 'FFA07A', 'Large change (101-300 lines)'], ['size/xl', 'FF6B6B', 'Extra large change (301-1000 lines)'], ['size/xxl', 'B60205', 'Extremely large change (1000+ lines)'], // Health ['health: excellent', '0E8A16', 'Health score 90-100'], ['health: good', 'FBCA04', 'Health score 70-89'], ['health: fair', 'FFA500', 'Health score 50-69'], ['health: poor', 'FF6B6B', 'Health score below 50'], // Sync / Automation ['standards-update', 'B60205', 'MokoStandards sync update'], ['standards-drift', 'FBCA04', 'Repository drifted from MokoStandards'], ['sync-report', '0075CA', 'Bulk sync run report'], ['sync-failure', 'D73A4A', 'Bulk sync failure requiring attention'], ['push-failure', 'D73A4A', 'File push failure requiring attention'], ['health-check', '0E8A16', 'Repository health check results'], ['version-drift', 'FFA500', 'Version mismatch detected'], ['deploy-failure', 'CC0000', 'Automated deploy failure tracking'], ['template-validation-failure', 'D73A4A', 'Template workflow validation failure'], ['version', '0E8A16', 'Version bump or release'], ['type: version', '0E8A16', 'Version-related change'], // Testing ['type: test', '00FF00', 'Test suite additions or changes'], ['needs-testing', 'FBCA04', 'Requires manual or automated testing'], ['test-failure', 'D73A4A', 'Automated test failure'], ['regression', 'B60205', 'Regression from a previous working state'], // Version & Release ['type: release', '0E8A16', 'Release preparation or tracking'], ['release-candidate', 'BFD4F2', 'Release candidate build'], ['minor-release', '0E8A16', 'Minor version release (XX.YY.00)'], ['patch-release', 'C5DEF5', 'Patch version release (XX.YY.ZZ)'], ['major-release', 'B60205', 'Major version release (breaking changes)'], ['version-branch', '1D76DB', 'Version branch related'], ]; /** * Configure available arguments. */ protected function configure(): void { $this->setDescription('REQUIRED: Deploy standard labels to repository'); $this->addArgument('--dry-run', 'Show what would be created without actually creating labels', false); $this->addArgument('--org', 'Organization name', 'mokoconsulting-tech'); $this->addArgument('--repo', 'Repository name (defaults to current repo)', ''); } /** * Run the label deployment. * * @return int Exit code: 0 on success, 1 on error. */ protected function run(): int { $dryRun = (bool) $this->getArgument('--dry-run'); $config = Config::load(); try { $this->adapter = PlatformAdapterFactory::create($config); } catch (\RuntimeException $e) { $this->log('ERROR', $e->getMessage()); return 1; } $orgArg = (string) $this->getArgument('--org'); $repoArg = (string) $this->getArgument('--repo'); $org = $orgArg ?: $config->getString($this->adapter->getPlatformName() . '.organization', 'mokoconsulting-tech'); $repo = $repoArg ?: basename(getcwd() ?: '.'); $this->log('INFO', "Setting up labels for repository: {$org}/{$repo} ({$this->adapter->getPlatformName()})"); echo "\n"; $this->deployGroup('Creating REQUIRED project type labels...', 0, 2, $org, $repo, $dryRun); $this->deployGroup('Creating REQUIRED language labels...', 3, 8, $org, $repo, $dryRun); $this->deployGroup('Creating REQUIRED component labels...', 9, 16, $org, $repo, $dryRun); $this->deployGroup('Creating REQUIRED workflow labels...', 17, 21, $org, $repo, $dryRun); $this->deployGroup('Creating REQUIRED priority labels...', 22, 25, $org, $repo, $dryRun); $this->deployGroup('Creating REQUIRED type labels...', 26, 30, $org, $repo, $dryRun); $this->deployGroup('Creating REQUIRED status labels...', 31, 35, $org, $repo, $dryRun); $this->deployGroup('Creating REQUIRED size labels...', 36, 41, $org, $repo, $dryRun); $this->deployGroup('Creating REQUIRED health labels...', 42, 45, $org, $repo, $dryRun); $this->deployGroup('Creating REQUIRED sync/automation labels...', 46, 56, $org, $repo, $dryRun); $this->deployGroup('Creating REQUIRED testing labels...', 57, 60, $org, $repo, $dryRun); $this->deployGroup('Creating REQUIRED version/release labels...', 61, 66, $org, $repo, $dryRun); echo "\n============================================================\n"; if ($dryRun) { $this->log('INFO', '[DRY-RUN] Label deployment simulation completed'); } else { $this->log('INFO', 'Label deployment completed successfully!'); echo "\n - TOTAL: " . count(self::LABELS) . " labels\n"; } echo "============================================================\n\n"; return 0; } // ── Private helpers ─────────────────────────────────────────────────────── /** * Deploy a named group of labels by index range in self::LABELS. * * @param string $heading Informational banner printed before the group. * @param int $fromIndex First label index (inclusive). * @param int $toIndex Last label index (inclusive). * @param string $org Organization name. * @param string $repo Repository name. * @param bool $dryRun When true, preview only. */ private function deployGroup(string $heading, int $fromIndex, int $toIndex, string $org, string $repo, bool $dryRun): void { $this->log('INFO', $heading); for ($i = $fromIndex; $i <= $toIndex; $i++) { [$name, $color, $desc] = self::LABELS[$i]; $this->createLabelViaApi($name, $color, $desc, $org, $repo, $dryRun); } echo "\n"; } /** * Create or update a single label via the platform adapter. * * @param string $name Label name. * @param string $color Hex colour without the leading '#'. * @param string $desc Short description text. * @param string $org Organization name. * @param string $repo Repository name. * @param bool $dryRun When true, preview only. */ private function createLabelViaApi(string $name, string $color, string $desc, string $org, string $repo, bool $dryRun): void { if ($dryRun) { echo "[DRY-RUN] Would create label: {$name} (color: #{$color}, description: {$desc})\n"; return; } try { $this->adapter->createLabel($org, $repo, $name, $color, $desc); $this->log('INFO', "Created/updated label: {$name}"); } catch (\Exception $e) { // Label may already exist — that's fine if (str_contains($e->getMessage(), '422') || str_contains($e->getMessage(), 'already exists')) { $this->log('INFO', "Label already exists: {$name}"); } else { $this->log('WARNING', "Failed to create label: {$name} — " . $e->getMessage()); } } } } $script = new SetupLabels('setup_labels', 'REQUIRED: Deploy standard labels to repository'); exit($script->execute());