Files
rspade_system/app/RSpade/Commands/Rsx/Prod_Build_Command.php
root e389093a0a Deduplicate CDN assets by URL in bundle compiler
Unify CDN asset collection between dev and prod modes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-14 23:25:10 +00:00

169 lines
5.9 KiB
PHP
Executable File

<?php
namespace App\RSpade\Commands\Rsx;
use App\RSpade\Core\Bundle\BundleCompiler;
use App\RSpade\Core\Bundle\Minifier;
use App\RSpade\Core\Manifest\Manifest;
use App\RSpade\Core\Rsx;
use Exception;
use Illuminate\Console\Command;
/**
* Build all production assets
*
* Rebuilds manifest and compiles all bundles for production/debug mode.
* Must be run before the application can serve pages in production-like modes.
*/
class Prod_Build_Command extends Command
{
protected $signature = 'rsx:prod:build
{--force : Force rebuild even if assets exist}
{--skip-laravel-cache : Skip Laravel config/route/view caching}';
protected $description = 'Build all production assets (manifest, bundles, caches)';
public function handle(): int
{
if (Rsx::is_development()) {
$this->warn('Warning: Building production assets in development mode.');
$this->line('Assets will be built but will not be used until you switch modes:');
$this->line(' php artisan rsx:mode:set debug');
$this->line(' php artisan rsx:mode:set production');
$this->newLine();
}
$this->info('Building production assets...');
$this->newLine();
$start_time = microtime(true);
// Step 1: Rebuild manifest
$this->line('[1/3] Building manifest...');
try {
// Enable force build mode to allow rebuilding in production-like modes
Manifest::$_force_build = true;
// Force a fresh manifest rebuild by clearing and re-initializing
Manifest::clear();
Manifest::init();
$this->line(' Manifest built successfully');
} catch (Exception $e) {
$this->error(' Failed to build manifest: ' . $e->getMessage());
return 1;
} finally {
Manifest::$_force_build = false;
}
// Step 2: Compile all bundles
$this->line('[2/3] Compiling bundles...');
// Force restart minify server to pick up any code changes
if (Rsx::is_production()) {
Minifier::force_restart();
}
$manifest_data = Manifest::get_all();
$bundle_classes = [];
foreach ($manifest_data as $file_info) {
$class_name = $file_info['class'] ?? null;
if ($class_name && Manifest::php_is_subclass_of($class_name, 'Rsx_Module_Bundle_Abstract')) {
$fqcn = $file_info['fqcn'] ?? $class_name;
$bundle_classes[$fqcn] = $class_name;
}
}
if (empty($bundle_classes)) {
$this->warn(' No bundles found in manifest');
} else {
// Ensure storage directory exists
$bundle_dir = storage_path('rsx-build/bundles');
if (!is_dir($bundle_dir)) {
mkdir($bundle_dir, 0755, true);
}
$compiled_count = 0;
$failed_bundles = [];
foreach ($bundle_classes as $fqcn => $class_name) {
$this->line(" Compiling: {$class_name}");
try {
$compiler = new BundleCompiler();
$compiled = $compiler->compile($fqcn, ['force_build' => true]);
// Get output file info (always vendor/app split)
$js_size = 0;
$css_size = 0;
if (isset($compiled['vendor_js_bundle_path'])) {
$js_size += filesize("{$bundle_dir}/{$compiled['vendor_js_bundle_path']}");
}
if (isset($compiled['app_js_bundle_path'])) {
$js_size += filesize("{$bundle_dir}/{$compiled['app_js_bundle_path']}");
}
if (isset($compiled['vendor_css_bundle_path'])) {
$css_size += filesize("{$bundle_dir}/{$compiled['vendor_css_bundle_path']}");
}
if (isset($compiled['app_css_bundle_path'])) {
$css_size += filesize("{$bundle_dir}/{$compiled['app_css_bundle_path']}");
}
$this->line(' JS: ' . $this->_format_size($js_size) . ', CSS: ' . $this->_format_size($css_size));
$compiled_count++;
} catch (Exception $e) {
$this->error(" Failed: {$e->getMessage()}");
$failed_bundles[$class_name] = $e->getMessage();
}
}
if (!empty($failed_bundles)) {
$this->error(" {$compiled_count} compiled, " . count($failed_bundles) . ' failed');
return 1;
}
$this->line(" {$compiled_count} bundles compiled");
}
// Step 3: Laravel caches
if (!$this->option('skip-laravel-cache')) {
$this->line('[3/3] Building Laravel caches...');
passthru('php artisan optimize:cache 2>/dev/null', $exit_code);
if ($exit_code === 0) {
$this->line(' Laravel caches built');
}
} else {
$this->line('[3/3] Skipping Laravel caches (--skip-laravel-cache)');
}
$elapsed = round(microtime(true) - $start_time, 2);
$this->newLine();
$this->info("[OK] Production build complete ({$elapsed}s)");
if (Rsx::is_development()) {
$this->newLine();
$this->line('To use these assets, switch to production mode:');
$this->line(' php artisan rsx:mode:set production');
}
return 0;
}
private function _format_size(int $bytes): string
{
if ($bytes < 1024) {
return "{$bytes} B";
}
if ($bytes < 1048576) {
return round($bytes / 1024, 1) . ' KB';
}
return round($bytes / 1048576, 2) . ' MB';
}
}