From 7ad9c6fea88d52c712041b6213341d67783f8d10 Mon Sep 17 00:00:00 2001
From: E98Developer
Date: Sun, 16 Aug 2026 07:09:05 +0200
Subject: [PATCH] =?UTF-8?q?ADD=20Deployment=20csomagol=C3=B3=20phase2=20cs?=
=?UTF-8?q?omag=20el=C5=91=C3=A1ll=C3=ADt=C3=A1sa=20(mappa,=20manifest,=20?=
=?UTF-8?q?t=C3=B6rlend=C5=91k,=20verzi=C3=B3jel=C3=B6l=C5=91)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
A kijelölt tartományból elkészül a feltölthető mappa: deploy__, benne
a fájlok eredeti relatív útvonalon, hogy a feltöltés mappa-összeolvasztás legyen.
- GitRepository::archiveTo(): git archive --format=zip + ZipArchive kicsomagolás, ~100
fájlonként darabolva a parancssor-limit miatt. A tartalom a git objektumtárból jön, nem a
working tree-ből: így nem szivárog ki commitolatlan módosítás, és a .gitattributes eol=lf
miatt LF sorvéggel kerül a Linux célgépre. Üres pathspec esetén nem hívunk gitet, mert az
a TELJES fát csomagolná.
- DeploymentPackageBuilder: _MANIFEST.json/.txt (sha1 + méret fájlonként), _TORLENDO.txt a
törölt és az átnevezett fájlok régi útvonalával, _TEENDOK.md a diffből származó lépésekkel
(migrate, composer install, asset-figyelmeztetés, verzió-ellenőrzés), és public/
deploy-version.json a kirakott commit hashével.
- A git archive által kihagyott fájlok (pl. .gitattributes export-ignore) külön "missing"
listára kerülnek - csendben hiányzó fájl a manuális deploynál a legrosszabb hiba.
- Felületen fájlonkénti kézi kivétel, megerősítéses csomagolás gomb, eredmény-kártya.
- DeploymentGuard::ensureEnvironmentAllowed(): a builder felhasználó nélkül is ellenőrzi az
env kapcsolót és a stage whitelistet, így konzolról indítva sem fut le egy szerveren.
Co-Authored-By: Claude Opus 5
---
app/Filament/Pages/DeploymentPackage.php | 74 +++-
app/Services/Deployment/DeploymentGuard.php | 27 +-
.../Deployment/DeploymentPackageBuilder.php | 354 ++++++++++++++++++
app/Services/Deployment/GitRepository.php | 93 +++++
.../pages/deployment-package.blade.php | 67 +++-
tests/Feature/DeploymentPackageTest.php | 108 ++++++
6 files changed, 715 insertions(+), 8 deletions(-)
create mode 100644 app/Services/Deployment/DeploymentPackageBuilder.php
diff --git a/app/Filament/Pages/DeploymentPackage.php b/app/Filament/Pages/DeploymentPackage.php
index 2ab29f0..a9ddf7b 100644
--- a/app/Filament/Pages/DeploymentPackage.php
+++ b/app/Filament/Pages/DeploymentPackage.php
@@ -4,6 +4,7 @@
use App\Services\Deployment\ChangeSetAnalyzer;
use App\Services\Deployment\DeploymentGuard;
+use App\Services\Deployment\DeploymentPackageBuilder;
use App\Services\Deployment\DeploymentTargets;
use App\Services\Deployment\GitRepository;
use Filament\Forms\Components\Select;
@@ -18,10 +19,10 @@
use RuntimeException;
/**
- * Deployment csomagoló - fázis 1: célkörnyezet, commitlista és diff-előnézet.
+ * Deployment csomagoló: célkörnyezet, commit-tartomány, diff-előnézet és csomagolás.
*
- * Ez az oldal (a fázis 1-ben) semmit nem ír a fájlrendszerre, csak olvassa a git
- * történetet. A tényleges csomagolás a fázis 2-ben érkezik.
+ * Az oldal a git történetet olvassa, és a kijelölt tartományból egy időbélyeges mappát
+ * állít elő a config('deployment.output_path') alatt - a repón kívülre nem ír.
*
* A hozzáférést a DeploymentGuard dönti el (env kapcsoló + stage whitelist +
* developer szerep + feature flag), és a mount()-on kívül minden akció elején
@@ -50,6 +51,12 @@ class DeploymentPackage extends Page implements HasForms
public ?string $gitError = null;
+ /** @var array a felületen kézzel kivett fájlok */
+ public array $skippedPaths = [];
+
+ /** @var array|null az utoljára elkészített csomag adatai */
+ public ?array $lastPackage = null;
+
public static function canAccess(): bool
{
return app(DeploymentGuard::class)->isAllowed(auth()->user());
@@ -132,6 +139,67 @@ public function clearRange(): void
{
$this->fromCommit = null;
$this->toCommit = null;
+ $this->skippedPaths = [];
+ }
+
+ /**
+ * Egy fájl kézi ki-/visszavétele a csomagból.
+ */
+ public function toggleFile(string $path): void
+ {
+ app(DeploymentGuard::class)->ensureAllowed(auth()->user());
+
+ $this->skippedPaths = in_array($path, $this->skippedPaths, true)
+ ? array_values(array_diff($this->skippedPaths, [$path]))
+ : array_merge($this->skippedPaths, [$path]);
+ }
+
+ /**
+ * A csomag előállítása a kijelölt tartományból.
+ */
+ public function createPackage(): void
+ {
+ app(DeploymentGuard::class)->ensureAllowed(auth()->user());
+
+ if (! $this->fromCommit || ! $this->toCommit) {
+ Notification::make()
+ ->title('Nincs kijelölt tartomány')
+ ->body('Válassz egy kezdő és egy záró commitot.')
+ ->danger()
+ ->send();
+
+ return;
+ }
+
+ try {
+ $this->lastPackage = app(DeploymentPackageBuilder::class)->build(
+ (string) ($this->data['target'] ?? ''),
+ (string) ($this->data['branch'] ?? ''),
+ $this->fromCommit,
+ $this->toCommit,
+ $this->skippedPaths,
+ auth()->user()?->email,
+ );
+ } catch (RuntimeException $exception) {
+ Notification::make()
+ ->title('A csomag nem készült el')
+ ->body($exception->getMessage())
+ ->danger()
+ ->persistent()
+ ->send();
+
+ return;
+ }
+
+ Notification::make()
+ ->title('A csomag elkészült')
+ ->body(sprintf(
+ '%d fájl a(z) %s mappában.',
+ $this->lastPackage['counts']['copied'],
+ $this->lastPackage['name'],
+ ))
+ ->success()
+ ->send();
}
/**
diff --git a/app/Services/Deployment/DeploymentGuard.php b/app/Services/Deployment/DeploymentGuard.php
index 623d421..bd9e425 100644
--- a/app/Services/Deployment/DeploymentGuard.php
+++ b/app/Services/Deployment/DeploymentGuard.php
@@ -4,6 +4,7 @@
use App\Models\User;
use Laravel\Pennant\Feature;
+use RuntimeException;
/**
* A deployment csomagoló hozzáférés-ellenőrzése egy helyen.
@@ -26,9 +27,12 @@ public function isAllowed(?User $user = null): bool
}
/**
- * Az első nem teljesülő feltétel magyarázata, vagy null, ha minden rendben.
+ * Csak a környezeti feltételek (1-2. réteg), felhasználó nélkül.
+ *
+ * Ezt hívja a csomagoló service is: így akkor sem fut le, ha valaki a felületet
+ * megkerülve (konzolról, jobból) indítaná el egy szerveren.
*/
- public function denialReason(?User $user = null): ?string
+ public function environmentDenialReason(): ?string
{
if (! config('deployment.enabled')) {
return 'A deployment csomagoló ki van kapcsolva (DEPLOYMENT_PACKAGE_ENABLED).';
@@ -45,6 +49,18 @@ public function denialReason(?User $user = null): ?string
);
}
+ return null;
+ }
+
+ /**
+ * Az első nem teljesülő feltétel magyarázata, vagy null, ha minden rendben.
+ */
+ public function denialReason(?User $user = null): ?string
+ {
+ if ($reason = $this->environmentDenialReason()) {
+ return $reason;
+ }
+
if (! $user) {
return 'Bejelentkezés szükséges.';
}
@@ -66,4 +82,11 @@ public function ensureAllowed(?User $user = null): void
abort(403, $reason);
}
}
+
+ public function ensureEnvironmentAllowed(): void
+ {
+ if ($reason = $this->environmentDenialReason()) {
+ throw new RuntimeException($reason);
+ }
+ }
}
diff --git a/app/Services/Deployment/DeploymentPackageBuilder.php b/app/Services/Deployment/DeploymentPackageBuilder.php
new file mode 100644
index 0000000..b49b54d
--- /dev/null
+++ b/app/Services/Deployment/DeploymentPackageBuilder.php
@@ -0,0 +1,354 @@
+ $skippedPaths a felületen kézzel kivett fájlok
+ * @return array{
+ * name:string, path:string, target:string, branch:string,
+ * from:string, to:string, created_at:string, created_by:string,
+ * counts:array{copied:int, deleted:int, excluded:int, skipped:int, missing:int},
+ * files:array,
+ * deleted:array, skipped:array, missing:array,
+ * warnings:array
+ * }
+ */
+ public function build(
+ string $targetKey,
+ string $branch,
+ string $fromCommit,
+ string $toCommit,
+ array $skippedPaths = [],
+ ?string $createdBy = null,
+ ): array {
+ $this->guard->ensureEnvironmentAllowed();
+
+ $target = $this->targets->find($targetKey);
+
+ if (! $target) {
+ throw new RuntimeException('Ismeretlen célkörnyezet.');
+ }
+
+ $from = $this->repository->resolveCommit($fromCommit);
+ $to = $this->repository->resolveCommit($toCommit);
+
+ if (! $from || ! $to) {
+ throw new RuntimeException('Ismeretlen commit azonosító.');
+ }
+
+ $changeSet = $this->analyzer->analyze(
+ $this->repository->changedFiles($from, $to),
+ $target,
+ $branch,
+ );
+
+ $statuses = [];
+
+ foreach ($changeSet['files'] as $file) {
+ $statuses[$file['path']] = $file['status'];
+ }
+
+ $skipped = array_values(array_intersect($changeSet['copied'], $skippedPaths));
+ $copy = array_values(array_diff($changeSet['copied'], $skippedPaths));
+ $maxFiles = (int) config('deployment.max_files', 500);
+
+ if (count($copy) > $maxFiles) {
+ throw new RuntimeException(sprintf(
+ 'A csomag %d fájlt tartalmazna, a limit %d. Szűkítsd a commit-tartományt.',
+ count($copy),
+ $maxFiles,
+ ));
+ }
+
+ if ($copy === [] && $changeSet['deleted'] === []) {
+ throw new RuntimeException('Nincs mit csomagolni: a tartományban nincs a célgépre kerülő változás.');
+ }
+
+ $createdAt = Carbon::now();
+ $path = $this->createDirectory($targetKey, $createdAt);
+ $written = $this->repository->archiveTo($to, $copy, $path);
+
+ // Ha a git archive kihagyott valamit (pl. .gitattributes export-ignore), az itt
+ // derül ki - csendben hiányzó fájl a manuális deploynál pont a legrosszabb hiba.
+ $missing = array_values(array_diff($copy, $written));
+
+ $manifest = [
+ 'name' => basename($path),
+ 'path' => $path,
+ 'target' => $targetKey,
+ 'branch' => $branch,
+ 'from' => $from,
+ 'to' => $to,
+ 'created_at' => $createdAt->toIso8601String(),
+ 'created_by' => $createdBy ?? 'ismeretlen',
+ 'counts' => [
+ 'copied' => count($written),
+ 'deleted' => count($changeSet['deleted']),
+ 'excluded' => $changeSet['counts']['excluded'],
+ 'skipped' => count($skipped),
+ 'missing' => count($missing),
+ ],
+ 'files' => $this->describeFiles($path, $written, $statuses),
+ 'deleted' => $changeSet['deleted'],
+ 'skipped' => $skipped,
+ 'missing' => $missing,
+ 'warnings' => $changeSet['warnings'],
+ ];
+
+ $this->writeVersionMarker($path, $manifest, $target);
+ $this->writeManifest($path, $manifest, $target);
+ $this->writeDeleteList($path, $manifest, $target);
+ $this->writeInstructions($path, $manifest, $target);
+
+ return $manifest;
+ }
+
+ /**
+ * @param array $written
+ * @param array $statuses
+ * @return array
+ */
+ private function describeFiles(string $path, array $written, array $statuses): array
+ {
+ sort($written);
+
+ return array_map(function (string $relative) use ($path, $statuses): array {
+ $absolute = $path.DIRECTORY_SEPARATOR.str_replace('/', DIRECTORY_SEPARATOR, $relative);
+
+ return [
+ 'path' => $relative,
+ 'status' => $statuses[$relative] ?? '?',
+ 'size' => $this->files->exists($absolute) ? $this->files->size($absolute) : 0,
+ 'sha1' => $this->files->exists($absolute) ? (string) sha1_file($absolute) : '',
+ ];
+ }, $written);
+ }
+
+ private function createDirectory(string $targetKey, Carbon $createdAt): string
+ {
+ $base = rtrim((string) config('deployment.output_path'), '/\\');
+ $name = sprintf('deploy_%s_%s', $targetKey, $createdAt->format('Ymd_His'));
+ $path = $base.DIRECTORY_SEPARATOR.$name;
+
+ // Ugyanabban a másodpercben indított második csomag ne írja felül az elsőt.
+ $suffix = 2;
+
+ while ($this->files->exists($path)) {
+ $path = $base.DIRECTORY_SEPARATOR.$name.'-'.$suffix++;
+ }
+
+ $this->files->makeDirectory($path, 0775, true);
+
+ return $path;
+ }
+
+ /**
+ * A kirakott verzió azonosítója - ettől kezdve a "melyik commit fut a szerveren?"
+ * kérdés egy HTTP kéréssel megválaszolható, nem kézi fájldiffel.
+ *
+ * @param array $manifest
+ * @param array $target
+ */
+ private function writeVersionMarker(string $path, array $manifest, array $target): void
+ {
+ $this->put($path.'/public/deploy-version.json', json_encode([
+ 'target' => $target['key'],
+ 'commit' => $manifest['to'],
+ 'short' => substr((string) $manifest['to'], 0, 7),
+ 'branch' => $manifest['branch'],
+ 'packaged_at' => $manifest['created_at'],
+ 'by' => $manifest['created_by'],
+ ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)."\n");
+ }
+
+ /**
+ * @param array $manifest
+ * @param array $target
+ */
+ private function writeManifest(string $path, array $manifest, array $target): void
+ {
+ $this->put(
+ $path.'/_MANIFEST.json',
+ json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)."\n",
+ );
+
+ $lines = [
+ 'DEPLOYMENT CSOMAG',
+ str_repeat('=', 60),
+ 'Csomag: '.$manifest['name'],
+ 'Célkörnyezet: '.$target['label'],
+ 'Cél útvonal: '.$target['remote_path'],
+ 'Branch: '.$manifest['branch'],
+ 'Tartomány: '.substr((string) $manifest['from'], 0, 7).' (kizárva) -> '.substr((string) $manifest['to'], 0, 7).' (beleértve)',
+ 'Készítette: '.$manifest['created_by'],
+ 'Készült: '.$manifest['created_at'],
+ '',
+ sprintf('MÁSOLANDÓ FÁJLOK (%d)', $manifest['counts']['copied']),
+ str_repeat('-', 60),
+ ];
+
+ foreach ($manifest['files'] as $file) {
+ // mb_str_pad, mert a sprintf %-70s bájtban számol - egy ékezetes fájlnév
+ // elcsúsztatná az oszlopot.
+ $lines[] = sprintf(
+ '%-2s %s %8d %s',
+ $file['status'],
+ mb_str_pad($file['path'], 70),
+ $file['size'],
+ $file['sha1'],
+ );
+ }
+
+ if ($manifest['deleted'] !== []) {
+ $lines[] = '';
+ $lines[] = sprintf('TÖRLENDŐ A CÉLGÉPEN (%d) - ld. _TORLENDO.txt', $manifest['counts']['deleted']);
+ $lines[] = str_repeat('-', 60);
+
+ foreach ($manifest['deleted'] as $deleted) {
+ $lines[] = ' '.$deleted;
+ }
+ }
+
+ if ($manifest['skipped'] !== []) {
+ $lines[] = '';
+ $lines[] = sprintf('KÉZZEL KIVÉVE A CSOMAGBÓL (%d)', $manifest['counts']['skipped']);
+ $lines[] = str_repeat('-', 60);
+
+ foreach ($manifest['skipped'] as $skipped) {
+ $lines[] = ' '.$skipped;
+ }
+ }
+
+ if ($manifest['missing'] !== []) {
+ $lines[] = '';
+ $lines[] = sprintf('FIGYELEM - NEM KERÜLT A CSOMAGBA (%d)', $manifest['counts']['missing']);
+ $lines[] = str_repeat('-', 60);
+
+ foreach ($manifest['missing'] as $missing) {
+ $lines[] = ' '.$missing;
+ }
+ }
+
+ $this->put($path.'/_MANIFEST.txt', implode("\n", $lines)."\n");
+ }
+
+ /**
+ * @param array $manifest
+ * @param array $target
+ */
+ private function writeDeleteList(string $path, array $manifest, array $target): void
+ {
+ $lines = [
+ '# '.$manifest['name'].' - a célgépen TÖRLENDŐ fájlok',
+ '# Cél: '.$target['label'].' - '.$target['remote_path'],
+ '# A másolás ezeket nem intézi el, a törlés kézi lépés.',
+ '',
+ ];
+
+ foreach ($manifest['deleted'] as $deleted) {
+ $lines[] = $deleted;
+ }
+
+ if ($manifest['deleted'] === []) {
+ $lines[] = '# (nincs törlendő fájl ebben a csomagban)';
+ }
+
+ $this->put($path.'/_TORLENDO.txt', implode("\n", $lines)."\n");
+ }
+
+ /**
+ * @param array $manifest
+ * @param array $target
+ */
+ private function writeInstructions(string $path, array $manifest, array $target): void
+ {
+ $steps = [
+ 'A csomag mappájának **tartalmát** másold fel ide: `'.$target['remote_path'].'`'
+ .' — mappa-összeolvasztás, NEM a teljes app mappa cseréje. A `_` kezdetű fájlokat ne másold fel.',
+ ];
+
+ if ($manifest['deleted'] !== []) {
+ $steps[] = sprintf('Töröld a `_TORLENDO.txt`-ben felsorolt %d fájlt a célgépen.', $manifest['counts']['deleted']);
+ }
+
+ foreach ($manifest['warnings'] as $warning) {
+ if (str_contains($warning['title'], 'Migráció')) {
+ $steps[] = 'Futtasd a célgépen: `php artisan migrate`';
+ }
+
+ if (str_contains($warning['title'], 'composer')) {
+ $steps[] = 'Futtasd a célgépen: `composer install --no-dev` (a vendor/ nincs a csomagban)';
+ }
+
+ if (str_contains($warning['title'], 'Frontend')) {
+ $steps[] = 'A fordított assetek NINCSENEK a csomagban (`.gitignore`) — `npm run build` után kézzel másold fel a `public/build` tartalmát.';
+ }
+ }
+
+ $steps[] = 'Ürítsd a cache-t: `php artisan config:clear && php artisan view:clear && php artisan route:clear`';
+ $steps[] = 'Ellenőrizd a kirakott verziót: `curl https://'.$target['domain'].'/deploy-version.json`'
+ .' — a commitnak `'.substr((string) $manifest['to'], 0, 7).'` kell lennie.';
+
+ $lines = [
+ '# Teendők — '.$manifest['name'],
+ '',
+ '| | |',
+ '|---|---|',
+ '| Célkörnyezet | '.$target['label'].' |',
+ '| Cél útvonal | `'.$target['remote_path'].'` |',
+ '| Tartomány | `'.substr((string) $manifest['from'], 0, 7).'` → `'.substr((string) $manifest['to'], 0, 7).'` |',
+ '| Fájlok | '.$manifest['counts']['copied'].' másolandó, '.$manifest['counts']['deleted'].' törlendő |',
+ '',
+ ];
+
+ foreach ($steps as $index => $step) {
+ $lines[] = ($index + 1).'. '.$step;
+ }
+
+ if ($manifest['missing'] !== []) {
+ $lines[] = '';
+ $lines[] = '> **Figyelem:** '.$manifest['counts']['missing'].' fájl nem került a csomagba, '
+ .'a listájuk a `_MANIFEST.txt` végén van. Ezeket ellenőrizd, mielőtt feltöltesz.';
+ }
+
+ $this->put($path.'/_TEENDOK.md', implode("\n", $lines)."\n");
+ }
+
+ /**
+ * Mindig LF sorvéggel írunk: a csomag Windowson készül, de Linux célgépre megy.
+ */
+ private function put(string $path, string $contents): void
+ {
+ $directory = dirname($path);
+
+ if (! $this->files->isDirectory($directory)) {
+ $this->files->makeDirectory($directory, 0775, true);
+ }
+
+ $this->files->put($path, str_replace("\r\n", "\n", $contents));
+ }
+}
diff --git a/app/Services/Deployment/GitRepository.php b/app/Services/Deployment/GitRepository.php
index 4d84b2f..1fa9e10 100644
--- a/app/Services/Deployment/GitRepository.php
+++ b/app/Services/Deployment/GitRepository.php
@@ -4,6 +4,7 @@
use RuntimeException;
use Symfony\Component\Process\Process;
+use ZipArchive;
/**
* Csak olvasó git wrapper a deployment csomagolóhoz.
@@ -227,6 +228,98 @@ public function changedFiles(string $from, string $to): array
return $files;
}
+ /**
+ * A megadott commit szerinti fájltartalmat írja ki a célmappába, relatív útvonalakat megőrizve.
+ *
+ * Szándékosan a git objektumtárból dolgozunk (git archive) és nem a working tree-ből:
+ * így garantáltan a commitolt állapot kerül a csomagba (nem szivárog ki félkész
+ * módosítás), és a .gitattributes `text=auto eol=lf` miatt LF sorvégekkel, ami a
+ * Linux célgépeknek kell - a working tree másolása ezt egyik esetben sem garantálná.
+ *
+ * @param array $paths
+ * @return array a ténylegesen kiírt útvonalak
+ */
+ public function archiveTo(string $commit, array $paths, string $targetDirectory): array
+ {
+ // Pathspec nélkül a git archive a TELJES fát csomagolná - üres listánál ezért
+ // nem hívhatjuk meg egyáltalán.
+ if ($paths === []) {
+ return [];
+ }
+
+ $resolved = $this->resolveCommit($commit);
+
+ if (! $resolved) {
+ throw new RuntimeException('Ismeretlen commit azonosító.');
+ }
+
+ $written = [];
+
+ // Az útvonalak argumentumként mennek: Windowson a CreateProcess parancssora
+ // ~32 000 karakter, ezért darabolunk.
+ foreach (array_chunk($paths, 100) as $chunk) {
+ $archivePath = tempnam(sys_get_temp_dir(), 'deployment-archive-');
+
+ if ($archivePath === false) {
+ throw new RuntimeException('Nem hozható létre ideiglenes fájl a csomagoláshoz.');
+ }
+
+ try {
+ $this->run(array_merge(
+ ['archive', '--format=zip', '--output='.$archivePath, $resolved, '--'],
+ $chunk,
+ ));
+
+ $written = array_merge($written, $this->extract($archivePath, $targetDirectory));
+ } finally {
+ @unlink($archivePath);
+ }
+ }
+
+ return $written;
+ }
+
+ /**
+ * @return array
+ */
+ private function extract(string $archivePath, string $targetDirectory): array
+ {
+ $zip = new ZipArchive;
+
+ if ($zip->open($archivePath) !== true) {
+ throw new RuntimeException('A git archive kimenete nem nyitható meg.');
+ }
+
+ $entries = [];
+
+ try {
+ for ($index = 0; $index < $zip->numFiles; $index++) {
+ $name = $zip->getNameIndex($index);
+
+ if ($name === false || str_ends_with($name, '/')) {
+ continue;
+ }
+
+ // A célmappából kimutató bejegyzés nem fordulhat elő valódi git archive-ban,
+ // de a kiírás előtti ellenőrzés olcsó - és ez az egyetlen pont, ahol az
+ // alkalmazás a repón kívülre írhatna.
+ if (! $this->isSafePath($name)) {
+ throw new RuntimeException(sprintf('Gyanús útvonal a csomagban: %s', $name));
+ }
+
+ $entries[] = $name;
+ }
+
+ if ($entries !== [] && ! $zip->extractTo($targetDirectory, $entries)) {
+ throw new RuntimeException('A fájlok kicsomagolása nem sikerült.');
+ }
+ } finally {
+ $zip->close();
+ }
+
+ return $entries;
+ }
+
/**
* A repo gyökeréből ki nem mutató, relatív útvonal-e.
*/
diff --git a/resources/views/filament/pages/deployment-package.blade.php b/resources/views/filament/pages/deployment-package.blade.php
index 2c5a43b..44d84c1 100644
--- a/resources/views/filament/pages/deployment-package.blade.php
+++ b/resources/views/filament/pages/deployment-package.blade.php
@@ -35,10 +35,33 @@
(a kezdő commit kizárva, a záró beleértve).
- Ez a felület jelenleg csak olvas — a csomag előállítása a következő fázisban készül el.
+ A csomag ide készül: {{ config('deployment.output_path') }}