UpdateNNTmux::performComposerUpdate()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 11
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
3
namespace App\Console\Commands;
4
5
use Blacklight\Tmux;
6
use Illuminate\Console\Command;
7
use Illuminate\Support\Facades\App;
8
use Illuminate\Support\Facades\Cache;
9
use Illuminate\Support\Facades\File;
10
use Illuminate\Support\Facades\Process;
11
use Symfony\Component\Console\Helper\ProgressBar;
12
use Ytake\LaravelSmarty\Smarty;
13
14
class UpdateNNTmux extends Command
15
{
16
    /**
17
     * The name and signature of the console command.
18
     */
19
    protected $signature = 'nntmux:all
20
                            {--skip-git : Skip git operations}
21
                            {--skip-composer : Skip composer update}
22
                            {--skip-npm : Skip npm operations}
23
                            {--skip-db : Skip database migrations}
24
                            {--force : Force update even if up-to-date}';
25
26
    /**
27
     * The console command description.
28
     */
29
    protected $description = 'Update NNTmux installation with improved performance and error handling';
30
31
    /**
32
     * @var bool Whether the app was in maintenance mode before we started
33
     */
34
    private bool $wasInMaintenance = false;
35
36
    /**
37
     * @var bool Whether tmux was running before we started
38
     */
39
    private bool $tmuxWasRunning = false;
40
41
    /**
42
     * @var ProgressBar Progress bar for tracking operations
43
     */
44
    private ProgressBar $progressBar;
45
46
    /**
47
     * Execute the console command.
48
     */
49
    public function handle(): int
50
    {
51
        $this->info('🚀 Starting NNTmux update process...');
52
53
        // Initialize progress tracking
54
        $totalSteps = $this->calculateTotalSteps();
55
        $this->progressBar = $this->output->createProgressBar($totalSteps);
56
        $this->progressBar->start();
57
58
        try {
59
            // Prepare environment
60
            $this->prepareEnvironment();
61
62
            // Execute update steps
63
            $this->executeUpdateSteps();
64
65
            // Finalize
66
            $this->finalizeUpdate();
67
68
            $this->progressBar->finish();
69
            $this->newLine(2);
70
            $this->info('✅ NNTmux update completed successfully!');
71
72
            return Command::SUCCESS;
73
74
        } catch (\Exception $e) {
75
            $this->progressBar->finish();
76
            $this->newLine(2);
77
            $this->error('❌ Update failed: '.$e->getMessage());
78
            $this->restoreEnvironment();
79
80
            return Command::FAILURE;
81
        }
82
    }
83
84
    /**
85
     * Calculate total steps for progress tracking
86
     */
87
    private function calculateTotalSteps(): int
88
    {
89
        $steps = 3; // prepare, finalize, cleanup
90
91
        if (! $this->option('skip-git')) {
92
            $steps++;
93
        }
94
        if (! $this->option('skip-composer')) {
95
            $steps++;
96
        }
97
        if (! $this->option('skip-npm')) {
98
            $steps += 2;
99
        } // install + build
100
        if (! $this->option('skip-db')) {
101
            $steps++;
102
        }
103
104
        $steps++; // smarty cache clear
105
        $steps++; // env merge
106
107
        return $steps;
108
    }
109
110
    /**
111
     * Prepare the environment for updates
112
     */
113
    private function prepareEnvironment(): void
114
    {
115
        $this->info('🔧 Preparing environment...');
116
117
        // Check if app is in maintenance mode
118
        $this->wasInMaintenance = App::isDownForMaintenance();
119
        if (! $this->wasInMaintenance) {
120
            $this->call('down', [
121
                '--render' => 'errors::maintenance',
122
                '--retry' => 120,
123
                '--secret' => config('app.key'),
124
            ]);
125
        }
126
127
        // Check if tmux is running
128
        $tmux = new Tmux;
129
        $this->tmuxWasRunning = $tmux->isRunning();
130
        if ($this->tmuxWasRunning) {
131
            $this->call('tmux-ui:stop', ['--kill' => true]);
132
        }
133
134
        $this->progressBar->advance();
135
    }
136
137
    /**
138
     * Execute the main update steps
139
     */
140
    private function executeUpdateSteps(): void
141
    {
142
        // Git operations
143
        if (! $this->option('skip-git')) {
144
            $this->performGitUpdate();
145
        }
146
147
        // Composer operations
148
        if (! $this->option('skip-composer')) {
149
            $this->performComposerUpdate();
150
        }
151
152
        // Database migrations
153
        if (! $this->option('skip-db')) {
154
            $this->performDatabaseUpdate();
155
        }
156
157
        // NPM operations
158
        if (! $this->option('skip-npm')) {
159
            $this->performNpmOperations();
160
        }
161
162
        // Clear caches and perform maintenance
163
        $this->performMaintenanceTasks();
164
    }
165
166
    /**
167
     * Perform git update with better error handling
168
     */
169
    private function performGitUpdate(): void
170
    {
171
        $this->info('📥 Updating from git repository...');
172
173
        $gitResult = $this->call('nntmux:git');
174
175
        if ($gitResult !== 0) {
176
            throw new \Exception('Git update failed');
177
        }
178
179
        $this->progressBar->advance();
180
    }
181
182
    /**
183
     * Perform composer update with optimization
184
     */
185
    private function performComposerUpdate(): void
186
    {
187
        $this->info('📦 Updating composer dependencies...');
188
189
        $composerResult = $this->call('nntmux:composer');
190
191
        if ($composerResult !== 0) {
192
            throw new \Exception('Composer update failed');
193
        }
194
195
        $this->progressBar->advance();
196
    }
197
198
    /**
199
     * Perform database updates
200
     */
201
    private function performDatabaseUpdate(): void
202
    {
203
        $this->info('🗄️ Updating database...');
204
205
        $dbResult = $this->call('nntmux:db');
206
207
        if ($dbResult !== 0) {
208
            throw new \Exception('Database update failed');
209
        }
210
211
        $this->progressBar->advance();
212
    }
213
214
    /**
215
     * Perform NPM operations with parallel processing where possible
216
     */
217
    private function performNpmOperations(): void
218
    {
219
        // Check if package.json has changed
220
        $packageLockExists = File::exists(base_path('package-lock.json'));
221
        $shouldInstall = ! $packageLockExists || $this->option('force');
222
223
        if ($shouldInstall) {
224
            $this->info('📦 Installing npm packages...');
225
226
            $process = Process::timeout(600)
227
                ->path(base_path())
228
                ->run('npm ci --silent');
229
230
            if (! $process->successful()) {
231
                // Fallback to npm install if ci fails
232
                $process = Process::timeout(600)
233
                    ->path(base_path())
234
                    ->run('npm install --silent');
235
236
                if (! $process->successful()) {
237
                    throw new \Exception('NPM install failed: '.$process->errorOutput());
238
                }
239
            }
240
        }
241
242
        $this->progressBar->advance();
243
244
        // Build assets
245
        $this->info('🔨 Building assets...');
246
247
        $buildProcess = Process::timeout(600)
248
            ->path(base_path())
249
            ->run('npm run build');
250
251
        if (! $buildProcess->successful()) {
252
            throw new \Exception('Asset build failed: '.$buildProcess->errorOutput());
253
        }
254
255
        $this->progressBar->advance();
256
    }
257
258
    /**
259
     * Perform maintenance tasks
260
     */
261
    private function performMaintenanceTasks(): void
262
    {
263
        // Clear Smarty cache
264
        $this->info('🧹 Clearing Smarty cache...');
265
        $this->clearSmartyCache();
266
        $this->progressBar->advance();
267
268
        // Merge environment variables
269
        $this->info('⚙️ Merging environment configuration...');
270
        $this->mergeEnvironmentConfig();
271
        $this->progressBar->advance();
272
    }
273
274
    /**
275
     * Clear Smarty compiled templates
276
     */
277
    private function clearSmartyCache(): void
278
    {
279
        try {
280
            $smarty = new Smarty;
281
            $smarty->setCompileDir(config('ytake-laravel-smarty.compile_path'));
282
283
            if ($smarty->clearCompiledTemplate()) {
284
                $this->line('  ✓ Smarty compiled template cache cleared');
285
            } else {
286
                $this->warn('  ⚠ Could not clear Smarty cache automatically');
287
                $this->line('    Please clear manually: '.config('ytake-laravel-smarty.compile_path'));
288
            }
289
        } catch (\Exception $e) {
290
            $this->warn('  ⚠ Smarty cache clear failed: '.$e->getMessage());
291
        }
292
    }
293
294
    /**
295
     * Merge environment configuration with improved error handling
296
     */
297
    private function mergeEnvironmentConfig(): void
298
    {
299
        try {
300
            $envExamplePath = base_path('.env.example');
301
            $envPath = base_path('.env');
302
303
            if (! File::exists($envExamplePath)) {
304
                $this->warn('  ⚠ .env.example not found, skipping environment merge');
305
306
                return;
307
            }
308
309
            if (! File::exists($envPath)) {
310
                $this->warn('  ⚠ .env not found, skipping environment merge');
311
312
                return;
313
            }
314
315
            $envExampleVars = $this->parseEnvFile($envExamplePath);
316
            $envVars = $this->parseEnvFile($envPath);
317
318
            $missingKeys = array_diff_key($envExampleVars, $envVars);
319
320
            if (empty($missingKeys)) {
321
                $this->line('  ✓ No new environment variables to merge');
322
323
                return;
324
            }
325
326
            $this->addMissingEnvVars($envPath, $missingKeys);
327
            $this->line('  ✓ Merged '.count($missingKeys).' new environment variables');
328
329
        } catch (\Exception $e) {
330
            $this->warn('  ⚠ Environment merge failed: '.$e->getMessage());
331
        }
332
    }
333
334
    /**
335
     * Parse environment file into key-value pairs
336
     */
337
    private function parseEnvFile(string $path): array
338
    {
339
        $content = File::get($path);
340
        $vars = [];
341
342
        foreach (preg_split("/\r\n|\n|\r/", $content) as $line) {
343
            $line = trim($line);
344
345
            if (empty($line) || str_starts_with($line, '#')) {
346
                continue;
347
            }
348
349
            if (preg_match('/^([^=]+)=(.*)$/', $line, $matches)) {
350
                $key = trim($matches[1]);
351
                $value = $matches[2];
352
                $vars[$key] = $value;
353
            }
354
        }
355
356
        return $vars;
357
    }
358
359
    /**
360
     * Add missing environment variables to .env file
361
     */
362
    private function addMissingEnvVars(string $envPath, array $missingKeys): void
363
    {
364
        $content = File::get($envPath);
365
366
        if (! str_ends_with($content, "\n")) {
367
            $content .= "\n";
368
        }
369
370
        $content .= "\n# New settings added from .env.example on ".now()->toDateTimeString()."\n";
371
372
        foreach ($missingKeys as $key => $value) {
373
            $content .= "$key=$value\n";
374
        }
375
376
        File::put($envPath, $content);
377
    }
378
379
    /**
380
     * Finalize the update process
381
     */
382
    private function finalizeUpdate(): void
383
    {
384
        $this->info('🏁 Finalizing update...');
385
386
        // Clear application caches
387
        Cache::flush();
388
389
        // Restore application state
390
        $this->restoreEnvironment();
391
392
        $this->progressBar->advance();
393
    }
394
395
    /**
396
     * Restore the original environment state
397
     */
398
    private function restoreEnvironment(): void
399
    {
400
        // Restore maintenance mode state
401
        if (! $this->wasInMaintenance && App::isDownForMaintenance()) {
402
            $this->call('up');
403
        }
404
405
        // Restore tmux state
406
        if ($this->tmuxWasRunning) {
407
            $this->call('tmux-ui:start');
408
        }
409
    }
410
}
411