Files
MokoCLI/lib/Enterprise/PluginRegistry.php
T
jmiller 4009d68a7a
Universal: PR Check / Branch Policy (pull_request) Failing after 2s
Generic: Repo Health / Site Health (pull_request) Has been skipped
Generic: Repo Health / Access control (pull_request) Successful in 3s
Universal: PR Check / Validate PR (pull_request) Failing after 14s
Universal: PR Check / Secret Scan (pull_request) Successful in 15s
Generic: Project CI / Lint & Validate (pull_request) Successful in 57s
Platform: mokocli CI / Gate 1: Code Quality (pull_request) Successful in 2m4s
Universal: Build & Release / Promote to RC (pull_request) Has been skipped
RC Revert / Rename rc/ back to dev/ (pull_request) Has been skipped
Branch Cleanup / Delete merged branch (pull_request) Successful in 2s
Universal: Build & Release / Build & Release Pipeline (pull_request) Successful in 35s
Universal: Workflow Sync Trigger / Sync workflows to live repos (pull_request) Failing after 28m16s
Generic: Project CI / Tests (pull_request) Has been cancelled
Platform: mokocli CI / Gate 2: Unit Tests (8.1) (pull_request) Has been cancelled
Platform: mokocli CI / Gate 2: Unit Tests (8.2) (pull_request) Has been cancelled
Platform: mokocli CI / Gate 2: Unit Tests (8.3) (pull_request) Has been cancelled
Platform: mokocli CI / Gate 3: Self-Health Check (pull_request) Has been cancelled
Platform: mokocli CI / Gate 4: Governance (pull_request) Has been cancelled
Platform: mokocli CI / Gate 5: Template Integrity (pull_request) Has been cancelled
Platform: mokocli CI / CI Summary (pull_request) Has been cancelled
Universal: PR Check / Build RC Package (pull_request) Has been cancelled
Universal: PR Check / Report Issues (pull_request) Has been cancelled
Generic: Repo Health / Scripts governance (pull_request) Has been cancelled
Generic: Repo Health / Repository health (pull_request) Has been cancelled
Generic: Repo Health / Report: Scripts Governance (pull_request) Has been cancelled
Generic: Repo Health / Report: Repository Health (pull_request) Has been cancelled
chore: rename all mokoplatform -> mokocli references
The repo was renamed mokoplatform -> mokocli; this rewrites every stale
reference across the tree (case-sensitive):

  MokoPlatform  -> MokoCLI   (DEFGROUP/INGROUP doc tags)
  mokoplatform  -> mokocli   (repo URLs, /opt & /tmp paths, clone URLs,
                              EXCLUDE lists, XML xmlns + <root> namespace)
  moko-platform -> moko-cli  (marker files)

