feat(modules): harden module platform with event constants, validation checks, and CLI improvements
- Add ModuleEvents constants class with all 8 event strings; update dispatch call sites - ValidateCommand: check AuthorizationPolicy container registration (check 20) - ValidateCommand: warn on unknown event_listeners keys (check 21) - make:module --simple flag for minimal manifests - module:deactivate reverse-dependency warning - 12 new PHPUnit tests covering all improvements Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -227,25 +227,31 @@ final class ModuleRunner implements ModuleRunnerInterface
|
||||
return 1;
|
||||
}
|
||||
|
||||
$manifest = ModuleManifestLoader::loadFromDisk(
|
||||
ModuleCliRuntime::projectRoot() . '/modules',
|
||||
$moduleId
|
||||
);
|
||||
$modulesDir = ModuleCliRuntime::projectRoot() . '/modules';
|
||||
$manifest = ModuleManifestLoader::loadFromDisk($modulesDir, $moduleId);
|
||||
|
||||
// Check for reverse dependencies (other modules that require this one)
|
||||
$dependents = $this->findReverseDependencies($modulesDir, $moduleId);
|
||||
$warning = '';
|
||||
if ($dependents !== []) {
|
||||
$dependentList = implode(', ', $dependents);
|
||||
$warning = "Warning: module(s) [{$dependentList}] declare '{$moduleId}' in their requires — deactivation may break them.\n";
|
||||
}
|
||||
|
||||
$handlerClass = $manifest->deactivationHandler;
|
||||
if ($handlerClass === null) {
|
||||
$message = "Module '{$moduleId}' has no deactivation_handler declared — nothing to do.";
|
||||
$message = $warning . "Module '{$moduleId}' has no deactivation_handler declared — nothing to do.";
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!class_exists($handlerClass)) {
|
||||
$message = "Error: deactivation handler class '{$handlerClass}' not found.";
|
||||
$message = $warning . "Error: deactivation handler class '{$handlerClass}' not found.";
|
||||
return 1;
|
||||
}
|
||||
|
||||
$container = $GLOBALS['minty_app_container'] ?? null;
|
||||
if (!$container instanceof AppContainer) {
|
||||
$message = 'Error: app container is not initialized.';
|
||||
$message = $warning . 'Error: app container is not initialized.';
|
||||
return 1;
|
||||
}
|
||||
$handler = $container->has($handlerClass)
|
||||
@@ -253,17 +259,17 @@ final class ModuleRunner implements ModuleRunnerInterface
|
||||
: new $handlerClass();
|
||||
|
||||
if (!$handler instanceof ModuleDeactivationHandler) {
|
||||
$message = "Error: '{$handlerClass}' does not implement ModuleDeactivationHandler.";
|
||||
$message = $warning . "Error: '{$handlerClass}' does not implement ModuleDeactivationHandler.";
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($dryRun) {
|
||||
$message = "Dry-run: handler '{$handlerClass}' found and valid for module '{$moduleId}'.";
|
||||
$message = $warning . "Dry-run: handler '{$handlerClass}' found and valid for module '{$moduleId}'.";
|
||||
return 0;
|
||||
}
|
||||
|
||||
$handler->deactivate($container);
|
||||
$message = "Deactivation handler for module '{$moduleId}' completed.";
|
||||
$message = $warning . "Deactivation handler for module '{$moduleId}' completed.";
|
||||
|
||||
return 0;
|
||||
});
|
||||
@@ -345,4 +351,34 @@ final class ModuleRunner implements ModuleRunnerInterface
|
||||
'message' => $step['error'] ?? $message,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string> Module IDs that declare $targetId in their requires
|
||||
*/
|
||||
private function findReverseDependencies(string $modulesDir, string $targetId): array
|
||||
{
|
||||
$dependents = [];
|
||||
$entries = is_dir($modulesDir) ? (scandir($modulesDir) ?: []) : [];
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if ($entry === '.' || $entry === '..' || $entry === $targetId) {
|
||||
continue;
|
||||
}
|
||||
$manifestFile = $modulesDir . '/' . $entry . '/module.php';
|
||||
if (!is_file($manifestFile)) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
$manifest = ModuleManifestLoader::loadFromDisk($modulesDir, $entry);
|
||||
if (in_array($targetId, $manifest->requires, true)) {
|
||||
$dependents[] = $entry;
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// Skip unparseable manifests
|
||||
}
|
||||
}
|
||||
|
||||
sort($dependents);
|
||||
return $dependents;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user