1
0
Files
breadcrumb-the-shire/core/Console/Support/ModuleCliRuntime.php
fs 0e86925464 refactor: rename lib/ to core/ for clearer core-module separation
Rename the top-level lib/ directory to core/ so the project root
immediately communicates which code is core platform and which lives
in modules/. PHP namespaces (MintyPHP\*) are unchanged — only the
Composer PSR-4 path mapping moves from lib/ to core/.

Module-internal lib/ directories (modules/*/lib/) are untouched.

Updated across the full stack:
- composer.json PSR-4 mapping
- bootstrap entry points (web/index.php, bin/*, tests/bootstrap.php)
- tooling configs (phpstan.neon, phpunit.xml, php-cs-fixer)
- 26 architecture contract tests
- enforcement-policy, guard-catalog, quality-gates
- all documentation (CLAUDE.md, README, 25 docs/, .agents/skills/)
- bin/qa-extended.sh search paths
- .claude/settings.local.json permission paths

Workflow: .agents/runs/CORE-LIB-RENAME-001/ (Analyst → Planner →
Executor → Code Review (4 findings fixed) → Security Review →
Acceptance Test → Finalizer)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 23:20:42 +02:00

174 lines
4.7 KiB
PHP

<?php
declare(strict_types=1);
namespace MintyPHP\Console\Support;
use RuntimeException;
use Throwable;
final class ModuleCliRuntime
{
private static bool $booted = false;
private static bool $errorPolicyInstalled = false;
private static int $vendorWarningsIgnored = 0;
private function __construct()
{
}
public static function bootstrap(): void
{
if (self::$booted) {
return;
}
self::installErrorPolicy();
$projectRoot = CliAppBootstrap::projectRoot();
chdir($projectRoot);
require_once $projectRoot . '/vendor/autoload.php';
require_once $projectRoot . '/config/config.php';
require_once $projectRoot . '/lib/Support/helpers.php';
$container = require $projectRoot . '/lib/App/registerContainer.php';
setAppContainer($container);
self::$booted = true;
}
public static function projectRoot(): string
{
return CliAppBootstrap::projectRoot();
}
public static function vendorWarningsIgnoredTotal(): int
{
return self::$vendorWarningsIgnored;
}
/**
* @param callable():int $runner
* @return array{step: string, status: string, exit_code: int, vendor_warnings_ignored: int, error: string|null}
*/
public static function runStep(string $stepName, callable $runner): array
{
$warningsBefore = self::vendorWarningsIgnoredTotal();
self::bootstrap();
$exitCode = 0;
$error = null;
try {
$exitCode = $runner();
} catch (Throwable $throwable) {
$exitCode = 1;
$error = $throwable->getMessage();
}
return [
'step' => $stepName,
'status' => $exitCode === 0 ? 'ok' : 'failed',
'exit_code' => $exitCode,
'vendor_warnings_ignored' => max(0, self::vendorWarningsIgnoredTotal() - $warningsBefore),
'error' => $error,
];
}
/**
* @param callable():int $runner
* @return array{busy: bool, message: string|null, exit_code: int}
*/
public static function withFileLock(string $lockFile, string $busyMessage, callable $runner): array
{
$lockDir = dirname($lockFile);
if (!is_dir($lockDir) && !mkdir($lockDir, 0775, true) && !is_dir($lockDir)) {
throw new RuntimeException("Failed to create lock directory: {$lockDir}");
}
$lock = fopen($lockFile, 'c');
if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
if (is_resource($lock)) {
fclose($lock);
}
return [
'busy' => true,
'message' => $busyMessage,
'exit_code' => 0,
];
}
try {
return [
'busy' => false,
'message' => null,
'exit_code' => $runner(),
];
} finally {
flock($lock, LOCK_UN);
fclose($lock);
}
}
private static function installErrorPolicy(): void
{
if (self::$errorPolicyInstalled) {
return;
}
self::$errorPolicyInstalled = true;
error_reporting(E_ALL);
ini_set('display_errors', '0');
set_error_handler(static function (
int $severity,
string $message,
string $file = '',
int $line = 0
): bool {
$handledSeverities = [
E_WARNING,
E_NOTICE,
E_USER_WARNING,
E_USER_NOTICE,
E_DEPRECATED,
E_USER_DEPRECATED,
];
if (!in_array($severity, $handledSeverities, true)) {
return false;
}
if ((error_reporting() & $severity) === 0) {
return true;
}
$normalizedFile = str_replace('\\', '/', $file);
if (str_contains($normalizedFile, '/vendor/')) {
self::$vendorWarningsIgnored++;
return true;
}
throw new RuntimeException(sprintf(
'First-party runtime warning (%s) in %s:%d: %s',
self::severityName($severity),
$file,
$line,
$message
));
});
}
private static function severityName(int $severity): string
{
return match ($severity) {
E_WARNING => 'E_WARNING',
E_NOTICE => 'E_NOTICE',
E_USER_WARNING => 'E_USER_WARNING',
E_USER_NOTICE => 'E_USER_NOTICE',
E_DEPRECATED => 'E_DEPRECATED',
E_USER_DEPRECATED => 'E_USER_DEPRECATED',
default => 'E_' . $severity,
};
}
}