[fix] solution/site: 대장에 없는 자산을 지우지 않는다 — 첫 배포에 운영 사이트가 끊겼다
직전 커밋(옛 해시 자산 30일 보관)을 배포하자 기존 사이트의 CSS·JS 가 전부 404 가 됐다. 옛 자산을 남기려고 만든 코드가 첫 실행에서 정확히 반대로 동작했다. pruneAssets 가 "대장(.builds.json)에 없는 파일"을 만료로 보고 지웠다. 그런데 대장은 이 기능과 함께 처음 생긴다 — 배포 직후 첫 실행에는 대장이 없으므로, 디스크에 있던 기존 자산이 전부 "대장에 없음"으로 분류돼 한꺼번에 삭제됐다. 아직 다시 굽지 않은 사이트는 그 순간 죽는다. 검증을 out/ 을 비운 상태에서만 돌린 탓에 못 봤다 — 재현했어야 할 것은 빈 디렉토리가 아니라 "옛 자산은 있는데 대장은 없는" 상태, 즉 실제 배포 직전의 서버 모습이었다. - scripts/prerender.ts: 대장에 없는 파일은 "지금 처음 본 것"으로 입양해 보관 기간을 새로 준다 - AGENTS.md: "기록이 없다"와 "만료됐다"를 같이 묶지 않는다 — 함정 목록에 ★로 박았다 - docs/DEVLOG.md: 사고 기록과 복구 절차(docker compose restart solution-frontend) 검증: 배포 직전 상태 재현 — out/assets 에 옛 해시 파일만 두고 대장 없이 첫 실행하면 옛 파일이 그대로 남고 대장에 입양 항목으로 들어간다. 재실행해도 대장이 늘지 않는다. tsc·eslint 통과, vitest 22 passed Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018xTWrJ6Mrr6HhEN6hZEER4
This commit is contained in:
parent
8f6ea16f65
commit
292cf26fd9
@ -34,6 +34,13 @@
|
||||
스타일 없는 페이지를 렌더한 것으로 기록된다. 보관 근거는 `out/assets/.builds.json` 대장이고
|
||||
파일 mtime 이 아니다.
|
||||
→ 재굽기는 여전히 필요하지만 **급하지 않다**. 디자인이 반영 안 될 뿐, 깨지지는 않는다.
|
||||
- **★ 대장(`out/assets/.builds.json`)에 없는 자산은 지우지 않는다** — "지금 처음 본 것" 으로
|
||||
치고 보관 기간을 새로 준다(`pruneAssets`). **이 규칙을 깨면 운영 사이트가 즉시 끊긴다.**
|
||||
실제로 그랬다(2026-09-07): 대장은 이 기능과 함께 생겼으므로 **배포 직후 첫 실행에는 대장이
|
||||
없고**, 그때 디스크에 있던 기존 자산이 전부 "대장에 없음" 으로 분류돼 한꺼번에 삭제됐다.
|
||||
옛 자산을 남기려고 만든 코드가 첫 실행에서 정확히 반대로 동작했다.
|
||||
→ 자산을 지우는 코드를 손볼 때는 **"기록이 없다"와 "만료됐다"를 절대 같이 묶지 않는다.**
|
||||
→ 이미 끊겼다면 복구는 `docker compose restart solution-frontend` (기동하며 전체 재굽기).
|
||||
- **★ 프론트(`solution/site`)를 배포하면 반드시 전체 재굽기 + 전체 재업로드.**
|
||||
`azure_static.publish(slug)` 는 공용 자산 + `s/<slug>` 만 올린다 —
|
||||
**렌더러를 고쳐도 다른 사이트에는 반영되지 않는다.**
|
||||
|
||||
@ -5,6 +5,32 @@
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-07 — (사고) 자산 보관 첫 배포에 운영 사이트 CSS 가 끊겼다
|
||||
|
||||
**무슨 일**
|
||||
바로 아래 항목(옛 해시 자산 30일 보관)을 배포하자 **기존 사이트의 CSS·JS 가 전부 404** 가 됐다.
|
||||
옛 자산을 남기려고 만든 코드가 첫 실행에서 정확히 반대로 동작했다.
|
||||
|
||||
**왜**
|
||||
`pruneAssets` 가 "대장(`.builds.json`)에 없는 파일" 을 만료로 보고 지웠다. 그런데 **대장은 이
|
||||
기능과 함께 처음 생긴다** — 배포 직후 첫 실행에는 대장이 없으므로, 디스크에 있던 기존 자산이
|
||||
전부 "대장에 없음" 으로 분류돼 한꺼번에 삭제됐다. 아직 다시 굽지 않은 사이트는 그 순간 죽는다.
|
||||
|
||||
**놓친 것** — 검증을 `out/` 을 비운 상태에서만 돌렸다. 재현해야 했던 건 빈 디렉토리가 아니라
|
||||
**"옛 자산은 있는데 대장은 없는"** 상태, 즉 실제 배포 직전의 서버 모습이었다.
|
||||
|
||||
**고친 것** (`scripts/prerender.ts` `pruneAssets`)
|
||||
- 대장에 없는 파일은 지우지 않고 **"지금 처음 본 것" 으로 입양해** 보관 기간을 새로 준다
|
||||
- 규칙으로 굳혀 둔다: **"기록이 없다" 와 "만료됐다" 를 같이 묶지 않는다**(AGENTS.md 함정 목록)
|
||||
|
||||
**복구** — `docker compose restart solution-frontend` (기동하며 전체 재굽기 → HTML 이 새 해시를
|
||||
가리킨다). 자산을 되살리는 게 아니라 HTML 을 새로 굽는 쪽이 빠르다.
|
||||
|
||||
**검증** — 배포 직전 상태를 재현: `out/assets` 에 옛 해시 파일만 두고 대장 없이 첫 실행 →
|
||||
옛 파일 2개가 그대로 남고 대장에 입양 항목으로 들어간다. 재실행해도 대장이 늘지 않는다.
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-07 — 옛 해시 자산을 30일 남긴다 — 배포와 재굽기를 뗀다
|
||||
|
||||
**왜**
|
||||
|
||||
@ -531,8 +531,8 @@ function readAssetLedger(assetsDir: string): AssetBuild[] {
|
||||
};
|
||||
return Array.isArray(parsed.builds) ? parsed.builds : [];
|
||||
} catch {
|
||||
// 없거나 깨졌으면 이번 빌드부터 다시 센다. 옛 파일은 그대로 남는다 —
|
||||
// 대장을 못 읽었다고 지우는 쪽으로 실패하면 사이트가 깨진다.
|
||||
// 없거나 깨졌으면 빈 대장으로 시작한다. 디스크에 있던 파일은 pruneAssets 가
|
||||
// "처음 본 것" 으로 입양하므로 지워지지 않는다 — 그 ★ 주석이 이 실패의 근거다.
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@ -545,13 +545,32 @@ function readAssetLedger(assetsDir: string): AssetBuild[] {
|
||||
*/
|
||||
function pruneAssets(assetsDir: string, current: string[]) {
|
||||
const signature = (files: string[]) => [...files].sort().join('\n');
|
||||
const now = new Date().toISOString();
|
||||
const previous = readAssetLedger(assetsDir);
|
||||
const head: AssetBuild = {at: new Date().toISOString(), files: current};
|
||||
const head: AssetBuild = {at: now, files: current};
|
||||
const builds =
|
||||
previous[0] && signature(previous[0].files) === signature(current)
|
||||
? [head, ...previous.slice(1)]
|
||||
: [head, ...previous];
|
||||
|
||||
/**
|
||||
* ★ 대장에 없는 파일은 **지우지 않는다.** 언제 깔렸는지 모를 뿐이므로 지금 처음 본 것으로
|
||||
* 치고 보관 기간을 새로 준다.
|
||||
*
|
||||
* 이 줄이 없어서 실제로 운영 사이트가 끊겼다(2026-09-07). 대장은 이 기능과 함께 생겼으니
|
||||
* **배포 직후 첫 실행에는 대장이 없다** — 그때 디스크에 있던 기존 자산이 전부 "대장에 없음"
|
||||
* 으로 분류돼 한꺼번에 삭제됐고, 아직 다시 굽지 않은 사이트의 CSS 가 통째로 404 가 됐다.
|
||||
* 옛 자산을 남기려고 만든 코드가 첫 실행에서 정확히 반대로 동작했다.
|
||||
*/
|
||||
const recorded = new Set(builds.flatMap((build) => build.files));
|
||||
const adopted = listRelativeFiles(assetsDir).filter(
|
||||
(file) => file !== ASSET_LEDGER && !recorded.has(file),
|
||||
);
|
||||
if (adopted.length > 0) {
|
||||
builds.push({at: now, files: adopted});
|
||||
console.log(` ✓ 대장에 없던 자산 ${adopted.length}개를 보관 대상으로 넣는다`);
|
||||
}
|
||||
|
||||
const cutoff = Date.now() - ASSET_RETENTION_DAYS * 24 * 60 * 60 * 1000;
|
||||
const kept = builds.filter(
|
||||
(build, index) => index < ASSET_MIN_BUILDS || Date.parse(build.at) >= cutoff,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user