$changedFiles * @param array{key:string, label:string, domain:string, remote_path:string, expected_branch:?string}|null $target * @return array{ * files: array, * copied: array, * deleted: array, * counts: array{total:int, copied:int, deleted:int, excluded:int}, * warnings: array * } */ public function analyze(array $changedFiles, ?array $target = null, ?string $branch = null): array { /** @var array $excludePatterns */ $excludePatterns = (array) config('deployment.exclude', []); $files = []; $copied = []; $deleted = []; $excludedCount = 0; foreach ($changedFiles as $file) { $excluded = Str::is($excludePatterns, $file['path']); // Törlés esetén nincs mit másolni: a fájl a célgépen létezik, itt már nem. // Átnevezésnél mindkettő kell - az új útvonal másolandó, a régi törlendő. $action = $excluded ? 'skip' : ($file['status'] === 'D' ? 'delete' : 'copy'); if ($excluded) { $excludedCount++; } elseif ($action === 'delete') { $deleted[] = $file['path']; } else { $copied[] = $file['path']; if ($file['old_path'] !== null && ! Str::is($excludePatterns, $file['old_path'])) { $deleted[] = $file['old_path']; } } $files[] = array_merge($file, ['excluded' => $excluded, 'action' => $action]); } return [ 'files' => $files, 'copied' => $copied, 'deleted' => $deleted, 'counts' => [ 'total' => count($files), 'copied' => count($copied), 'deleted' => count($deleted), 'excluded' => $excludedCount, ], 'warnings' => $this->warnings($copied, $deleted, $excludedCount, $target, $branch), ]; } /** * @param array $copied * @param array $deleted * @param array{key:string, label:string, domain:string, remote_path:string, expected_branch:?string}|null $target * @return array */ private function warnings(array $copied, array $deleted, int $excludedCount, ?array $target, ?string $branch): array { $warnings = []; $maxFiles = (int) config('deployment.max_files', 500); if ($copied === [] && $deleted === []) { $warnings[] = [ 'level' => 'info', 'title' => 'Nincs csomagolható változás', 'body' => 'A két commit között nincs olyan fájl, ami a célgépre kerülne.', ]; } if (count($copied) > $maxFiles) { $warnings[] = [ 'level' => 'danger', 'title' => sprintf('Túl sok fájl (%d db, limit %d)', count($copied), $maxFiles), 'body' => 'Ekkora tartomány kézi másolással már nehezen ellenőrizhető. Szűkítsd a commit-tartományt, vagy bontsd több csomagra.', ]; } $migrations = $this->matching($copied, 'migration'); if ($migrations !== []) { $warnings[] = [ 'level' => 'warning', 'title' => sprintf('Migráció a csomagban (%d db)', count($migrations)), 'body' => 'A fájlok felmásolása után a célgépen le kell futtatni: php artisan migrate', ]; } $seeders = $this->matching($copied, 'seeder'); if ($seeders !== []) { $warnings[] = [ 'level' => 'warning', 'title' => sprintf('Seeder a csomagban (%d db)', count($seeders)), 'body' => 'A seeder felmásolása önmagában nem csinál semmit - el kell dönteni, hogy le kell-e futtatni a célgépen. ' .'Ha feature flageket ír, a lefutása után azok azonnal hatnak.', ]; } if ($this->matching($copied, 'composer') !== []) { $warnings[] = [ 'level' => 'warning', 'title' => 'Függőség változott (composer)', 'body' => 'A vendor/ nincs verziókövetve, ezért nem kerül a csomagba - a célgépen composer install szükséges.', ]; } if ($this->matching($copied, 'asset') !== []) { $warnings[] = [ 'level' => 'warning', 'title' => 'Frontend forrás változott', 'body' => 'A fordított assetek (public/build, public/js, public/css, mix-manifest.json) a .gitignore miatt NEM kerülnek a csomagba. Futtass npm run build-ot, és a fázis 4-től külön csatolhatók lesznek.', ]; } if ($this->matching($copied, 'runtime') !== []) { $jobs = count($this->matching($copied, 'job')); $consoles = count($this->matching($copied, 'console')); $detail = []; if ($jobs > 0) { $detail[] = $jobs.' job'; } if ($consoles > 0) { $detail[] = $consoles.' konzol parancs'; } $warnings[] = [ 'level' => 'warning', 'title' => 'Hosszan futó konténerek érintve'.($detail === [] ? '' : ' ('.implode(', ', $detail).')'), 'body' => 'A queue worker és a scheduler külön konténerben fut, és a betöltött osztályokat a memóriájában ' .'tartja - a fájlok felmásolása után is a RÉGI kóddal dolgozik, amíg újra nem indul. Ez némán rossz ' .'viselkedést okoz (a job "elkészül", de nem azt csinálja, amit az új kód). A konkrét restart parancsok ' .'a csomag _TEENDOK.md fájljában lesznek.', ]; } if ($deleted !== []) { $warnings[] = [ 'level' => 'warning', 'title' => sprintf('Törlendő fájl a célgépen (%d db)', count($deleted)), 'body' => 'Ezeket a másolás nem intézi el, kézzel kell törölni - a csomag _TORLENDO.txt fájlja fogja tartalmazni őket.', ]; } if ($excludedCount > 0) { $warnings[] = [ 'level' => 'info', 'title' => sprintf('Kihagyott fájl (%d db)', $excludedCount), 'body' => 'A kizárási lista (.env, storage/, vendor/, node_modules/, .git*) alapján ezek soha nem kerülnek csomagba.', ]; } if ($target && $target['expected_branch'] && $branch && $branch !== $target['expected_branch']) { $warnings[] = [ 'level' => 'warning', 'title' => 'Nem a szokásos branch', 'body' => sprintf( 'A(z) "%s" környezetre általában a %s branchről megy csomag, most viszont a %s van kiválasztva.', $target['label'], $target['expected_branch'], $branch, ), ]; } return $warnings; } /** * @param array $paths * @return array */ private function matching(array $paths, string $group): array { /** @var array $patterns */ $patterns = (array) config('deployment.attention.'.$group, []); return array_values(array_filter($paths, fn (string $path): bool => Str::is($patterns, $path))); } }