|
<?php |
|
|
|
namespace App\Console\Commands; |
|
|
|
use Illuminate\Console\Command; |
|
use Illuminate\Support\Facades\Route; |
|
use Illuminate\Support\Facades\Request; |
|
use Illuminate\Support\Facades\File; |
|
use Symfony\Component\DomCrawler\Crawler; |
|
|
|
class ExportToAstro extends Command |
|
{ |
|
/** |
|
* The name and signature of the console command. |
|
* |
|
* @var string |
|
*/ |
|
protected $signature = 'export:astro {--output=static : The target directory for Astro files}'; |
|
|
|
/** |
|
* The console command description. |
|
* |
|
* @var string |
|
*/ |
|
protected $description = 'Renders Laravel /pages routes and exports them as clean Astro components'; |
|
|
|
/** |
|
* Execute the console command. |
|
*/ |
|
public function handle() |
|
{ |
|
$outputDir = base_path($this->option('output')); |
|
|
|
// 1. Find all routes starting with /pages |
|
$routes = collect(Route::getRoutes()->getRoutes()) |
|
->filter(function ($route) { |
|
return in_array('GET', $route->methods()) && str_starts_with($route->uri(), 'pages'); |
|
}); |
|
|
|
if ($routes->isEmpty()) { |
|
$this->error('No routes found starting with "/pages".'); |
|
return Command::FAILURE; |
|
} |
|
|
|
$this->info("Found {$routes->count()} routes to export..."); |
|
|
|
// Ensure output directory exists |
|
if (!File::exists($outputDir)) { |
|
File::makeDirectory($outputDir, 0755, true); |
|
} |
|
|
|
foreach ($routes as $route) { |
|
$uri = $route->uri(); |
|
$this->comment("Processing: /$uri"); |
|
|
|
try { |
|
// 2. Render the full Blade page internally |
|
$html = $this->renderRoute($uri); |
|
|
|
// 3. Parse the HTML using Symfony DomCrawler |
|
$crawler = new Crawler($html); |
|
|
|
// Extract metadata (fallback to defaults if not found) |
|
$title = $crawler->filter('title')->count() ? $crawler->filter('title')->text() : 'Default Title'; |
|
$description = $crawler->filter('meta[name="description"]')->count() |
|
? $crawler->filter('meta[name="description"]')->attr('content') |
|
: 'Default description'; |
|
|
|
// 4. Extract Main Content |
|
// Adjust selectors based on your blade layouts (e.g., 'main', '#content', 'body') |
|
$contentHtml = ''; |
|
if ($crawler->filter('main')->count() > 0) { |
|
$contentHtml = $crawler->filter('main')->html(); |
|
} elseif ($crawler->filter('body')->count() > 0) { |
|
$contentHtml = $crawler->filter('body')->html(); |
|
} else { |
|
$contentHtml = $html; // Fallback to full HTML if structural tags are missing |
|
} |
|
|
|
// Clean up trailing whitespace/indentation cleanly |
|
$contentHtml = trim($contentHtml); |
|
|
|
// 5. Generate Astro File Content |
|
$astroContent = $this->buildAstroTemplate($title, $description, $contentHtml); |
|
|
|
// 6. Determine target filename and save |
|
$slug = str_replace('pages/', '', $uri); |
|
$slug = $slug === 'pages' || $slug === '' ? 'index' : $slug; |
|
|
|
$filePath = $outputDir . '/' . $slug . '.astro'; |
|
|
|
// Ensure subdirectories exist if routes are nested (e.g., pages/about/team) |
|
File::ensureDirectoryExists(dirname($filePath)); |
|
File::put($filePath, $astroContent); |
|
|
|
$this->info("Successfully exported to: {$filePath}"); |
|
|
|
} catch (\Exception $e) { |
|
$this->error("Failed to export /$uri: " . $e->getMessage()); |
|
} |
|
} |
|
|
|
$this->info('Export complete!'); |
|
return Command::SUCCESS; |
|
} |
|
|
|
/** |
|
* Simulates an internal HTTP request to render the HTML. |
|
*/ |
|
protected function renderRoute(string $uri): string |
|
{ |
|
$request = Request::create($uri, 'GET'); |
|
$response = app()->handle($request); |
|
|
|
if ($response->getStatusCode() !== 200) { |
|
throw new \Exception("HTTP Status Code " . $response->getStatusCode()); |
|
} |
|
|
|
return $response->getContent(); |
|
} |
|
|
|
/** |
|
* Compiles the metadata and body HTML into Astro's file format. |
|
*/ |
|
protected function buildAstroTemplate(string $title, string $description, string $bodyContent): string |
|
{ |
|
// Escaping single quotes for Frontmatter string safety |
|
$title = addslashes($title); |
|
$description = addslashes($description); |
|
|
|
return <<<ASTRO |
|
--- |
|
import Layout from "../layouts/Layout.astro"; |
|
|
|
const title = "{$title}"; |
|
const description = "{$description}"; |
|
--- |
|
|
|
<Layout title={title} description={description}> |
|
{$bodyContent} |
|
</Layout> |
|
ASTRO; |
|
} |
|
} |