terms: seed-on-fetch + rename/delete sync (v0.3.3)

- public route `/manifest/terms/:packKey/:fileName` 가 sendFile 전에
  `ensurePackTermsDir(packKey)` 를 호출하도록 수정. 관리자가 사이트 약관
  페이지를 한 번도 열지 않은 fresh 배포에서도 설치기가 정상적으로 약관을
  받을 수 있다. `loadPackDefinition` 으로 실제 pack 만 허용해 임의 키로
  빈 폴더가 생성되는 것을 차단.
- `renamePack`: pack JSON 이름이 바뀌면 `manifest/terms/<oldKey>/` 도
  `<newKey>/` 로 함께 rename.
- `deletePackKeys`: pack 삭제 시 약관 폴더도 `fs.rm` 으로 정리 — 동일 key
  재생성 시 옛 약관 부활 방지.
- `ensurePackTermsDir` export.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 01:39:28 +09:00
parent 25977d894b
commit 05dc9d7166
3 changed files with 53 additions and 13 deletions

View File

@@ -178,6 +178,14 @@ export async function deletePackKeys(keys: string[]): Promise<void> {
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
}
// pack 이 삭제되면 약관 폴더도 함께 정리한다. 동일 packKey 로 재생성될 때
// 옛 약관이 부활하는 것을 막기 위함.
const termsDir = path.join(manifestTermsDirPath, key)
try {
await fsp.rm(termsDir, { recursive: true, force: true })
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
}
await syncManifestWith(key, '', 'remove')
}
}
@@ -198,6 +206,19 @@ export async function renamePack(oldKey: string, newKey: string, pack: PackDefin
} catch (error) {
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error
}
// 약관 폴더도 함께 이름을 바꾼다 (있는 경우만). pack 이름이 바뀌었는데 약관이
// 옛 폴더에 남아 있으면 인스톨러가 새 packKey 로 약관을 받지 못한다.
const oldTermsDir = path.join(manifestTermsDirPath, oldKey)
const newTermsDir = path.join(manifestTermsDirPath, safeNew)
try {
await fsp.rename(oldTermsDir, newTermsDir)
} catch (error) {
const code = (error as NodeJS.ErrnoException).code
// 옛 약관 폴더가 없으면 그대로 둔다. 새 폴더가 이미 있어 충돌하면 그것도 그냥 둔다
// (renamePack 단계에서 사용자에게 보낼 마땅한 UX 가 없고, 다음 약관 접근 때
// 새 폴더 내용이 정상적으로 사용된다).
if (code !== 'ENOENT' && code !== 'ENOTEMPTY' && code !== 'EEXIST') throw error
}
await syncManifestWith(oldKey, '', 'remove')
}
await syncManifestWith(safeNew, pack.name, 'upsert')
@@ -347,8 +368,12 @@ function isValidPackKey(packKey: string): boolean {
* 해당 pack 폴더가 없으면 만든다. 이전 버전(v0.3.1) 의 전역 `manifest/terms/*.md`
* 파일이 남아 있는 경우 첫 접근 시 그 내용을 그대로 새 폴더에 복사해 시드한다.
* 시드는 한 번만 발생: 폴더가 이미 있으면 아무것도 안 한다.
*
* 공개 라우트(`/manifest/terms/<packKey>/<file>`) 에서도 호출되므로 export 한다.
* 라우트 측은 packKey 가 실제 존재하는 pack 인지 확인한 다음에 호출해야 한다
* (그렇지 않으면 임의 키로 빈 폴더가 생성될 수 있다).
*/
async function ensurePackTermsDir(packKey: string): Promise<string> {
export async function ensurePackTermsDir(packKey: string): Promise<string> {
const dir = termsDirForPack(packKey)
try {
await fsp.access(dir)