terms: per-term installer visibility toggles + universal delete (v0.3.4)

- _meta.json: customLabels -> terms.{label,showInInstaller,showInInstallerRp}
- Drop builtin protection; any term kind can be deleted/added/toggled
- New public route /manifest/terms/<pack>/index.json for installer term lists
- Installers fetch terms:list dynamically; skip agreement step if list empty
- Term editor: 2 visibility checkboxes (설치기 / 리소스팩 설치기), multi-select
- Migration from old schema preserves custom labels (default: visible in both)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-20 10:14:42 +09:00
parent 05dc9d7166
commit 9ba5dc6b7b
14 changed files with 445 additions and 130 deletions

View File

@@ -6,7 +6,9 @@ import {
manifestRootPath, manifestDirPath, manifestTermsDirPath,
fileDirPath, viewsDirPath, publicDirPath
} from '../shared/paths.js'
import { ensurePackTermsDir, isPublicTermsFile, loadPackDefinition } from '../shared/store.js'
import {
ensurePackTermsDir, isPublicTermsFile, listTermsWithLabels, loadPackDefinition
} from '../shared/store.js'
import { loadEnv } from '../shared/env.js'
import { t, localeDict } from './i18n.js'
import { indexRouter } from './routes/index.js'
@@ -71,6 +73,29 @@ app.get('/manifest.json', (_req, res) => {
// 요청하는 경우에도 작동하도록, 실제 pack 이면 ensurePackTermsDir 로 v0.3.1
// 전역 .md 들을 시드 복사한 뒤 sendFile 한다. 임의 packKey 로 빈 폴더가
// 생성되는 것은 loadPackDefinition 으로 차단.
// 설치기가 자기에게 표시할 약관 목록을 받아갈 수 있도록 packKey 별 index.json.
// 응답: [{ kind, label, showInInstaller, showInInstallerRp }]. v0.3.4~ builtin 개념이
// 없어졌으므로 인스톨러는 이 목록을 받아 자기 인스톨러용(`showInInstaller` / `showInInstallerRp`)
// 으로 필터링해서 탭을 만든다.
app.get('/manifest/terms/:packKey/index.json', async (req, res, next) => {
try {
const { packKey } = req.params
if (!/^[a-zA-Z0-9_\-]+$/.test(packKey)) {
res.status(404).json({ terms: [] })
return
}
const pack = await loadPackDefinition(packKey)
if (!pack) {
res.status(404).json({ terms: [] })
return
}
const terms = await listTermsWithLabels(packKey)
res.json({ terms })
} catch (error) {
next(error)
}
})
app.get('/manifest/terms/:packKey/:fileName', async (req, res, next) => {
try {
const { packKey, fileName } = req.params