#!/usr/bin/env php 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); } } }