$preserve 삭제하지 않을 최상위 항목명 * @return array 삭제한 최상위 항목명 목록 */ private function pruneBuildOutput(string $buildPath, array $preserve = ['vendor']): array { $distPath = rtrim($buildPath, '/\\').DIRECTORY_SEPARATOR.'dist'; if (! File::isDirectory($distPath)) { return []; } if (! $this->isBundledSourcePath($buildPath)) { $this->warn( ' ⚠️ 활성 디렉토리 빌드 — 이전 산출물을 정리하지 않습니다 ' .'(정리하면 빌드 완료까지 서빙이 끊깁니다). stale 산출물이 누적될 수 있으니 ' .'개발은 _bundled 에서 하고 활성 반영은 update 커맨드로 하세요.' ); return []; } $removed = []; foreach (new \FilesystemIterator($distPath, \FilesystemIterator::SKIP_DOTS) as $item) { $name = $item->getBasename(); if (in_array($name, $preserve, true)) { continue; } $deleted = $item->isDir() ? File::deleteDirectory($item->getPathname()) : File::delete($item->getPathname()); if ($deleted) { $removed[] = $name; continue; } // 삭제 실패는 빌드를 막을 이유가 못 된다 — vite 가 같은 경로를 덮어쓴다. // 다만 조용히 넘기면 stale 산출물이 남은 것을 알 수 없으므로 기록한다. Log::warning('빌드 산출물 정리 실패 (다음 빌드에서 재시도)', [ 'path' => $item->getPathname(), ]); } return $removed; } /** * 빌드 경로가 소스 디렉토리(`_bundled` / `_pending`)인지 판정합니다. * * 이 둘은 웹이 서빙하지 않는 소스 보관소라 비워도 서빙 공백이 없다. 그 밖의 경로는 * 활성 디렉토리(= 서빙 중)로 본다 — 판정을 뒤집어 두면(활성 목록을 열거하면) 새로운 * 배치가 생길 때마다 조용히 활성 경로가 정리 대상이 된다. * * @param string $buildPath 확장 루트 경로 * @return bool 소스 디렉토리 여부 */ private function isBundledSourcePath(string $buildPath): bool { $normalized = str_replace('\\', '/', $buildPath); return str_contains($normalized, '/_bundled/') || str_contains($normalized, '/_pending/'); } }