177 lines
5.2 KiB
PHP
177 lines
5.2 KiB
PHP
|
|
#!/usr/bin/env php
|
||
|
|
<?php
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Builds the runtime page root by symlinking module pages into storage/runtime/pages/.
|
||
|
|
*
|
||
|
|
* Usage: php bin/module-build.php
|
||
|
|
*
|
||
|
|
* The runtime page root mirrors the core pages/ directory and adds symlinks for
|
||
|
|
* each enabled module's pages. MintyPHP Router::$pageRoot points to this directory.
|
||
|
|
*
|
||
|
|
* This script is idempotent and uses a lock file to prevent concurrent builds.
|
||
|
|
*
|
||
|
|
* Exit codes:
|
||
|
|
* 0 — build successful (or nothing to do)
|
||
|
|
* 1 — error during build
|
||
|
|
*/
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
chdir(__DIR__ . '/..');
|
||
|
|
|
||
|
|
require 'vendor/autoload.php';
|
||
|
|
require 'config/config.php';
|
||
|
|
require 'lib/Support/helpers.php';
|
||
|
|
$container = require 'lib/App/registerContainer.php';
|
||
|
|
setAppContainer($container);
|
||
|
|
|
||
|
|
use MintyPHP\App\Module\ModuleRegistry;
|
||
|
|
|
||
|
|
$exitCode = 0;
|
||
|
|
$runtimeDir = __DIR__ . '/../storage/runtime/pages';
|
||
|
|
$lockFile = __DIR__ . '/../storage/runtime/.module-build.lock';
|
||
|
|
|
||
|
|
try {
|
||
|
|
// Ensure runtime directory exists
|
||
|
|
$runtimeParent = dirname($runtimeDir);
|
||
|
|
if (!is_dir($runtimeParent)) {
|
||
|
|
mkdir($runtimeParent, 0775, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Lock to prevent concurrent builds
|
||
|
|
$lock = fopen($lockFile, 'c');
|
||
|
|
if ($lock === false || !flock($lock, LOCK_EX | LOCK_NB)) {
|
||
|
|
fwrite(STDERR, "module-build: another build is in progress, skipping.\n");
|
||
|
|
exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
$registry = app(ModuleRegistry::class);
|
||
|
|
$modules = $registry->getModules();
|
||
|
|
|
||
|
|
$corePages = realpath(__DIR__ . '/../pages');
|
||
|
|
if ($corePages === false) {
|
||
|
|
throw new RuntimeException('Core pages/ directory not found.');
|
||
|
|
}
|
||
|
|
|
||
|
|
// If no modules, ensure runtime dir points to core pages
|
||
|
|
if (count($modules) === 0) {
|
||
|
|
// Remove existing runtime dir if it's a real directory (not a symlink)
|
||
|
|
if (is_dir($runtimeDir) && !is_link($runtimeDir)) {
|
||
|
|
removeDirectoryContents($runtimeDir);
|
||
|
|
rmdir($runtimeDir);
|
||
|
|
}
|
||
|
|
// Symlink runtime → core pages
|
||
|
|
if (is_link($runtimeDir)) {
|
||
|
|
unlink($runtimeDir);
|
||
|
|
}
|
||
|
|
symlink($corePages, $runtimeDir);
|
||
|
|
fwrite(STDOUT, "module-build: no modules, runtime → core pages (symlink).\n");
|
||
|
|
exit(0);
|
||
|
|
}
|
||
|
|
|
||
|
|
// With modules: build merged directory
|
||
|
|
if (is_link($runtimeDir)) {
|
||
|
|
unlink($runtimeDir);
|
||
|
|
}
|
||
|
|
if (!is_dir($runtimeDir)) {
|
||
|
|
mkdir($runtimeDir, 0775, true);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clean existing symlinks in runtime dir
|
||
|
|
removeDirectoryContents($runtimeDir);
|
||
|
|
|
||
|
|
// Symlink all core page directories/files
|
||
|
|
$coreEntries = scandir($corePages);
|
||
|
|
if ($coreEntries === false) {
|
||
|
|
throw new RuntimeException('Cannot scan core pages/ directory.');
|
||
|
|
}
|
||
|
|
foreach ($coreEntries as $entry) {
|
||
|
|
if ($entry === '.' || $entry === '..') {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
$source = $corePages . '/' . $entry;
|
||
|
|
$target = $runtimeDir . '/' . $entry;
|
||
|
|
symlink($source, $target);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add module page symlinks (fail-fast on collision)
|
||
|
|
$modulePageCount = 0;
|
||
|
|
foreach ($modules as $manifest) {
|
||
|
|
$modulePagesDir = $manifest->basePath . '/pages';
|
||
|
|
if (!is_dir($modulePagesDir)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
$entries = scandir($modulePagesDir);
|
||
|
|
if ($entries === false) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ($entries as $entry) {
|
||
|
|
if ($entry === '.' || $entry === '..') {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
$source = $modulePagesDir . '/' . $entry;
|
||
|
|
$target = $runtimeDir . '/' . $entry;
|
||
|
|
|
||
|
|
if (file_exists($target) || is_link($target)) {
|
||
|
|
throw new RuntimeException(
|
||
|
|
"Page path collision: '{$entry}' from module '{$manifest->id}' conflicts with existing page."
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
symlink($source, $target);
|
||
|
|
$modulePageCount++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
fwrite(STDOUT, sprintf(
|
||
|
|
"module-build: runtime pages built — %d core entries + %d module entries.\n",
|
||
|
|
count($coreEntries) - 2, // minus . and ..
|
||
|
|
$modulePageCount
|
||
|
|
));
|
||
|
|
} catch (Throwable $exception) {
|
||
|
|
fwrite(STDERR, sprintf("module-build: FAILED: %s\n", $exception->getMessage()));
|
||
|
|
$exitCode = 1;
|
||
|
|
} finally {
|
||
|
|
if (isset($lock) && is_resource($lock)) {
|
||
|
|
flock($lock, LOCK_UN);
|
||
|
|
fclose($lock);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
exit($exitCode);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Remove all files and symlinks in a directory (non-recursive, top-level only).
|
||
|
|
*/
|
||
|
|
function removeDirectoryContents(string $dir): void
|
||
|
|
{
|
||
|
|
$entries = scandir($dir);
|
||
|
|
if ($entries === false) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
foreach ($entries as $entry) {
|
||
|
|
if ($entry === '.' || $entry === '..') {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
$path = $dir . '/' . $entry;
|
||
|
|
if (is_link($path)) {
|
||
|
|
unlink($path);
|
||
|
|
} elseif (is_dir($path)) {
|
||
|
|
// Recursively remove directories that were created (shouldn't happen in normal flow)
|
||
|
|
$items = new RecursiveIteratorIterator(
|
||
|
|
new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS),
|
||
|
|
RecursiveIteratorIterator::CHILD_FIRST
|
||
|
|
);
|
||
|
|
foreach ($items as $item) {
|
||
|
|
$item->isDir() ? rmdir($item->getPathname()) : unlink($item->getPathname());
|
||
|
|
}
|
||
|
|
rmdir($path);
|
||
|
|
} else {
|
||
|
|
unlink($path);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|