*/ protected array $createdFiles = []; /** * Execute the console command. */ public function handle(): int { $name = Str::studly($this->argument('name')); $domain = $this->option('domain') ?: Str::snake($name, '-').'.test'; $websitePath = $this->getWebsitePath($name); if (File::isDirectory($websitePath) && ! $this->option('force')) { $this->newLine(); $this->components->error("Website [{$name}] already exists!"); $this->newLine(); $this->components->warn('Use --force to overwrite the existing website.'); $this->newLine(); return self::FAILURE; } $this->newLine(); $this->components->info("Creating website: {$name}"); $this->components->twoColumnDetail('Domain', "{$domain}>"); $this->newLine(); // Create directory structure $this->createDirectoryStructure($websitePath); // Create Boot.php $this->createBootFile($websitePath, $name, $domain); // Create optional route files $this->createOptionalFiles($websitePath, $name); // Show summary table of created files $this->newLine(); $this->components->twoColumnDetail('Created Files>', 'Description>'); foreach ($this->createdFiles as $file) { $this->components->twoColumnDetail( "{$file['file']}>", "{$file['description']}>" ); } $this->newLine(); $this->components->info("Website [{$name}] created successfully!"); $this->newLine(); $this->components->twoColumnDetail('Location', "{$websitePath}>"); $this->newLine(); $this->components->info('Next steps:'); $this->line(" 1.> Configure your local dev server to serve {$domain}>"); $this->line(' (e.g.,> valet link '.Str::snake($name, '-').')>'); $this->line(" 2.> Visit http://{$domain}> to see your website"); $this->line(' 3.> Add routes, views, and controllers as needed'); $this->newLine(); return self::SUCCESS; } /** * Get the path for the website. */ protected function getWebsitePath(string $name): string { // Websites go in app/Website for consuming applications return base_path("app/Website/{$name}"); } /** * Create the directory structure for the website. */ protected function createDirectoryStructure(string $websitePath): void { $directories = [ $websitePath, "{$websitePath}/View", "{$websitePath}/View/Blade", "{$websitePath}/View/Blade/layouts", ]; if ($this->hasRoutes()) { $directories[] = "{$websitePath}/Routes"; } foreach ($directories as $directory) { File::ensureDirectoryExists($directory); } $this->components->task('Creating directory structure', fn () => true); } /** * Check if any route handlers are requested. */ protected function hasRoutes(): bool { return $this->option('web') || $this->option('admin') || $this->option('api') || $this->option('all') || ! $this->hasAnyOption(); } /** * Check if any specific option was provided. */ protected function hasAnyOption(): bool { return $this->option('web') || $this->option('admin') || $this->option('api') || $this->option('all'); } /** * Create the Boot.php file. */ protected function createBootFile(string $websitePath, string $name, string $domain): void { $namespace = "Website\\{$name}"; $domainPattern = $this->buildDomainPattern($domain); $listeners = $this->buildListenersArray(); $handlers = $this->buildHandlerMethods($name); $content = <<buildUseStatements()} use Illuminate\Support\ServiceProvider; /** * {$name} Website - Domain-isolated website provider. * * This website is loaded when the incoming HTTP host matches * the domain pattern defined in \$domains. */ class Boot extends ServiceProvider { /** * Domain patterns this website responds to. * * Uses regex patterns. Common examples: * - '/^example\\.test\$/' - exact match * - '/^example\\.(com|test)\$/' - multiple TLDs * - '/^(www\\.)?example\\.com\$/' - optional www * * @var array */ public static array \$domains = [ '{$domainPattern}', ]; /** * Events this module listens to for lazy loading. * * @var array */ public static array \$listens = [ DomainResolving::class => 'onDomainResolving', {$listeners} ]; /** * Register any application services. */ public function register(): void { // } /** * Bootstrap any application services. */ public function boot(): void { // } /** * Handle domain resolution - register if domain matches. */ public function onDomainResolving(DomainResolving \$event): void { foreach (self::\$domains as \$pattern) { if (\$event->matches(\$pattern)) { \$event->register(self::class); return; } } } {$handlers} } PHP; File::put("{$websitePath}/Boot.php", $content); $this->createdFiles[] = ['file' => 'Boot.php', 'description' => 'Domain-isolated website provider']; $this->components->task('Creating Boot.php', fn () => true); } /** * Build the domain regex pattern. */ protected function buildDomainPattern(string $domain): string { // Escape dots and create a regex pattern $escaped = preg_quote($domain, '/'); return '/^'.$escaped.'$/'; } /** * Build the use statements for the Boot file. */ protected function buildUseStatements(): string { $statements = []; if ($this->option('web') || $this->option('all') || ! $this->hasAnyOption()) { $statements[] = 'use Core\Events\WebRoutesRegistering;'; } if ($this->option('admin') || $this->option('all')) { $statements[] = 'use Core\Events\AdminPanelBooting;'; } if ($this->option('api') || $this->option('all')) { $statements[] = 'use Core\Events\ApiRoutesRegistering;'; } return implode("\n", $statements); } /** * Build the listeners array content (excluding DomainResolving). */ protected function buildListenersArray(): string { $listeners = []; if ($this->option('web') || $this->option('all') || ! $this->hasAnyOption()) { $listeners[] = " WebRoutesRegistering::class => 'onWebRoutes',"; } if ($this->option('admin') || $this->option('all')) { $listeners[] = " AdminPanelBooting::class => 'onAdminPanel',"; } if ($this->option('api') || $this->option('all')) { $listeners[] = " ApiRoutesRegistering::class => 'onApiRoutes',"; } return implode("\n", $listeners); } /** * Build the handler methods. */ protected function buildHandlerMethods(string $name): string { $methods = []; $websiteName = Str::snake($name); if ($this->option('web') || $this->option('all') || ! $this->hasAnyOption()) { $methods[] = <<views('{$websiteName}', __DIR__.'/View/Blade'); if (file_exists(__DIR__.'/Routes/web.php')) { \$event->routes(fn () => require __DIR__.'/Routes/web.php'); } } PHP; } if ($this->option('admin') || $this->option('all')) { $methods[] = <<<'PHP' /** * Register admin panel routes. */ public function onAdminPanel(AdminPanelBooting $event): void { if (file_exists(__DIR__.'/Routes/admin.php')) { $event->routes(fn () => require __DIR__.'/Routes/admin.php'); } } PHP; } if ($this->option('api') || $this->option('all')) { $methods[] = <<<'PHP' /** * Register API routes. */ public function onApiRoutes(ApiRoutesRegistering $event): void { if (file_exists(__DIR__.'/Routes/api.php')) { $event->routes(fn () => require __DIR__.'/Routes/api.php'); } } PHP; } return implode("\n", $methods); } /** * Create optional files based on flags. */ protected function createOptionalFiles(string $websitePath, string $name): void { $websiteName = Str::snake($name); if ($this->option('web') || $this->option('all') || ! $this->hasAnyOption()) { $this->createWebRoutes($websitePath, $websiteName); $this->createLayout($websitePath, $name); $this->createHomepage($websitePath, $websiteName); } if ($this->option('admin') || $this->option('all')) { $this->createAdminRoutes($websitePath, $websiteName); } if ($this->option('api') || $this->option('all')) { $this->createApiRoutes($websitePath, $websiteName); } } /** * Create web routes file. */ protected function createWebRoutes(string $websitePath, string $websiteName): void { $content = <<name('{$websiteName}.home'); PHP; File::put("{$websitePath}/Routes/web.php", $content); $this->createdFiles[] = ['file' => 'Routes/web.php', 'description' => 'Public web routes']; $this->components->task('Creating Routes/web.php', fn () => true); } /** * Create admin routes file. */ protected function createAdminRoutes(string $websitePath, string $websiteName): void { $content = <<name('{$websiteName}.admin.')->group(function () { Route::get('/', function () { return 'Admin dashboard for {$websiteName}'; })->name('index'); }); PHP; File::put("{$websitePath}/Routes/admin.php", $content); $this->createdFiles[] = ['file' => 'Routes/admin.php', 'description' => 'Admin panel routes']; $this->components->task('Creating Routes/admin.php', fn () => true); } /** * Create API routes file. */ protected function createApiRoutes(string $websitePath, string $websiteName): void { $content = <<name('api.{$websiteName}.')->group(function () { Route::get('/health', function () { return response()->json(['status' => 'ok', 'website' => '{$websiteName}']); })->name('health'); }); PHP; File::put("{$websitePath}/Routes/api.php", $content); $this->createdFiles[] = ['file' => 'Routes/api.php', 'description' => 'REST API routes']; $this->components->task('Creating Routes/api.php', fn () => true); } /** * Create a base layout file. */ protected function createLayout(string $websitePath, string $name): void { $content = << {{ \$title ?? '{$name}' }} @vite(['resources/css/app.css', 'resources/js/app.js']) {$name} {{ \$slot }} BLADE; File::put("{$websitePath}/View/Blade/layouts/app.blade.php", $content); $this->createdFiles[] = ['file' => 'View/Blade/layouts/app.blade.php', 'description' => 'Base layout template']; $this->components->task('Creating View/Blade/layouts/app.blade.php', fn () => true); } /** * Create a homepage view. */ protected function createHomepage(string $websitePath, string $websiteName): void { $name = Str::studly($websiteName); $content = << Welcome - {$name} Welcome to {$name} This is your new website. Start building something amazing! BLADE; File::put("{$websitePath}/View/Blade/home.blade.php", $content); $this->createdFiles[] = ['file' => 'View/Blade/home.blade.php', 'description' => 'Homepage view']; $this->components->task('Creating View/Blade/home.blade.php', fn () => true); } /** * Get shell completion suggestions for arguments and options. */ public function complete( CompletionInput $input, CompletionSuggestions $suggestions ): void { if ($input->mustSuggestArgumentValuesFor('name')) { // Suggest common website naming patterns $suggestions->suggestValues([ 'MarketingSite', 'Blog', 'Documentation', 'LandingPage', 'Portal', 'Dashboard', 'Support', ]); } if ($input->mustSuggestOptionValuesFor('domain')) { // Suggest common development domains $suggestions->suggestValues([ 'example.test', 'app.test', 'site.test', 'dev.test', ]); } } }
This is your new website. Start building something amazing!