From ba916ea47d4b49aa826867ee8a4b2ef3110ad34b Mon Sep 17 00:00:00 2001 From: Sam Maosa Date: Mon, 15 Apr 2024 13:58:28 +0300 Subject: [PATCH 1/2] New Feature: A command to generate a Theme inside a module - Generate a theme in a module --- .../ModuleMakeFilamentThemeCommand.php | 116 ++++++++++++++++++ src/Commands/stubs/filament-theme-css.stub | 3 + .../stubs/filament-theme-postcss.stub | 7 ++ .../stubs/filament-theme-tailwind-config.stub | 10 ++ src/Concerns/ModuleFilamentPlugin.php | 14 ++- src/ModulesServiceProvider.php | 17 +-- 6 files changed, 155 insertions(+), 12 deletions(-) create mode 100644 src/Commands/ModuleMakeFilamentThemeCommand.php create mode 100644 src/Commands/stubs/filament-theme-css.stub create mode 100644 src/Commands/stubs/filament-theme-postcss.stub create mode 100644 src/Commands/stubs/filament-theme-tailwind-config.stub diff --git a/src/Commands/ModuleMakeFilamentThemeCommand.php b/src/Commands/ModuleMakeFilamentThemeCommand.php new file mode 100644 index 00000000..b47c908b --- /dev/null +++ b/src/Commands/ModuleMakeFilamentThemeCommand.php @@ -0,0 +1,116 @@ +getModule(); + + $this->call('vendor:publish', [ + '--provider' => 'Nwidart\Modules\LaravelModulesServiceProvider', + '--tag' => 'vite', + ]); + + $pm = $this->option('pm') ?? 'npm'; + + exec("{$pm} -v", $pmVersion, $pmVersionExistCode); + + if ($pmVersionExistCode !== 0) { + $this->error('Node.js is not installed. Please install before continuing.'); + + return static::FAILURE; + } + + $this->info("Using {$pm} v{$pmVersion[0]}"); + + $installCommand = match ($pm) { + 'yarn' => 'yarn add', + default => "{$pm} install", + }; + $cdCommand = 'cd '.$module->getPath(); + + exec("$cdCommand && {$installCommand} tailwindcss @tailwindcss/forms @tailwindcss/typography postcss postcss-nesting autoprefixer --save-dev"); + + // $panel = $this->argument('panel'); + + $cssFilePath = $module->resourcesPath('css/filament/theme.css'); + $tailwindConfigFilePath = $module->resourcesPath('css/filament/tailwind.config.js'); + + if (! $this->option('force') && $this->checkForCollision([ + $cssFilePath, + $tailwindConfigFilePath, + ])) { + return static::INVALID; + } + + $classPathPrefix = ''; + + $viewPathPrefix = ''; + + $this->copyStubToApp('filament-theme-css', $cssFilePath); + $this->copyStubToApp('filament-theme-tailwind-config', $tailwindConfigFilePath, [ + 'classPathPrefix' => $classPathPrefix, + 'viewPathPrefix' => $viewPathPrefix, + ]); + + $this->components->info('Filament theme [resources/css/filament/theme.css] and [resources/css/filament/tailwind.config.js] created successfully in '.$module->getStudlyName().' module.'); + + $buildDirectory = 'build-'.$module->getLowerName(); + $moduleStudlyName = $module->getStudlyName(); + + if (empty(glob($module->getExtraPath('vite.config.*s')))) { + $this->components->warn('Action is required to complete the theme setup:'); + $this->components->bulletList([ + "It looks like you don't have Vite installed in your module. Please use your asset bundling system of choice to compile `resources/css/filament/theme.css` into `public/$buildDirectory/css/filament/theme.css`.", + "If you're not currently using a bundler, we recommend using Vite. Alternatively, you can use the Tailwind CLI with the following command inside the $moduleStudlyName module:", + 'npx tailwindcss --input ./resources/css/filament/theme.css --output ./public/'.$buildDirectory.'/css/filament/theme.css --config ./resources/css/filament/tailwind.config.js --minify', + "Make sure to register the theme in the {$moduleStudlyName} module plugin under the afterRegister() function using `->theme(asset('css/filament/theme.css'))`", + ]); + + return static::SUCCESS; + } + + $postcssConfigPath = $module->getExtraPath('postcss.config.js'); + + if (! file_exists($postcssConfigPath)) { + $this->copyStubToApp('filament-theme-postcss', $postcssConfigPath); + + $this->components->info('Filament theme [postcss.config.js] created successfully.'); + } + + $this->components->warn('Action is required to complete the theme setup:'); + $this->components->bulletList([ + "First, add a new item to the `input` array of `vite.config.js`: `resources/css/filament/theme.css` in the $moduleStudlyName module.", + "Next, register the theme in the {$module->getStudlyName()} module plugin under the `afterRegister()` method using `->viteTheme('resources/css/filament/theme.css', '$buildDirectory')`", + "Finally, run `{$pm} run build` from the root of this module to compile the theme.", + ]); + + return static::SUCCESS; + } + + protected function getDefaultStubPath(): string + { + return __DIR__.'/stubs'; + } + + private function getModule(): Module + { + $moduleName = $this->argument('module') ?? text('In which Module should we create this?', 'e.g Blog', required: true); + $moduleStudlyName = str($moduleName)->studly()->toString(); + + return FilamentModules::getModule($moduleStudlyName); + } +} diff --git a/src/Commands/stubs/filament-theme-css.stub b/src/Commands/stubs/filament-theme-css.stub new file mode 100644 index 00000000..62e01b86 --- /dev/null +++ b/src/Commands/stubs/filament-theme-css.stub @@ -0,0 +1,3 @@ +@import '../../../../../vendor/filament/filament/resources/css/theme.css'; + +@config 'tailwind.config.js'; \ No newline at end of file diff --git a/src/Commands/stubs/filament-theme-postcss.stub b/src/Commands/stubs/filament-theme-postcss.stub new file mode 100644 index 00000000..9fab0884 --- /dev/null +++ b/src/Commands/stubs/filament-theme-postcss.stub @@ -0,0 +1,7 @@ +export default { + plugins: { + 'tailwindcss/nesting': 'postcss-nesting', + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/src/Commands/stubs/filament-theme-tailwind-config.stub b/src/Commands/stubs/filament-theme-tailwind-config.stub new file mode 100644 index 00000000..e2d19832 --- /dev/null +++ b/src/Commands/stubs/filament-theme-tailwind-config.stub @@ -0,0 +1,10 @@ +import preset from '../../../../../vendor/filament/filament/tailwind.config.preset' + +export default { + presets: [preset], + content: [ + './app/Filament/**/*.php', + './resources/views/filament/**/*.blade.php', + '../../vendor/filament/**/*.blade.php', + ], +} diff --git a/src/Concerns/ModuleFilamentPlugin.php b/src/Concerns/ModuleFilamentPlugin.php index 792a8053..a18868fb 100644 --- a/src/Concerns/ModuleFilamentPlugin.php +++ b/src/Concerns/ModuleFilamentPlugin.php @@ -19,15 +19,15 @@ public function register(Panel $panel): void $module = $this->getModule(); $useClusters = config('filament-modules.clusters.enabled', false); $panel->discoverPages( - in: $module->appPath('Filament' . DIRECTORY_SEPARATOR . 'Pages'), + in: $module->appPath('Filament'.DIRECTORY_SEPARATOR.'Pages'), for: $module->appNamespace('\\Filament\\Pages') ); $panel->discoverResources( - in: $module->appPath('Filament' . DIRECTORY_SEPARATOR . 'Resources'), + in: $module->appPath('Filament'.DIRECTORY_SEPARATOR.'Resources'), for: $module->appNamespace('\\Filament\\Resources') ); $panel->discoverWidgets( - in: $module->appPath('Filament' . DIRECTORY_SEPARATOR . 'Widgets'), + in: $module->appPath('Filament'.DIRECTORY_SEPARATOR.'Widgets'), for: $module->appNamespace('\\Filament\\Widgets') ); @@ -37,13 +37,14 @@ public function register(Panel $panel): void ); if ($useClusters) { - $path = $module->appPath('Filament' . DIRECTORY_SEPARATOR . 'Clusters'); + $path = $module->appPath('Filament'.DIRECTORY_SEPARATOR.'Clusters'); $namespace = $module->appNamespace('\\Filament\\Clusters'); $panel->discoverClusters( in: $path, for: $namespace, ); } + $this->afterRegister($panel); } public static function make(): static @@ -58,4 +59,9 @@ public static function get(): static return $plugin; } + + public function afterRegister(Panel $panel) + { + // override this to implement additional logic + } } diff --git a/src/ModulesServiceProvider.php b/src/ModulesServiceProvider.php index 157a7878..0a1ddbdb 100644 --- a/src/ModulesServiceProvider.php +++ b/src/ModulesServiceProvider.php @@ -78,7 +78,7 @@ public function packageBooted(): void // Handle Stubs if (app()->runningInConsole()) { - foreach (app(Filesystem::class)->files(__DIR__ . '/../stubs/') as $file) { + foreach (app(Filesystem::class)->files(__DIR__.'/../stubs/') as $file) { $this->publishes([ $file->getRealPath() => base_path("stubs/modules/{$file->getFilename()}"), ], 'modules-stubs'); @@ -114,6 +114,7 @@ protected function getCommands(): array Commands\ModuleMakeFilamentResourceCommand::class, Commands\ModuleMakeFilamentPageCommand::class, Commands\ModuleMakeFilamentWidgetCommand::class, + Commands\ModuleMakeFilamentThemeCommand::class, ]; } @@ -170,44 +171,44 @@ protected function registerModuleMacros(): void $relativeNamespace = str_replace('App\\', '', $relativeNamespace); $relativeNamespace = str_replace('App', '', $relativeNamespace); $relativeNamespace = trim($relativeNamespace, '\\'); - $relativeNamespace = '\\' . $relativeNamespace; + $relativeNamespace = '\\'.$relativeNamespace; return $this->namespace($relativeNamespace); }); Module::macro('appPath', function (string $relativePath = '') { $appPath = $this->getExtraPath('app'); - return $appPath . ($relativePath ? DIRECTORY_SEPARATOR . $relativePath : ''); + return $appPath.($relativePath ? DIRECTORY_SEPARATOR.$relativePath : ''); }); Module::macro('databasePath', function (string $relativePath = '') { $appPath = $this->getExtraPath('database'); - return $appPath . ($relativePath ? DIRECTORY_SEPARATOR . $relativePath : ''); + return $appPath.($relativePath ? DIRECTORY_SEPARATOR.$relativePath : ''); }); Module::macro('resourcesPath', function (string $relativePath = '') { $appPath = $this->getExtraPath('resources'); - return $appPath . ($relativePath ? DIRECTORY_SEPARATOR . $relativePath : ''); + return $appPath.($relativePath ? DIRECTORY_SEPARATOR.$relativePath : ''); }); Module::macro('migrationsPath', function (string $relativePath = '') { $appPath = $this->databasePath('migrations'); - return $appPath . ($relativePath ? DIRECTORY_SEPARATOR . $relativePath : ''); + return $appPath.($relativePath ? DIRECTORY_SEPARATOR.$relativePath : ''); }); Module::macro('seedersPath', function (string $relativePath = '') { $appPath = $this->databasePath('seeders'); - return $appPath . ($relativePath ? DIRECTORY_SEPARATOR . $relativePath : ''); + return $appPath.($relativePath ? DIRECTORY_SEPARATOR.$relativePath : ''); }); Module::macro('factoriesPath', function (string $relativePath = '') { $appPath = $this->databasePath('factories'); - return $appPath . ($relativePath ? DIRECTORY_SEPARATOR . $relativePath : ''); + return $appPath.($relativePath ? DIRECTORY_SEPARATOR.$relativePath : ''); }); } } From 953f108010afd51deeca110ea515cf83d48e9459 Mon Sep 17 00:00:00 2001 From: coolsam726 Date: Mon, 15 Apr 2024 10:59:28 +0000 Subject: [PATCH 2/2] Fix styling --- src/Commands/ModuleMakeFilamentThemeCommand.php | 10 +++++----- src/Concerns/ModuleFilamentPlugin.php | 8 ++++---- src/ModulesServiceProvider.php | 16 ++++++++-------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Commands/ModuleMakeFilamentThemeCommand.php b/src/Commands/ModuleMakeFilamentThemeCommand.php index b47c908b..c7f03e6b 100644 --- a/src/Commands/ModuleMakeFilamentThemeCommand.php +++ b/src/Commands/ModuleMakeFilamentThemeCommand.php @@ -40,7 +40,7 @@ public function handle(): int 'yarn' => 'yarn add', default => "{$pm} install", }; - $cdCommand = 'cd '.$module->getPath(); + $cdCommand = 'cd ' . $module->getPath(); exec("$cdCommand && {$installCommand} tailwindcss @tailwindcss/forms @tailwindcss/typography postcss postcss-nesting autoprefixer --save-dev"); @@ -66,9 +66,9 @@ public function handle(): int 'viewPathPrefix' => $viewPathPrefix, ]); - $this->components->info('Filament theme [resources/css/filament/theme.css] and [resources/css/filament/tailwind.config.js] created successfully in '.$module->getStudlyName().' module.'); + $this->components->info('Filament theme [resources/css/filament/theme.css] and [resources/css/filament/tailwind.config.js] created successfully in ' . $module->getStudlyName() . ' module.'); - $buildDirectory = 'build-'.$module->getLowerName(); + $buildDirectory = 'build-' . $module->getLowerName(); $moduleStudlyName = $module->getStudlyName(); if (empty(glob($module->getExtraPath('vite.config.*s')))) { @@ -76,7 +76,7 @@ public function handle(): int $this->components->bulletList([ "It looks like you don't have Vite installed in your module. Please use your asset bundling system of choice to compile `resources/css/filament/theme.css` into `public/$buildDirectory/css/filament/theme.css`.", "If you're not currently using a bundler, we recommend using Vite. Alternatively, you can use the Tailwind CLI with the following command inside the $moduleStudlyName module:", - 'npx tailwindcss --input ./resources/css/filament/theme.css --output ./public/'.$buildDirectory.'/css/filament/theme.css --config ./resources/css/filament/tailwind.config.js --minify', + 'npx tailwindcss --input ./resources/css/filament/theme.css --output ./public/' . $buildDirectory . '/css/filament/theme.css --config ./resources/css/filament/tailwind.config.js --minify', "Make sure to register the theme in the {$moduleStudlyName} module plugin under the afterRegister() function using `->theme(asset('css/filament/theme.css'))`", ]); @@ -103,7 +103,7 @@ public function handle(): int protected function getDefaultStubPath(): string { - return __DIR__.'/stubs'; + return __DIR__ . '/stubs'; } private function getModule(): Module diff --git a/src/Concerns/ModuleFilamentPlugin.php b/src/Concerns/ModuleFilamentPlugin.php index a18868fb..abb5214e 100644 --- a/src/Concerns/ModuleFilamentPlugin.php +++ b/src/Concerns/ModuleFilamentPlugin.php @@ -19,15 +19,15 @@ public function register(Panel $panel): void $module = $this->getModule(); $useClusters = config('filament-modules.clusters.enabled', false); $panel->discoverPages( - in: $module->appPath('Filament'.DIRECTORY_SEPARATOR.'Pages'), + in: $module->appPath('Filament' . DIRECTORY_SEPARATOR . 'Pages'), for: $module->appNamespace('\\Filament\\Pages') ); $panel->discoverResources( - in: $module->appPath('Filament'.DIRECTORY_SEPARATOR.'Resources'), + in: $module->appPath('Filament' . DIRECTORY_SEPARATOR . 'Resources'), for: $module->appNamespace('\\Filament\\Resources') ); $panel->discoverWidgets( - in: $module->appPath('Filament'.DIRECTORY_SEPARATOR.'Widgets'), + in: $module->appPath('Filament' . DIRECTORY_SEPARATOR . 'Widgets'), for: $module->appNamespace('\\Filament\\Widgets') ); @@ -37,7 +37,7 @@ public function register(Panel $panel): void ); if ($useClusters) { - $path = $module->appPath('Filament'.DIRECTORY_SEPARATOR.'Clusters'); + $path = $module->appPath('Filament' . DIRECTORY_SEPARATOR . 'Clusters'); $namespace = $module->appNamespace('\\Filament\\Clusters'); $panel->discoverClusters( in: $path, diff --git a/src/ModulesServiceProvider.php b/src/ModulesServiceProvider.php index 0a1ddbdb..0e1f0b33 100644 --- a/src/ModulesServiceProvider.php +++ b/src/ModulesServiceProvider.php @@ -78,7 +78,7 @@ public function packageBooted(): void // Handle Stubs if (app()->runningInConsole()) { - foreach (app(Filesystem::class)->files(__DIR__.'/../stubs/') as $file) { + foreach (app(Filesystem::class)->files(__DIR__ . '/../stubs/') as $file) { $this->publishes([ $file->getRealPath() => base_path("stubs/modules/{$file->getFilename()}"), ], 'modules-stubs'); @@ -171,44 +171,44 @@ protected function registerModuleMacros(): void $relativeNamespace = str_replace('App\\', '', $relativeNamespace); $relativeNamespace = str_replace('App', '', $relativeNamespace); $relativeNamespace = trim($relativeNamespace, '\\'); - $relativeNamespace = '\\'.$relativeNamespace; + $relativeNamespace = '\\' . $relativeNamespace; return $this->namespace($relativeNamespace); }); Module::macro('appPath', function (string $relativePath = '') { $appPath = $this->getExtraPath('app'); - return $appPath.($relativePath ? DIRECTORY_SEPARATOR.$relativePath : ''); + return $appPath . ($relativePath ? DIRECTORY_SEPARATOR . $relativePath : ''); }); Module::macro('databasePath', function (string $relativePath = '') { $appPath = $this->getExtraPath('database'); - return $appPath.($relativePath ? DIRECTORY_SEPARATOR.$relativePath : ''); + return $appPath . ($relativePath ? DIRECTORY_SEPARATOR . $relativePath : ''); }); Module::macro('resourcesPath', function (string $relativePath = '') { $appPath = $this->getExtraPath('resources'); - return $appPath.($relativePath ? DIRECTORY_SEPARATOR.$relativePath : ''); + return $appPath . ($relativePath ? DIRECTORY_SEPARATOR . $relativePath : ''); }); Module::macro('migrationsPath', function (string $relativePath = '') { $appPath = $this->databasePath('migrations'); - return $appPath.($relativePath ? DIRECTORY_SEPARATOR.$relativePath : ''); + return $appPath . ($relativePath ? DIRECTORY_SEPARATOR . $relativePath : ''); }); Module::macro('seedersPath', function (string $relativePath = '') { $appPath = $this->databasePath('seeders'); - return $appPath.($relativePath ? DIRECTORY_SEPARATOR.$relativePath : ''); + return $appPath . ($relativePath ? DIRECTORY_SEPARATOR . $relativePath : ''); }); Module::macro('factoriesPath', function (string $relativePath = '') { $appPath = $this->databasePath('factories'); - return $appPath.($relativePath ? DIRECTORY_SEPARATOR.$relativePath : ''); + return $appPath . ($relativePath ? DIRECTORY_SEPARATOR . $relativePath : ''); }); } }