info('๐Ÿš€ Starting NNTmux update process...'); // Initialize progress tracking $totalSteps = $this->calculateTotalSteps(); $this->progressBar = $this->output->createProgressBar($totalSteps); $this->progressBar->start(); try { // Prepare environment $this->prepareEnvironment(); // Execute update steps $this->executeUpdateSteps(); // Finalize $this->finalizeUpdate(); $this->progressBar->finish(); $this->newLine(2); $this->info('โœ… NNTmux update completed successfully!'); return Command::SUCCESS; } catch (\Exception $e) { $this->progressBar->finish(); $this->newLine(2); $this->error('โŒ Update failed: '.$e->getMessage()); $this->restoreEnvironment(); return Command::FAILURE; } } /** * Calculate total steps for progress tracking */ private function calculateTotalSteps(): int { $steps = 3; // prepare, finalize, cleanup if (! $this->option('skip-git')) { $steps++; } if (! $this->option('skip-composer')) { $steps++; } if (! $this->option('skip-npm')) { $steps += 2; } // install + build if (! $this->option('skip-db')) { $steps++; } $steps++; // env merge return $steps; } /** * Prepare the environment for updates */ private function prepareEnvironment(): void { $this->info('๐Ÿ”ง Preparing environment...'); // Check if app is in maintenance mode $this->wasInMaintenance = App::isDownForMaintenance(); if (! $this->wasInMaintenance) { $this->call('down', [ '--render' => 'errors::maintenance', '--retry' => 120, '--secret' => config('app.key'), ]); } // Check if tmux is running $tmux = new Tmux; $this->tmuxWasRunning = $tmux->isRunning(); if ($this->tmuxWasRunning) { $this->call('tmux:stop', ['--force' => true]); } $this->progressBar->advance(); } /** * Execute the main update steps */ private function executeUpdateSteps(): void { // Git operations if (! $this->option('skip-git')) { $this->performGitUpdate(); } // Composer operations if (! $this->option('skip-composer')) { $this->performComposerUpdate(); } // Database migrations if (! $this->option('skip-db')) { $this->performDatabaseUpdate(); } // NPM operations if (! $this->option('skip-npm')) { $this->performNpmOperations(); } // Clear caches and perform maintenance $this->performMaintenanceTasks(); } /** * Perform git update with better error handling */ private function performGitUpdate(): void { $this->info('๐Ÿ“ฅ Updating from git repository...'); $gitResult = $this->call('nntmux:git'); if ($gitResult !== 0) { throw new \Exception('Git update failed'); } $this->progressBar->advance(); } /** * Perform composer update with optimization */ private function performComposerUpdate(): void { $this->info('๐Ÿ“ฆ Updating composer dependencies...'); $composerResult = $this->call('nntmux:composer'); if ($composerResult !== 0) { throw new \Exception('Composer update failed'); } $this->composerWasRun = true; $this->progressBar->advance(); } /** * Perform database updates */ private function performDatabaseUpdate(): void { $this->info('๐Ÿ—„๏ธ Updating database...'); $dbResult = $this->composerWasRun ? $this->runFreshArtisanCommand(['nntmux:db']) : $this->call('nntmux:db'); if ($dbResult !== 0) { throw new \Exception('Database update failed'); } $this->progressBar->advance(); } /** * Run an Artisan command in a new PHP process so updated dependencies are loaded consistently. * * @param list $arguments */ private function runFreshArtisanCommand(array $arguments): int { $this->line(' โ„น Restarting Artisan for updated dependencies...'); $result = Process::timeout(3600) ->path(base_path()) ->run(array_merge([PHP_BINARY, 'artisan'], $arguments), function (string $type, string $output): void { $this->output->write($output); }); if (! $result->successful() && $result->errorOutput() !== '') { $this->error($result->errorOutput()); } return $result->exitCode(); } /** * Perform NPM operations with parallel processing where possible */ private function performNpmOperations(): void { $this->info('๐Ÿ“ฆ Installing npm packages...'); $process = Process::timeout(600) ->path(base_path()) ->run('npm ci --silent'); if (! $process->successful()) { // Fallback to npm install if ci fails $process = Process::timeout(600) ->path(base_path()) ->run('npm install --silent'); if (! $process->successful()) { throw new \Exception('NPM install failed: '.$process->errorOutput()); } } $this->progressBar->advance(); // Build assets $this->info('๐Ÿ”จ Building assets...'); $buildProcess = Process::timeout(600) ->path(base_path()) ->run('npm run build'); if (! $buildProcess->successful()) { throw new \Exception('Asset build failed: '.$buildProcess->errorOutput()); } $this->progressBar->advance(); } /** * Perform maintenance tasks */ private function performMaintenanceTasks(): void { // Clear view cache $this->info('๐Ÿงน Clearing view cache...'); $this->call('view:clear'); $this->info('Clearing route cache...'); $this->call('route:clear'); $this->info('Cache cleared successfully'); $this->info('Caching routes...'); $this->call('route:cache'); $this->info('Routes cached successfully'); // Merge environment variables $this->info('โš™๏ธ Merging environment configuration...'); $this->mergeEnvironmentConfig(); $this->progressBar->advance(); } /** * Merge environment configuration with improved error handling */ private function mergeEnvironmentConfig(): void { try { $envExamplePath = base_path('.env.example'); $envPath = base_path('.env'); if (! File::exists($envExamplePath)) { $this->warn(' โš  .env.example not found, skipping environment merge'); return; } if (! File::exists($envPath)) { $this->warn(' โš  .env not found, skipping environment merge'); return; } $envExampleVars = $this->parseEnvFile($envExamplePath); $envVars = $this->parseEnvFile($envPath); $missingKeys = array_diff_key($envExampleVars, $envVars); if (empty($missingKeys)) { $this->line(' โœ“ No new environment variables to merge'); return; } $this->addMissingEnvVars($envPath, $missingKeys); $this->line(' โœ“ Merged '.count($missingKeys).' new environment variables'); } catch (\Exception $e) { $this->warn(' โš  Environment merge failed: '.$e->getMessage()); } } /** * Parse environment file into key-value pairs * * @return array */ private function parseEnvFile(string $path): array { $content = File::get($path); $vars = []; foreach (preg_split("/\r\n|\n|\r/", $content) as $line) { $line = trim($line); if (empty($line) || str_starts_with($line, '#')) { continue; } if (preg_match('/^([^=]+)=(.*)$/', $line, $matches)) { $key = trim($matches[1]); $value = $matches[2]; $vars[$key] = $value; } } return $vars; } /** * Add missing environment variables to .env file * * @param array $missingKeys */ private function addMissingEnvVars(string $envPath, array $missingKeys): void { $content = File::get($envPath); if (! str_ends_with($content, "\n")) { $content .= "\n"; } $content .= "\n# New settings added from .env.example on ".now()->toDateTimeString()."\n"; foreach ($missingKeys as $key => $value) { $content .= "$key=$value\n"; } File::put($envPath, $content); } /** * Finalize the update process */ private function finalizeUpdate(): void { $this->info('๐Ÿ Finalizing update...'); // Clear application caches Cache::flush(); // Restore application state $this->restoreEnvironment(); $this->progressBar->advance(); } /** * Restore the original environment state */ private function restoreEnvironment(): void { // Restore maintenance mode state if (! $this->wasInMaintenance && App::isDownForMaintenance()) { $this->call('up'); } // Restore tmux state if ($this->tmuxWasRunning) { $this->call('tmux:start'); } } }