fail-fast (RuntimeException) * - vendor warnings/notices/deprecations => ignored and counted */ function moduleCliProjectRoot(): string { return dirname(__DIR__); } function moduleCliVendorWarningsIgnoredTotal(): int { $count = $GLOBALS['module_cli_vendor_warnings_ignored'] ?? 0; return is_int($count) ? $count : 0; } function moduleCliVendorWarningsIgnoredSince(int $before): int { return max(0, moduleCliVendorWarningsIgnoredTotal() - $before); } function moduleCliIncrementVendorWarningsIgnored(): void { $current = moduleCliVendorWarningsIgnoredTotal(); $GLOBALS['module_cli_vendor_warnings_ignored'] = $current + 1; } function moduleCliSeverityName(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, }; } function moduleCliInstallErrorPolicy(): void { static $installed = false; if ($installed) { return; } $installed = 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/')) { moduleCliIncrementVendorWarningsIgnored(); return true; } throw new RuntimeException(sprintf( "First-party runtime warning (%s) in %s:%d: %s", moduleCliSeverityName($severity), $file, $line, $message )); }); } function moduleCliBootstrapApp(): void { static $booted = false; if ($booted) { return; } moduleCliInstallErrorPolicy(); $projectRoot = moduleCliProjectRoot(); 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); $booted = true; } /** * Runs one module CLI step with shared bootstrap, failure handling and vendor warning summary. * * @param callable():int $runner */ function moduleCliRunStep(string $stepName, callable $runner): int { $warningsBefore = moduleCliVendorWarningsIgnoredTotal(); moduleCliBootstrapApp(); $exitCode = 0; try { $exitCode = $runner(); } catch (Throwable $exception) { fwrite(STDERR, sprintf("%s: FAILED: %s\n", $stepName, $exception->getMessage())); $exitCode = 1; } $vendorWarningsIgnored = moduleCliVendorWarningsIgnoredSince($warningsBefore); fwrite(STDOUT, sprintf("%s: vendor_warnings_ignored=%d\n", $stepName, $vendorWarningsIgnored)); return $exitCode; } /** * Executes a callable under a non-blocking file lock. * * @param callable():int $runner */ function moduleCliWithFileLock(string $lockFile, string $busyMessage, callable $runner): int { $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)) { fwrite(STDERR, $busyMessage . PHP_EOL); return 0; } try { return $runner(); } finally { flock($lock, LOCK_UN); fclose($lock); } }