XML namespace URIs and ManifestParser::NAMESPACE_URI are changed in
lockstep so local manifest-vs-parser validation stays consistent. The
external standards.mokoconsulting.tech namespace endpoint must be updated
to match separately (tracked in #336).

Refs #336

Claude-Session: https://claude.ai/code/session_01WbGBN9VyRK61zczYWcCQ2i
2026-07-05 15:25:45 -05:00

285 lines
8.6 KiB
PHP

<?php
declare(strict_types=1);
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of a Moko Consulting project.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoCLI.Enterprise.Plugins
* INGROUP: MokoCLI.Enterprise
* REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
* PATH: /lib/Enterprise/PluginRegistry.php
* BRIEF: Plugin registry for available project plugins
*/
namespace MokoCli;
use MokoCli\Plugins\JoomlaPlugin;
use MokoCli\Plugins\DolibarrPlugin;
use MokoCli\Plugins\GenericPlugin;
use MokoCli\Plugins\DocumentationPlugin;
use MokoCli\Plugins\NodeJsPlugin;
use MokoCli\Plugins\PythonPlugin;
use MokoCli\Plugins\TerraformPlugin;
use MokoCli\Plugins\WordPressPlugin;
use MokoCli\Plugins\MobilePlugin;
use MokoCli\Plugins\ApiPlugin;
use MokoCli\Plugins\McpServerPlugin;
/**
* Plugin Registry - Central registry for all project type plugins
*
* Manages plugin discovery, registration, and lifecycle
*
* @package MokoCLI\Enterprise
* @version 1.0.0
*/
class PluginRegistry
{
/** @var array<string, string> Map of project types to plugin class names */
private static $pluginClasses = [
'joomla' => JoomlaPlugin::class,
'dolibarr' => DolibarrPlugin::class,
'generic' => GenericPlugin::class,
'documentation' => DocumentationPlugin::class,
'nodejs' => NodeJsPlugin::class,
'python' => PythonPlugin::class,
'terraform' => TerraformPlugin::class,
'wordpress' => WordPressPlugin::class,
'mobile' => MobilePlugin::class,
'api' => ApiPlugin::class,
'mcp-server' => McpServerPlugin::class,
];
/** @var array<string, ProjectPluginInterface> Instantiated plugins */
private static $plugins = [];
/** @var AuditLogger|null Shared audit logger */
private static $logger = null;
/** @var MetricsCollector|null Shared metrics collector */
private static $metricsCollector = null;
/**
* Set shared logger for all plugins
*
* @param AuditLogger $logger Audit logger instance
* @return void
*/
public static function setLogger(AuditLogger $logger): void
{
self::$logger = $logger;
}
/**
* Set shared metrics collector for all plugins
*
* @param MetricsCollector $metricsCollector Metrics collector instance
* @return void
*/
public static function setMetricsCollector(MetricsCollector $metricsCollector): void
{
self::$metricsCollector = $metricsCollector;
}
/**
* Register a custom plugin for a project type
*
* @param string $projectType Project type identifier
* @param string $pluginClass Fully qualified plugin class name
* @return void
* @throws \InvalidArgumentException If plugin class doesn't implement ProjectPluginInterface
*/
public static function registerPlugin(string $projectType, string $pluginClass): void
{
if (!class_exists($pluginClass)) {
throw new \InvalidArgumentException("Plugin class does not exist: {$pluginClass}");
}
if (!is_subclass_of($pluginClass, ProjectPluginInterface::class)) {
throw new \InvalidArgumentException(
"Plugin class must implement ProjectPluginInterface: {$pluginClass}"
);
}
self::$pluginClasses[$projectType] = $pluginClass;
// Clear cached instance if exists
if (isset(self::$plugins[$projectType])) {
unset(self::$plugins[$projectType]);
}
}
/**
* Get plugin instance for a project type
*
* @param string $projectType Project type identifier
* @param array $config Optional plugin configuration
* @return ProjectPluginInterface|null Plugin instance or null if not found
*/
public static function getPlugin(string $projectType, array $config = []): ?ProjectPluginInterface
{
// Check if plugin is already instantiated
if (isset(self::$plugins[$projectType])) {
return self::$plugins[$projectType];
}
// Check if plugin class is registered
if (!isset(self::$pluginClasses[$projectType])) {
return null;
}
// Instantiate plugin
$pluginClass = self::$pluginClasses[$projectType];
$plugin = new $pluginClass(self::$logger, self::$metricsCollector, $config);
// Cache plugin instance
self::$plugins[$projectType] = $plugin;
return $plugin;
}
/**
* Get all registered project types
*
* @return array List of project type identifiers
*/
public static function getRegisteredTypes(): array
{
return array_keys(self::$pluginClasses);
}
/**
* Get all registered plugins
*
* @param array $config Optional plugin configuration
* @return array<string, ProjectPluginInterface> Map of project types to plugin instances
*/
public static function getAllPlugins(array $config = []): array
{
$plugins = [];
foreach (self::$pluginClasses as $projectType => $pluginClass) {
$plugins[$projectType] = self::getPlugin($projectType, $config);
}
return $plugins;
}
/**
* Check if a plugin is registered for a project type
*
* @param string $projectType Project type identifier
* @return bool True if plugin is registered
*/
public static function hasPlugin(string $projectType): bool
{
return isset(self::$pluginClasses[$projectType]);
}
/**
* Unregister a plugin
*
* @param string $projectType Project type identifier
* @return void
*/
public static function unregisterPlugin(string $projectType): void
{
unset(self::$pluginClasses[$projectType]);
unset(self::$plugins[$projectType]);
}
/**
* Clear all plugin instances (forces re-instantiation)
*
* @return void
*/
public static function clearCache(): void
{
self::$plugins = [];
}
/**
* Get plugin information
*
* @param string $projectType Project type identifier
* @return array|null Plugin info or null if not found
*/
public static function getPluginInfo(string $projectType): ?array
{
$plugin = self::getPlugin($projectType);
if ($plugin === null) {
return null;
}
return [
'project_type' => $plugin->getProjectType(),
'plugin_name' => $plugin->getPluginName(),
'plugin_version' => $plugin->getPluginVersion(),
'required_files' => $plugin->getRequiredFiles(),
'recommended_files' => $plugin->getRecommendedFiles(),
'best_practices_count' => count($plugin->getBestPractices()),
'commands_count' => count($plugin->getCommands()),
];
}
/**
* Get all plugins information
*
* @return array Map of project types to plugin information
*/
public static function getAllPluginsInfo(): array
{
$info = [];
foreach (self::getRegisteredTypes() as $projectType) {
$info[$projectType] = self::getPluginInfo($projectType);
}
return $info;
}
/**
* Find plugin by feature/capability
*
* @param string $feature Feature name (e.g., 'package_manager', 'type_checking')
* @return array List of project types supporting the feature
*/
public static function findPluginsByFeature(string $feature): array
{
$matches = [];
foreach (self::getRegisteredTypes() as $projectType) {
$plugin = self::getPlugin($projectType);
if ($plugin !== null) {
$bestPractices = $plugin->getBestPractices();
foreach ($bestPractices as $practice) {
if (
stripos($practice['title'] ?? '', $feature) !== false ||
stripos($practice['description'] ?? '', $feature) !== false
) {
$matches[] = $projectType;
break;
}
}
}
}
return $matches;
}
/**
* Get plugin registry statistics
*
* @return array Registry statistics
*/
public static function getStatistics(): array
{
return [
'total_plugins' => count(self::$pluginClasses),
'instantiated_plugins' => count(self::$plugins),
'registered_types' => self::getRegisteredTypes(),
'has_logger' => self::$logger !== null,
'has_metrics_collector' => self::$metricsCollector !== null,
];
}
}