updaetd deployer

This commit is contained in:
Mark Gregory
2026-08-25 14:05:49 +01:00
parent 6a16aaca3a
commit 3e6b3bfc79
3 changed files with 107 additions and 22 deletions
@@ -5,7 +5,6 @@ declare(strict_types=1);
namespace Modules\Helpdesk\Providers;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
@@ -136,14 +135,19 @@ final class HelpdeskServiceProvider extends ServiceProvider
*/
protected function registerViews(): void
{
$viewPath = resource_path('views/modules/'.ModuleInfo::nameLower());
$sourcePath = module_path(ModuleInfo::name(), 'Resources/views');
$themeRoot = rtrim((string) config('stylist.themes_path', resource_path('themes')), DIRECTORY_SEPARATOR);
$backendTheme = (string) config('stylist.default_backend_theme', 'backend');
$viewPath = $themeRoot.DIRECTORY_SEPARATOR.$backendTheme
.DIRECTORY_SEPARATOR.'views'.DIRECTORY_SEPARATOR.'modules'.DIRECTORY_SEPARATOR.ModuleInfo::nameLower();
// Module source views remain module-owned. Any application override is
// published into the selected backend theme, never resources/views.
$this->publishes([
$sourcePath => $viewPath,
], ['views', ModuleInfo::nameLower().'-module-views']);
$this->loadViewsFrom(array_merge($this->getPublishableViewPaths(), [$sourcePath]), ModuleInfo::nameLower());
$this->loadViewsFrom($sourcePath, ModuleInfo::nameLower());
Blade::componentNamespace('Modules\Helpdesk\View\Components', 'helpdesk');
Blade::anonymousComponentPath($sourcePath.'/components', 'helpdesk');
@@ -165,24 +169,6 @@ final class HelpdeskServiceProvider extends ServiceProvider
$this->loadTranslationsFrom(module_path(ModuleInfo::name(), 'Resources/lang'), ModuleInfo::nameLower());
}
/**
* Get publishable view override paths.
*
* @return array<int, string>
*/
private function getPublishableViewPaths(): array
{
$paths = [];
foreach (Config::get('view.paths') as $path) {
if (is_dir($path.'/modules/'.ModuleInfo::nameLower())) {
$paths[] = $path.'/modules/'.ModuleInfo::nameLower();
}
}
return $paths;
}
/**
* Get provided services.
*
@@ -73,6 +73,7 @@ final class StylistServiceProvider extends ServiceProvider
$this->app->booted(function (): void {
$this->registerSelectedThemeOverrides();
$this->registerSelectedThemeFluxOverrides();
});
}
@@ -218,7 +219,14 @@ final class StylistServiceProvider extends ServiceProvider
// boot order and incremental prependLocation() calls. Theme paths stay first;
// Laravel's normal view locations remain as the final fallback.
if (method_exists($finder, 'getPaths') && method_exists($finder, 'setPaths')) {
$existingPaths = $finder->getPaths();
// The application intentionally has no resources/views tree. Laravel's
// default FileViewFinder still starts with that location, and view:cache
// asks Symfony Finder to scan every registered path. Remove missing legacy
// locations so theme-only applications can be optimized on a clean release.
$existingPaths = array_values(array_filter(
$finder->getPaths(),
static fn (string $path): bool => is_dir($path),
));
$paths = array_values(array_unique(array_merge($themePaths, $existingPaths)));
$finder->setPaths($paths);
@@ -236,6 +244,78 @@ final class StylistServiceProvider extends ServiceProvider
}
}
/**
* Make Flux application overrides theme-owned.
*
* Flux normally discovers custom components from resources/views/flux. This
* application deliberately has no resources/views tree, so expose the selected
* theme's views/flux directory through Flux's existing view namespace instead.
* Package-provided Flux views remain available as the final fallback.
*/
private function registerSelectedThemeFluxOverrides(): void
{
$stylist = $this->app->make(Stylist::class);
$orderedThemes = [$stylist->current(), $stylist->frontend(), $stylist->backend()];
$keys = [];
foreach ($orderedThemes as $theme) {
foreach ($this->themeChain($theme) as $key) {
if (! in_array($key, $keys, true)) {
$keys[] = $key;
}
}
}
if (! in_array('common', $keys, true)) {
$keys[] = 'common';
}
$themeFluxPaths = [];
foreach ($keys as $key) {
try {
$theme = $this->app->make(Loader::class)->find($key);
} catch (\Throwable) {
continue;
}
$fluxPath = $theme->path().DIRECTORY_SEPARATOR.'views'.DIRECTORY_SEPARATOR.'flux';
if (is_dir($fluxPath)) {
$themeFluxPaths[] = $fluxPath;
}
}
if ($themeFluxPaths === []) {
return;
}
$finder = View::getFinder();
if (! method_exists($finder, 'getHints')) {
return;
}
$hints = $finder->getHints();
$existingFluxPaths = array_values(array_filter(
(array) ($hints['flux'] ?? []),
static fn (string $path): bool => is_dir($path),
));
$paths = array_values(array_unique(array_merge($themeFluxPaths, $existingFluxPaths)));
if (method_exists($finder, 'replaceNamespace')) {
$finder->replaceNamespace('flux', $paths);
} else {
foreach (array_reverse($themeFluxPaths) as $fluxPath) {
View::prependNamespace('flux', $fluxPath);
}
}
if (method_exists($finder, 'flush')) {
$finder->flush();
}
}
/** @return array<int, string> */
private function themeChain(Theme $theme): array
{
@@ -42,4 +42,23 @@ final class WelcomeThemeViewTest extends TestCase
$this->assertFileExists(resource_path('themes/backend/assets/css/app.css'));
$this->assertFileExists(resource_path('themes/backend/assets/js/passkeys.js'));
}
public function test_view_finder_contains_only_existing_application_view_locations(): void
{
foreach (view()->getFinder()->getPaths() as $path) {
$this->assertDirectoryExists($path);
}
}
public function test_backend_flux_icon_overrides_are_owned_by_the_backend_theme(): void
{
$this->assertTrue(view()->exists('flux::icon.folder-git-2'));
$this->assertTrue(view()->exists('flux::icon.book-open-text'));
$this->assertTrue(view()->exists('flux::icon.chevrons-up-down'));
$this->assertStringContainsString(
resource_path('themes/backend/views/flux/icon/folder-git-2.blade.php'),
view('flux::icon.folder-git-2')->getPath(),
);
}
}