feat: extend module platform with UI slots, runtime components, CLI tooling and {{userId}} search support
Completes the generic module platform that enables modules to contribute
UI elements, runtime JS components, and search resources without any
core hardcoding.
New generic UI slot types:
- topbar.right_item: module-contributed topbar buttons
- layout.body_end_template: module-contributed dialog/overlay templates
- layout.head_style: module-contributed global CSS
- runtime.component: declarative JS component registration with phase ordering
New infrastructure:
- ModuleAutoloader: PSR-4 autoloading for module-local PHP classes
- ModuleRuntimePageBuilder: symlinks module pages into runtime directory
- ModuleRuntimeAssetPublisher: publishes module CSS/JS to web/modules/
- ModulePermissionSynchronizer: syncs module permissions to DB
- CLI scripts: module-runtime-sync, module-build, module-migrate,
module-permissions-sync, module-assets-sync
- {{userId}} placeholder in SearchDataService for user-scoped search queries
- Component runtime with phased initialization (early/default/late)
- AppContainer.protectExistingBindings() to prevent module→core overwrites
- Architecture tests: ModuleStructureContractTest, CoreTemplateIsolationTest,
FrontendComponentRuntimeContractTest, AppContainerIsolationContractTest
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
116
bin/module-cli-bootstrap.php
Normal file
116
bin/module-cli-bootstrap.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Shared CLI bootstrap and error policy for module runtime scripts.
|
||||
*
|
||||
* Policy:
|
||||
* - first-party warnings/notices/deprecations => 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;
|
||||
}
|
||||
Reference in New Issue
Block a user