Document application modes (development/debug/production) Add global file drop handler, order column normalization, SPA hash fix Serve CDN assets via /_vendor/ URLs instead of merging into bundles Add production minification with license preservation Improve JSON formatting for debugging and production optimization Add CDN asset caching with CSS URL inlining for production builds Add three-mode system (development, debug, production) Update Manifest CLAUDE.md to reflect helper class architecture Refactor Manifest.php into helper classes for better organization Pre-manifest-refactor checkpoint: Add app_mode documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
232 lines
7.1 KiB
PHP
Executable File
232 lines
7.1 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\RSpade\Commands\Rsx;
|
|
|
|
use Illuminate\Console\Command;
|
|
use RecursiveDirectoryIterator;
|
|
use RecursiveIteratorIterator;
|
|
|
|
/**
|
|
* Export the application for deployment
|
|
*
|
|
* Runs rsx:prod:build then copies all necessary files to an export directory.
|
|
* The export can be deployed to production servers.
|
|
*/
|
|
class Prod_Export_Command extends Command
|
|
{
|
|
protected $signature = 'rsx:prod:export
|
|
{--path=./rsx-export : Export destination directory}
|
|
{--skip-build : Skip running rsx:prod:build (use existing assets)}';
|
|
|
|
protected $description = 'Export application for deployment';
|
|
|
|
public function handle(): int
|
|
{
|
|
$export_path = $this->option('path');
|
|
|
|
// Resolve relative path from base_path parent (since we're in /system)
|
|
if (str_starts_with($export_path, './')) {
|
|
$export_path = dirname(base_path()) . '/' . substr($export_path, 2);
|
|
} elseif (!str_starts_with($export_path, '/')) {
|
|
$export_path = dirname(base_path()) . '/' . $export_path;
|
|
}
|
|
|
|
$this->info('Exporting application for deployment...');
|
|
$this->line(" Destination: {$export_path}");
|
|
$this->newLine();
|
|
|
|
// Step 1: Run production build (unless --skip-build)
|
|
if (!$this->option('skip-build')) {
|
|
$this->line('[1/3] Running production build...');
|
|
passthru('php artisan rsx:prod:build', $exit_code);
|
|
if ($exit_code !== 0) {
|
|
$this->error('Production build failed');
|
|
|
|
return 1;
|
|
}
|
|
$this->newLine();
|
|
} else {
|
|
$this->line('[1/3] Skipping production build (--skip-build)');
|
|
}
|
|
|
|
// Step 2: Prepare export directory
|
|
$this->line('[2/3] Preparing export directory...');
|
|
|
|
// Clear existing export if it exists
|
|
if (is_dir($export_path)) {
|
|
$this->line(' Clearing existing export...');
|
|
$this->_clear_directory($export_path);
|
|
}
|
|
|
|
// Create export directory
|
|
if (!mkdir($export_path, 0755, true) && !is_dir($export_path)) {
|
|
$this->error("Failed to create export directory: {$export_path}");
|
|
|
|
return 1;
|
|
}
|
|
|
|
// Step 3: Copy files
|
|
$this->line('[3/3] Copying files...');
|
|
|
|
$base = dirname(base_path()); // Parent of /system
|
|
$copied_files = 0;
|
|
$copied_dirs = 0;
|
|
|
|
// Directories to copy
|
|
$dirs_to_copy = [
|
|
'system' => 'system',
|
|
'rsx' => 'rsx',
|
|
'node_modules' => 'node_modules',
|
|
'vendor' => 'vendor',
|
|
];
|
|
|
|
// Also include rsx-build from storage
|
|
$storage_rsx_build = base_path('storage/rsx-build');
|
|
if (is_dir($storage_rsx_build)) {
|
|
$dirs_to_copy['system/storage/rsx-build'] = 'system/storage/rsx-build';
|
|
}
|
|
|
|
foreach ($dirs_to_copy as $src_rel => $dest_rel) {
|
|
$src = "{$base}/{$src_rel}";
|
|
$dest = "{$export_path}/{$dest_rel}";
|
|
|
|
if (!is_dir($src)) {
|
|
$this->warn(" Skipping {$src_rel} (not found)");
|
|
continue;
|
|
}
|
|
|
|
$this->line(" Copying {$src_rel}/...");
|
|
$count = $this->_copy_directory($src, $dest, $src_rel);
|
|
$copied_files += $count;
|
|
$copied_dirs++;
|
|
}
|
|
|
|
// Copy *.json files from root
|
|
$json_files = glob("{$base}/*.json");
|
|
foreach ($json_files as $json_file) {
|
|
$filename = basename($json_file);
|
|
copy($json_file, "{$export_path}/{$filename}");
|
|
$copied_files++;
|
|
}
|
|
if (!empty($json_files)) {
|
|
$this->line(' Copying *.json files...');
|
|
}
|
|
|
|
// Create .gitignore for export directory
|
|
file_put_contents(
|
|
"{$export_path}/.gitignore",
|
|
"# This export should not be committed to version control\n*\n"
|
|
);
|
|
|
|
$this->newLine();
|
|
$this->info("[OK] Export complete");
|
|
$this->line(" {$copied_dirs} directories, {$copied_files} files");
|
|
$this->line(" Location: {$export_path}");
|
|
$this->newLine();
|
|
$this->line('Next steps:');
|
|
$this->line(' 1. Copy the export directory to your production server');
|
|
$this->line(' 2. Configure .env on the production server');
|
|
$this->line(' 3. Point your web server to the deployment');
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Copy a directory recursively, excluding certain paths
|
|
*/
|
|
private function _copy_directory(string $src, string $dest, string $rel_path): int
|
|
{
|
|
// Paths to exclude (relative to source)
|
|
$exclude_patterns = [
|
|
'.env',
|
|
'.env.example',
|
|
'storage/app',
|
|
'storage/logs',
|
|
'storage/framework/cache',
|
|
'storage/framework/sessions',
|
|
'storage/framework/views',
|
|
'storage/rsx-tmp',
|
|
'rsx-export',
|
|
'.git',
|
|
'.idea',
|
|
'.vscode',
|
|
'tests',
|
|
];
|
|
|
|
// For system directory, exclude additional paths
|
|
if ($rel_path === 'system') {
|
|
$exclude_patterns[] = 'storage/rsx-tmp';
|
|
}
|
|
|
|
if (!is_dir($dest)) {
|
|
mkdir($dest, 0755, true);
|
|
}
|
|
|
|
$copied = 0;
|
|
$iterator = new RecursiveIteratorIterator(
|
|
new RecursiveDirectoryIterator($src, RecursiveDirectoryIterator::SKIP_DOTS),
|
|
RecursiveIteratorIterator::SELF_FIRST
|
|
);
|
|
|
|
foreach ($iterator as $item) {
|
|
$sub_path = $iterator->getSubPathname();
|
|
|
|
// Check exclusions
|
|
$skip = false;
|
|
foreach ($exclude_patterns as $pattern) {
|
|
if (str_starts_with($sub_path, $pattern) || str_contains($sub_path, "/{$pattern}")) {
|
|
$skip = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ($skip) {
|
|
continue;
|
|
}
|
|
|
|
$dest_path = "{$dest}/{$sub_path}";
|
|
|
|
if ($item->isDir()) {
|
|
if (!is_dir($dest_path)) {
|
|
mkdir($dest_path, 0755, true);
|
|
}
|
|
} else {
|
|
// Ensure parent directory exists
|
|
$parent = dirname($dest_path);
|
|
if (!is_dir($parent)) {
|
|
mkdir($parent, 0755, true);
|
|
}
|
|
copy($item->getPathname(), $dest_path);
|
|
$copied++;
|
|
}
|
|
}
|
|
|
|
return $copied;
|
|
}
|
|
|
|
/**
|
|
* Clear a directory recursively
|
|
*/
|
|
private function _clear_directory(string $path): void
|
|
{
|
|
if (!is_dir($path)) {
|
|
return;
|
|
}
|
|
|
|
$iterator = new RecursiveIteratorIterator(
|
|
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
|
|
RecursiveIteratorIterator::CHILD_FIRST
|
|
);
|
|
|
|
foreach ($iterator as $item) {
|
|
if ($item->isDir()) {
|
|
rmdir($item->getPathname());
|
|
} else {
|
|
unlink($item->getPathname());
|
|
}
|
|
}
|
|
|
|
rmdir($path);
|
|
}
|
|
}
|