#!/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 (run `php bin/moko list` for the full list — 97 commands) * * Automation sync, automation:cleanup, automation:migrate-gitea * Validation health, detect, drift, check:syntax, check:version, ... * Release release, release:joomla, release:create, release:publish, ... * Version version:read, version:bump, version:auto-bump, ... * Build build:package, build:joomla, build:updates-xml, ... * Deploy deploy:joomla, deploy:dolibarr, deploy:sftp, deploy:rollback, ... * Repository repo:create, repo:archive, repo:rename-branch, repo:reset-dev, ... * Bulk Operations bulk:push-workflow, bulk:push-manifest, bulk:template-joomla, ... * Maintenance maintenance:labels, maintenance:rotate-secrets, maintenance:pin-shas, ... * Fix fix:line-endings, fix:tabs, fix:trailing, fix:permissions * Monitoring dashboard, grafana, client:inventory, client:health-check * Platform platform:detect, manifest:read, manifest:element * Wiki wiki:sync * Badges badge:update * * 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 = [ // Audit 'audit:query' => 'cli/audit_query.php', // Automation 'sync' => 'automation/bulk_sync.php', 'automation:cleanup' => 'automation/repo_cleanup.php', 'automation:migrate-gitea' => 'automation/migrate_to_gitea.php', // Maintenance 'inventory' => 'maintenance/update_repo_inventory.php', 'maintenance:pin-shas' => 'maintenance/pin_action_shas.php', 'maintenance:inventory' => 'maintenance/repo_inventory.php', 'maintenance:rotate-secrets' => 'maintenance/rotate_secrets.php', 'maintenance:labels' => 'maintenance/setup_labels.php', 'maintenance:sync-dolibarr' => 'maintenance/sync_dolibarr_readmes.php', 'maintenance:update-shas' => 'maintenance/update_sha_hashes.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:joomla-compat' => 'cli/joomla_compat_check.php', 'check:language' => 'validate/check_language_structure.php', 'check:client' => 'validate/check_client_theme.php', 'check:theme' => 'cli/theme_lint.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', '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', 'release:joomla' => 'cli/joomla_release.php', 'release:body-update' => 'cli/release_body_update.php', 'release:publish' => 'cli/release_publish.php', 'release:verify' => 'cli/release_verify.php', 'release:gen-dolibarr' => 'release/generate_dolibarr_version_txt.php', 'release:gen-joomla' => 'release/generate_joomla_update_xml.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', 'version:auto-bump' => 'cli/version_auto_bump.php', 'version:bump-remote' => 'cli/version_bump_remote.php', // Build & package 'build:package' => 'cli/package_build.php', 'build:joomla' => 'cli/joomla_build.php', 'build:updates-xml' => 'cli/updates_xml_build.php', 'build:updates-xml-sync' => 'cli/updates_xml_sync.php', // Platform detection & manifest 'platform:detect' => 'cli/platform_detect.php', 'manifest:read' => 'cli/manifest_read.php', 'manifest:element' => 'cli/manifest_element.php', // Repository management 'repo:create' => 'cli/create_repo.php', 'repo:create-project' => 'cli/create_project.php', 'repo:archive' => 'cli/archive_repo.php', 'repo:scaffold-client' => 'cli/scaffold_client.php', 'repo:provision' => 'cli/client_provision.php', 'repo:rename-branch' => 'cli/branch_rename.php', 'repo:reset-dev' => 'cli/dev_branch_reset.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', 'bulk:push-files' => 'automation/push_files.php', 'bulk:push-manifest' => 'automation/push_manifest_xml.php', 'bulk:push-mokostandards' => 'automation/push_mokostandards_xml.php', 'bulk:enrich-manifest' => 'automation/enrich_manifest_xml.php', 'bulk:enrich-mokostandards' => 'automation/enrich_mokostandards_xml.php', 'bulk:template-joomla' => 'automation/bulk_joomla_template.php', // Deploy 'deploy:joomla' => 'cli/deploy_joomla.php', 'deploy:joomla-legacy' => 'deploy/deploy-joomla.php', 'deploy:dolibarr' => 'deploy/deploy-dolibarr.php', 'deploy:sftp' => 'deploy/deploy-sftp.php', 'deploy:backup' => 'deploy/backup-before-deploy.php', 'deploy:health-check' => 'deploy/health-check.php', 'deploy:rollback' => 'deploy/rollback-joomla.php', 'deploy:sync' => 'deploy/sync-joomla.php', // Fix / auto-remediation 'fix:line-endings' => 'fix/fix_line_endings.php', 'fix:tabs' => 'fix/fix_tabs.php', 'fix:trailing' => 'fix/fix_trailing_spaces.php', 'fix:permissions' => 'fix/fix_permissions.php', // Monitoring & dashboards 'dashboard' => 'cli/client_dashboard.php', 'grafana' => 'cli/grafana_dashboard.php', 'client:inventory' => 'cli/client_inventory.php', 'client:health-check' => 'cli/client_health_check.php', // Badge & wiki 'badge:update' => 'cli/badge_update.php', 'wiki:sync' => 'cli/wiki_sync.php', // Licensing 'license' => 'cli/license_manage.php', // Shell completion 'completion' => 'cli/completion.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 ────────────────────────────────────────────────────────────────── $scriptRelative = null; if (array_key_exists($command, COMMAND_MAP)) { $scriptRelative = COMMAND_MAP[$command]; } else { // Fall back to plugin-provided commands before giving up. $pluginCommands = loadPluginCommands(); if (isset($pluginCommands[$command]) && !empty($pluginCommands[$command]['script'])) { $scriptRelative = $pluginCommands[$command]['script']; } } if ($scriptRelative === null) { fwrite(STDERR, "Error: Unknown command '{$command}'\n\n"); printCommandList(); exit(2); } $scriptPath = $repoRoot . '/' . $scriptRelative; if (!is_file($scriptPath)) { fwrite(STDERR, "Error: Script not found: {$scriptRelative}\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