Files
breadcrumb-the-shire/bin/module-deactivate.php
fs ef72b34c40 feat: module architecture improvements — session keys, dependency graph, event dispatcher, deactivation hooks
Six targeted improvements to the modular monolith platform:

1. Fix AddressBook session key prefix to follow module.<id>.* convention
2. Move ADDRESS_BOOK_VIEW permission constant from core PermissionService into module
3. Add declarative JSON schema for module manifests (.agents/contracts/)
4. Add `requires` field with missing-dependency and circular-dependency detection
5. Add lightweight fire-and-forget event dispatcher (user.created/deleted/login/logout)
6. Add module deactivation hook interface and CLI script (bin/module-deactivate.php)

Includes 15 new architecture/unit tests covering all new functionality.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 18:20:13 +01:00

85 lines
2.5 KiB
PHP

<?php
declare(strict_types=1);
/**
* CLI script to run a module's deactivation handler.
*
* Usage:
* php bin/module-deactivate.php <module-id> --confirm
* php bin/module-deactivate.php <module-id> --dry-run
*
* The module does NOT need to be in the enabled list — the manifest is loaded
* directly from modules/<id>/module.php.
*/
require_once __DIR__ . '/module-cli-bootstrap.php';
$exitCode = moduleCliRunStep('module-deactivate', static function (): int {
global $argv;
$moduleId = $argv[1] ?? '';
$flags = array_slice($argv, 2);
if ($moduleId === '' || $moduleId === '--help') {
fwrite(STDOUT, "Usage: php bin/module-deactivate.php <module-id> --confirm|--dry-run\n");
return $moduleId === '--help' ? 0 : 1;
}
$confirm = in_array('--confirm', $flags, true);
$dryRun = in_array('--dry-run', $flags, true);
if (!$confirm && !$dryRun) {
fwrite(STDERR, "Error: must pass --confirm to execute or --dry-run to validate.\n");
return 1;
}
$projectRoot = moduleCliProjectRoot();
$manifestFile = $projectRoot . '/modules/' . $moduleId . '/module.php';
if (!is_file($manifestFile)) {
fwrite(STDERR, "Error: module manifest not found at {$manifestFile}\n");
return 1;
}
$raw = require $manifestFile;
if (!is_array($raw)) {
fwrite(STDERR, "Error: manifest must return an array.\n");
return 1;
}
$manifest = \MintyPHP\App\Module\ModuleManifest::fromArray($raw, dirname($manifestFile));
$handlerClass = $manifest->deactivationHandler;
if ($handlerClass === null) {
fwrite(STDOUT, "Module '{$moduleId}' has no deactivation_handler declared — nothing to do.\n");
return 0;
}
if (!class_exists($handlerClass)) {
fwrite(STDERR, "Error: deactivation handler class '{$handlerClass}' not found.\n");
return 1;
}
$handler = new $handlerClass();
if (!$handler instanceof \MintyPHP\App\Module\Contracts\ModuleDeactivationHandler) {
fwrite(STDERR, "Error: '{$handlerClass}' does not implement ModuleDeactivationHandler.\n");
return 1;
}
if ($dryRun) {
fwrite(STDOUT, "Dry-run: handler '{$handlerClass}' found and valid for module '{$moduleId}'.\n");
return 0;
}
fwrite(STDOUT, "Running deactivation handler for module '{$moduleId}'...\n");
$container = app();
$handler->deactivate($container);
fwrite(STDOUT, "Deactivation handler for module '{$moduleId}' completed.\n");
return 0;
});
exit($exitCode);