https://github.com/gnuboard/g7/issues/122 제보자의 추가 지적 2건(빌드가 게시본을 지움 / CLI 최초 게시 후 웹 재생성 불가)에 대응하고, 같은 근본 원인을 공유하는 결함을 전수조사로 함께 고친다. - 빌드가 자기 산출물만 교체한다: 루트 vite `emptyOutDir: false` 기본값 true 는 폴백 없는 코어 3번들과 배달된 immutable URL 의 게시본을 함께 지웠다 - 게시 트리 권한을 웹이 이어받는다: 병합 전 프리플라이트 + 부모 그룹 상속·g+w 종전 소유권 정상화는 root 축만 덮어 비-root CLI 계정은 무방비였다 - 갱신 → 생성 → 완전성 확인을 한 묶음으로: 기록 바이트 대조, .old 원자 스왑, rename 일시 거부 재시도, 프론트 JSON 파싱 검증 - 실패를 억제하고 기록한다: 버전 한정 실패 마커 + 사유별 대시보드 알림 + ext-static:status 점검 커맨드 - 파생: catch-all 제외 목록을 에셋 화이트리스트 합집합에서 파생(mjs·webp·otf 누락), 번들 디스크 쓰기 fail-soft·빈 번들 503, 활성 디렉토리 prune 회피
104 lines
4.5 KiB
PHP
104 lines
4.5 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands\Concerns;
|
|
|
|
use Illuminate\Support\Facades\File;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* 빌드 산출물 정리 Trait
|
|
*
|
|
* 확장이 구동에 필요한 제3자 자산을 `dist/vendor/` 에 동봉하면서, vite 의
|
|
* `emptyOutDir: true` 를 그대로 둘 수 없게 됐다 — 매 빌드마다 동봉 자산이 삭제된다.
|
|
* 각 확장의 vite config 를 `emptyOutDir: false` 로 바꾸는 대신, **정리 책임을 빌드
|
|
* 커맨드로 옮긴다.** 확장마다 정리 규칙을 복제하면 한 곳만 빠져도 그 확장에서만
|
|
* 해시 청크가 누적되고, 그것은 배포본이 커진 뒤에야 드러난다.
|
|
*
|
|
* 정리 범위는 종전 `emptyOutDir: true` 와 같다 — `dist/` 전체를 비우되 **보존 대상만
|
|
* 남긴다.** "빌드가 만드는 것만 골라 지운다" 로 좁히면 소스에서 사라진 파일의 산출물이
|
|
* `dist/` 에 stale 로 남는다.
|
|
*/
|
|
trait PrunesBuildOutput
|
|
{
|
|
/**
|
|
* 빌드 산출물 디렉토리에서 보존 대상을 제외한 전부를 삭제합니다.
|
|
*
|
|
* 감시(watch) 모드에서는 호출하지 않는다 — 개발 중 재빌드마다 지우면 브라우저가
|
|
* 참조 중인 파일이 사라진다.
|
|
*
|
|
* **활성 디렉토리는 정리하지 않는다.** prune 은 빌드 *전에* 실행되므로, 웹이 서빙
|
|
* 중인 `dist/` 를 비우면 prune~빌드 완료 구간 전체가 서빙 공백이 된다(빌드가 실패하면
|
|
* 빈 채로 남는다). 확장 개발은 `_bundled` 에서 수행하고 활성 반영은 `{type}:update`
|
|
* 가 담당하므로, 활성 경로 빌드는 예외적 경로다 — 그 경우 stale 산출물이 누적되는
|
|
* 것을 감수하고 서빙 연속성을 택한다. 외부 확장처럼 `_bundled` 가 없어 활성 빌드가
|
|
* 유일한 경로인 경우에도 사이트가 끊기지 않는다.
|
|
*
|
|
* @param string $buildPath 확장 루트 경로 (`dist/` 의 부모)
|
|
* @param array<int, string> $preserve 삭제하지 않을 최상위 항목명
|
|
* @return array<int, string> 삭제한 최상위 항목명 목록
|
|
*/
|
|
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/');
|
|
}
|
|
}
|