#!/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.CLI * INGROUP: MokoStandards * REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform * PATH: /bin/moko * BRIEF: Unified CLI dispatcher — run any MokoStandards script without needing GitHub Actions * * USAGE * php bin/moko [options] (all platforms) * ./bin/moko [options] (Unix, after: chmod +x bin/moko) * * COMMANDS * sync Bulk-sync MokoStandards to organisation repos * health Full repository health check (runs most validators) * inventory Refresh docs/reference/REPOSITORY_INVENTORY.md * * check:syntax PHP syntax check (php -l) on all tracked .php files * check:version Verify VERSION fields and badges match composer.json * check:changelog Validate CHANGELOG.md format * check:structure Verify required root files and directories * check:headers Check SPDX-License-Identifier presence in source files * check:secrets Scan for leaked credentials / API keys * check:tabs Detect tab characters in YAML files * check:paths Detect backslash path separators in PHP source * check:xml Validate XML files are well-formed * check:enterprise Full enterprise-readiness check (headers, strict types, PSR-12) * check:dolibarr Validate Dolibarr module directory structure * check:joomla Validate Joomla XML manifest * check:language Validate Joomla/Dolibarr .ini language files * detect Auto-detect repository platform type * drift Scan org repos for drift from MokoStandards templates * * COMMON OPTIONS (passed through to each script) * --path Repository root to check (default: .) * --dry-run Preview changes without applying them * --verbose Show passing checks as well as failures * --quiet Show only failures * --json Machine-readable JSON output * --help Show help for the selected command * * AUTHENTICATION * Token resolution order (first non-empty wins): * 1. GH_TOKEN environment variable * 2. GITHUB_TOKEN environment variable * 3. `gh auth token` (GitHub CLI — run `gh auth login` once) * 4. .env file in repo root (GH_TOKEN=... line) * * EXAMPLES * php bin/moko health * php bin/moko sync -- --repos MokoDoliTraining --dry-run * php bin/moko check:version --path . * php bin/moko drift -- --org mokoconsulting-tech --json */ declare(strict_types=1); // ── Bootstrap ──────────────────────────────────────────────────────────────── $repoRoot = dirname(__DIR__); $autoloader = $repoRoot . '/vendor/autoload.php'; // Support global Composer installs (e.g. composer global require) if (isset($GLOBALS['_composer_autoload_path'])) { $autoloader = $GLOBALS['_composer_autoload_path']; } if (!is_file($autoloader)) { fwrite(STDERR, "Error: vendor/autoload.php not found.\nRun: composer install\n"); exit(2); } require_once $autoloader; // ── Command map ────────────────────────────────────────────────────────────── /** * Map of moko command names → relative path to the PHP script. * All paths are relative to the repo root. */ const COMMAND_MAP = [ // Automation 'sync' => 'automation/bulk_sync.php', // Maintenance 'inventory' => 'maintenance/update_repo_inventory.php', // Validation — general 'health' => 'validate/check_repo_health.php', 'check:syntax' => 'validate/check_php_syntax.php', 'check:version' => 'validate/check_version_consistency.php', 'check:changelog' => 'validate/check_changelog.php', 'check:structure' => 'validate/check_structure.php', 'check:headers' => 'validate/check_license_headers.php', 'check:secrets' => 'validate/check_no_secrets.php', 'check:tabs' => 'validate/check_tabs.php', 'check:paths' => 'validate/check_paths.php', 'check:xml' => 'validate/check_xml_wellformed.php', 'check:enterprise' => 'validate/check_enterprise_readiness.php', // Validation — platform-specific 'check:dolibarr' => 'validate/check_dolibarr_module.php', 'check:joomla' => 'validate/check_joomla_manifest.php', 'check:language' => 'validate/check_language_structure.php', 'check:client' => 'validate/check_client_theme.php', 'check:wiki' => 'validate/check_wiki_health.php', // Detection 'detect' => 'validate/auto_detect_platform.php', // Org-wide 'drift' => 'validate/scan_drift.php', // Release 'release' => 'cli/release.php', 'release:notes' => 'cli/release_notes.php', 'release:validate' => 'cli/release_validate.php', 'manifest:element' => 'cli/manifest_element.php', 'release:cascade' => 'cli/release_cascade.php', 'release:promote' => 'cli/release_promote.php', 'release:create' => 'cli/release_create.php', 'release:manage' => 'cli/release_manage.php', 'release:mirror' => 'cli/release_mirror.php', 'release:package' => 'cli/release_package.php', // Changelog 'changelog:promote' => 'cli/changelog_promote.php', 'changelog:prune' => 'cli/changelog_prune.php', // Version management 'version:read' => 'cli/version_read.php', 'version:bump' => 'cli/version_bump.php', 'version:check' => 'cli/version_check.php', 'version:propagate' => 'maintenance/update_version_from_readme.php', 'version:set-platform' => 'cli/version_set_platform.php', 'version:reset-dev' => 'cli/version_reset_dev.php', // Build & package 'build:package' => 'cli/package_build.php', 'build:joomla' => 'cli/joomla_build.php', 'build:updates-xml' => 'cli/updates_xml_build.php', // Platform detection 'platform:detect' => 'cli/platform_detect.php', 'manifest:read' => 'cli/manifest_read.php', // Repository management 'repo:create' => 'cli/create_repo.php', 'repo:archive' => 'cli/archive_repo.php', 'repo:scaffold-client' => 'cli/scaffold_client.php', 'repo:provision' => 'cli/client_provision.php', // Bulk operations 'bulk:push-workflow' => 'cli/bulk_workflow_push.php', 'bulk:trigger' => 'cli/bulk_workflow_trigger.php', 'bulk:sync-rulesets' => 'cli/sync_rulesets.php', // Monitoring & dashboards 'dashboard' => 'cli/client_dashboard.php', 'grafana' => 'cli/grafana_dashboard.php', 'client:inventory' => 'cli/client_inventory.php', // Module validation 'validate:module' => 'bin/validate-module', ]; // ── Argument parsing ───────────────────────────────────────────────────────── $args = array_slice($argv, 1); $command = array_shift($args) ?? ''; // Strip leading -- separator that Composer passes when using `composer run-script cmd -- extra-args` if (isset($args[0]) && $args[0] === '--') { array_shift($args); } // ── Help / list ─────────────────────────────────────────────────────────────── if ($command === '' || $command === '--help' || $command === '-h' || $command === 'help') { printHelp(); exit(0); } if ($command === 'list' || $command === 'commands') { printCommandList(); exit(0); } // ── Dispatch ────────────────────────────────────────────────────────────────── if (!array_key_exists($command, COMMAND_MAP)) { fwrite(STDERR, "Error: Unknown command '{$command}'\n\n"); printCommandList(); exit(2); } $scriptPath = $repoRoot . '/' . COMMAND_MAP[$command]; if (!is_file($scriptPath)) { fwrite(STDERR, "Error: Script not found: " . COMMAND_MAP[$command] . "\n"); fwrite(STDERR, "Ensure the repository is complete and run: composer install\n"); exit(2); } // Rebuild $argv as if the target script were invoked directly, then include it. // This is equivalent to: php