Commit Graph

40 Commits

Author SHA1 Message Date
e5dcc47548 feat: 최종 리소스팩(선택 설치) + 약관 표시대상 추가
- pack.finalResourcepackPath 추가(/file/resourcepacks/outputs/, "."=없음, 기본 ".").
  편집기에 "최종 리소스팩 (.zip)" 입력 필드 추가.
- 약관 표시대상에 showInFinalResourcepack("최종 리소스팩에 표시") 3번째 토글 추가
  (스키마/시드/마이그레이션/편집기 체크박스·배지·저장 라우트, index.json 노출).
- 간편설치기 완료 단계: 기존 "직접 적용" 경고 제거 → 최종 리소스팩이 "."이 아니면
  "리소스팩을 설치하시겠습니까?" 예/아니요. 아니요→완료, 예→최종 리소스팩 약관
  (showInFinalResourcepack) 동의→/file/resourcepacks/outputs/ 에서 다운로드→완료.
  "." 이면 이 페이지 자체를 건너뜀.
- 0.4.15→0.4.16.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-23 00:33:07 +09:00
fefa2dcdd2 feat(server): 권장 JDK 목록을 Adoptium 에서 동적으로 + 자세히 보기(스냅샷)
- 하드코딩 [8,11,17,21,25] 대신 Adoptium(Temurin) API 에서 실제 배포 버전을 가져와
  편집기 드롭다운을 채운다. 브라우저 CORS 차단이라 서버가 프록시:
  GET /op/jdk-versions (메이저 목록), GET /op/jdk-versions/:major (GA/EA 릴리스명).
  6h 캐시 + 네트워크 실패 시 정적 폴백.
- 편집기: 기본은 LTS(25/21/17/11/8)만 표시, "자세히 보기" 체크 시 비-LTS 메이저까지
  펼치고 선택 메이저의 GA/EA 스냅샷 릴리스명을 함께 보여준다.
- normalizeRecommendedJdk 를 고정 목록 대신 8~99 범위로 완화(동적 메이저 허용).
- 버전 0.4.7→0.4.8.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-17 14:43:55 +09:00
a1d8435b71 feat(server): 사이트에서 권장 JDK 선택 + 설치기 권장 JDK 우선 탐색
- pack 에 recommendedJdk 추가(shared/types, store 정규화, 기본 25, 허용목록
  [8,11,17,21,25]). 사이트 편집기에 JDK 버전 드롭다운 추가(직접 입력 X).
- 설치기 JDK 탐색 순서 변경: ① 권장 JDK 자동설치 위치(temurin-<권장>) 우선 →
  ② 없으면 컴퓨터 JDK(JAVA_HOME/Program Files, 권장 버전 이상만 인정).
- 자동 설치/버전 검증도 pack 의 권장 버전을 사용(하드코딩 25 제거).
- 렌더러에 권장 JDK 표시, 관련 안내/문서 갱신. 버전 0.4.5→0.4.6.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-17 08:59:41 +09:00
996a7cc03e feat(installer): v0.4.0 — 파일제거기, 커스텀 폴더명, 포트포워딩 개편, 이름/개발자용 표기
- 음악퀴즈 파일제거 도구 신규 추가: 동의 → 휴지통/완전삭제 선택 → 커스텀 폴더
  (현재값 + 기본 .mc_custom) 전체, 데스크톱 바로가기, gameDir 가 해당 폴더인
  마인크래프트 런처 프로필 정리.
- MC_CUSTOM_DIR .env 로 커스텀 게임 폴더 이름 유동화(.mc_custom 기본). 렌더러
  사전에도 실제 폴더명 반영.
- 간편포트포워딩: 실행 중 UPnP 매핑 유지, 창 닫힘/종료 시 자동 제거(activePort 추적).
- 개발자용 빌드는 창/헤더 제목 앞에 (개발자용) 표기, exe 이름에도 반영.
- exe 이름 변경: 음악퀴즈 간편설치기 / 음악퀴즈 리소스팩설치기 / 간편포트포워딩.
- README 및 .env 템플릿 갱신, 버전 0.3.23 → 0.4.0.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-15 23:21:46 +09:00
662c3c7b23 docs+comment: pin account.json untrack-after-redeploy TODO
추적 해제는 코드 작업이 아니라 사용자의 1회 재배포에 게이트된 운영 절차이므로,
잊히지 않도록 명시적 TODO 를 코드(paths.ts) + 운영 문서(admin-site.md)에 고정.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-11 13:31:50 +09:00
face70b3e9 security: env-gate trust proxy, 0600 account file, seed+template for account.json untrack
리뷰 지적 추가 반영(서버 전용).
- trust proxy 를 항상 켜던 것을 TRUST_PROXY=true 일 때만 켜도록 변경. 직접 노출
  시 X-Forwarded-For 조작으로 로그인 rate limit 을 우회하던 문제 차단(프록시 뒤면
  TRUST_PROXY=true 설정).
- account.local.json 을 0o600(소유자 전용)으로 저장.
- 서버 시작 시 account.local.json 이 없으면 account.json 에서 시드(0o600). 이렇게
  하면 재배포 직후(로그인 전에도) 로컬 계정 파일이 항상 존재해, 이후 account.json
  을 안전하게 추적 해제할 수 있다.
- account.example.json 템플릿 추가.

account.json 자체의 git 추적 해제는 서버가 한 번 재배포되어 account.local.json 이
생성된 뒤 후속 커밋에서 처리(그 전에 지우면 pull 시 삭제되어 로그인이 막힘).

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-07-11 13:27:32 +09:00
f392842d7f security: login rate-limit, gitignored account store, secure cookie opt, upgrade-fail log
리뷰 지적 4건 반영(서버 전용, exe 영향 없음).
- 로그인 IP 기준 실패 제한(15분 창 10회 → 15분 차단). 브루트포스 + scrypt CPU
  남용 방지. 인메모리, 무한 성장 가드 포함.
- 운영 계정을 gitignore 된 account.local.json 으로 이전. readAccounts 는 local
  우선, 없으면 추적되는 account.json 을 시드로 읽음. writeAccounts 는 local 에만
  기록 → 첫 로그인 자동 해시 업그레이드부터는 추적 평문 파일을 더 쓰지 않음.
  (account.json 을 git rm --cached 하면 서버 pull 시 삭제되는 위험이 있어 추적 자체는
  건드리지 않고, 실질 사용 파일만 분리.)
- 세션 쿠키 secure 를 SESSION_COOKIE_SECURE=true 로 켤 수 있게(HTTPS 배포용).
- 평문→해시 자동 업그레이드 저장 실패를 조용히 무시하지 않고 console.error 로 기록.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-07-11 12:31:34 +09:00
00344084c7 security: hash operator passwords (scrypt) + persistent session secret
- 운영자 로그인 비밀번호를 평문 비교(===)에서 Node 내장 scrypt 해시 + 상수시간
  비교(verifyPassword)로 전환. 새 파일 src/server/password.ts. 기존 account.json
  의 평문 비밀번호는 그대로 검증되며, 로그인 성공 시 scrypt 해시로 자동 업그레이드
  후 저장(writeAccounts 추가). 외부 의존성 없음.
- 세션 시크릿을 하드코딩 폴백('...dev-secret') 대신, 환경변수 우선 → 없으면
  .session-secret 파일에 영구 랜덤값 생성/보관하도록 변경(세션 위조 방지, 재시작
  후에도 세션 유지). .session-secret 은 .gitignore 에 추가.

서버(사이트) 전용 변경이라 설치기 exe 는 영향 없음(재빌드 불필요).

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-07-11 12:24:42 +09:00
7b532d6520 feat: per-pack public flag + developer installer variants
manifest.json 의 각 pack 에 public(boolean) 을 추가하고, 대상(audience)별로
설치기를 구분한다.
- ManifestEntry.public 추가. 미지정=공개(하위호환). store 가 upsert/rename 시
  기존 public 보존, 신규 pack 은 public=true 기본. setPackPublic() 추가.
- 사이트 pack 편집기에 "공개" 체크박스 추가(체크=일반, 해제=개발자용). 저장 시
  manifest 엔트리 public 갱신, 편집기 진입 시 현재 값 표시.
- shared/audience.ts: 빌드의 musicQuizAudience(package.json)로 대상 판별.
  일반 설치기=public!==false 만, 개발자용=public===false 만 노출.
- 간편/리소스팩 설치기 packs 로드에서 audience 필터 적용.
- 개발자용 빌드 구성(electron-builder-dev.yml, electron-builder-rp-dev.yml)과
  dist:win:dev / dist:win:rp:dev 스크립트 추가(artifact: *-Dev-*). v0.3.20.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-07-11 00:32:06 +09:00
86883f56e4 add standalone port-forwarding tool (마인크래프트 간편 포트포워딩)
포트포워딩만 단독으로 수행하는 세 번째 Electron 앱 추가.
- 포트 입력(기본 25565) → UPnP 개방 시도 → 외부에서 닿는지 점검(ifconfig.co
  + 임시 리스너) → 외부 접속 주소/성공·실패·확인불가 안내. '포트 닫기'로 매핑 제거.
- 점검 로직은 메인 설치기의 v0.3.14 오탐 수정(ifconfig 실패→확인불가)을 반영해
  자체 포함. sharp 미사용이라 빌드에서 제외(exe 약 73MB).
- 신규: src/installer-pf/{main,preload}.ts, installer-pf/{index.html,renderer.js},
  locales/installer-pf/ko-kr.json, tsconfig.installer-pf.json, electron-builder-pf.yml.
- package.json 에 installer:pf / dist:win:pf 스크립트, i18n 컴포넌트 유니온에
  'installer-pf' 추가. v0.3.15.

Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
2026-06-24 21:43:06 +09:00
d22c6f17a3 store: rename file/list JSON when pack key changes
renamePack 가 manifest 정의와 약관 폴더는 새 키로 옮기면서 정작 음악·사진
목록(file/list/<key>.json)은 옛 키 파일에 남겨, 이름 변경 후 목록이 비어
보이던 버그 수정. 약관 폴더와 동일하게 fsp.rename 으로 옮기고 옛 파일이
없으면(ENOENT) 무시한다.
2026-06-05 01:57:30 +09:00
b4160aefc1 list-editor: add per-track description button + modal
Each music list row now shows a 설명 button immediately to the left of
the 별칭 button. Click opens a modal with a multi-line textarea; on
close the value is persisted into MusicListEntry.description and saved
to the same pack list JSON. The button gets a hasDesc visual indicator
when filled. Description is stored but intentionally not consumed by
datapack export or alias matching — purely informational metadata.

- types.ts: add description: string to MusicListEntry
- store.ts: normalize entry.description via sanitizeStr (defaults to '')
- listEditor.ejs: new #descModal alongside aliasModal
- listEditor.js: render descBtn left of aliasBtn, attach handlers,
  also set description: '' on playlist-fetched entries
- styles.css: extend trackRow grid to 6 cols, reuse aliasBtn styling
  for descBtn, add descTextarea sizing
- locale (ko-kr): descBtn / descModalTitle / descBack / descPlaceholder
  / descHint

Backwards-compatible: existing list JSON files without description
field normalize to ''.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 20:44:24 +09:00
9ba5dc6b7b 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>
2026-05-20 10:14:42 +09:00
05dc9d7166 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>
2026-05-20 01:39:28 +09:00
25977d894b terms: per-pack storage + import from another pack (v0.3.2)
- store.ts: 약관을 manifest/terms/<packKey>/ 폴더별로 저장. 첫 접근 시
  legacy 전역 .md 파일을 시드로 자동 복사한다.
- importTerms() 추가: 다른 음악퀴즈의 .md + _meta.json 을 현재 pack 으로
  복사한다. 동일 kind 는 source 값으로 덮어쓴다.
- /op/agreement 라우트를 세 단계로 분리:
  · /op/agreement → 음악퀴즈 카드 선택 페이지
  · /op/agreement/:packName → 해당 pack 의 약관 목록 + 추가 + 불러오기
  · /op/agreement/:packName/:kind → 에디터
- 공개 라우트도 /manifest/terms/:packKey/:fileName 으로 변경.
- 설치기 main.ts: state.selectedKey 를 약관 URL 에 포함하도록 수정 (메인 +
  rp 양쪽). pack 미선택 상태에서는 에러 반환.
- termsEditor.js: PACK_KEY 를 받아 저장 URL 에 포함.
- 다른 음악퀴즈 후보 select + 확인 모달 + locale 추가.

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

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 01:29:04 +09:00
c14b0507c7 terms: dark editor BG, vertical row layout, add/delete custom kinds
- 약관 편집기 배경/슬래시 메뉴를 사이트 다크 팔레트로 통일 (흰 배경 + 흰 글씨 가시성 문제 해결)
- 약관 목록을 가로 풀폭 1줄씩 세로로 쌓이는 레이아웃으로 변경
- 사용자 정의 약관 추가/삭제 지원
  - manifest/terms/_meta.json 에 라벨 저장
  - builtin 5종(map/resourcepack/mod/installer/installer-rp)은 삭제 불가, "기본" 배지 표시
  - kind 식별자 규칙: 소문자/숫자/하이픈 32자 이내
  - 공개 라우트 /manifest/terms/<file>.md 는 isPublicTermsFile() 로 _meta.json 차단
- 0.3.0 → 0.3.1

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 01:12:10 +09:00
ffb2048627 terms: agreement pages + site Notion-style editor + rp cancel fix
- 5종 약관(map/resourcepack/mod/installer/installer-rp) markdown 시드 + manifest/terms/ 노출
- 사이트 /op/agreement 목록 + Notion 스타일 markdown 에디터 (슬래시 명령어, 미리보기)
- 메인 installer: 음악퀴즈 선택 직후 약관 동의 페이지(맵·모드·설치기) 추가
- rp installer: 음악퀴즈 선택 직후 약관 동의 페이지(리소스팩·설치기) 추가
- rp installer 취소 버그 수정: buildResourcepackZip 단계간 + archive.abort() 폴링
- rp installer 취소 UX: 즉시 "취소 중…" 표시, 취소 시 installFailed 알림 생략
- 0.2.6 → 0.3.0 (큰 기능)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 00:55:36 +09:00
1f59f6a98b installer: move yt-dlp/ffmpeg under .mc_custom/installer/, clean platform-cache
- yt-dlp.exe, ffmpeg.exe now live in %appdata%/.mc_custom/installer/ so
  the .mc_custom root stays a clean Minecraft game folder. Existing
  binaries at the old location are migrated on first run.
- After a successful install, the platform-cache (downloaded fabric /
  forge / neoforge installer jars) is deleted — it's regenerable and
  was just wasting disk space.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 19:02:01 +09:00
794ad9b778 installer-rp: rename fallback pack name musicquiz → resourcepack
Per user request: when outputPackName is empty, fall back to
`<packKey>_resourcepack` instead of `<packKey>_musicquiz`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 18:54:31 +09:00
f810719d92 installer-rp: site-configured outputPackName for built zip
Adds a new "생성되는 리소스팩 이름" admin field saved to the pack
manifest and consumed by the rp installer when naming the final zip.
Empty value falls back to <packKey>_musicquiz; Windows-invalid chars
are sanitized to '_'. Bumps version 0.1.1 → 0.2.0 (new feature).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 18:34:46 +09:00
3017e77479 env: merge .env + .env.build instead of stopping at first match
Reviewer noted that returning on the first found file meant a project-root
`.env.build` could shadow the dev `.env`, leaving the server without
`PORT`/`HOST`/`SESSION_SECRET`. Switch `loadEnv()` to iterate every
candidate with `override:false` so multiple files merge — first-loaded
value wins per key.

Order also reshuffled so dev's `.env` takes precedence over `.env.build`
at the project root (server settings stay alive), while in packaged
mode `resources/.env.build` still wins. Verified manually with a
temp-dir reproduction.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 01:11:15 +09:00
c8da4207fc build: separate .env.build for packaging, keep .env dev-only
The previous setup packaged the development `.env` into the installer
resources, mixing local server settings (PORT/HOST/SESSION_SECRET) with
the build-time site domain. Introduce a dedicated `.env.build`:

- electron-builder configs now copy `.env.build` (gitignored) into
  `resources/`, no longer touching the dev `.env`.
- `loadEnv()` prefers `resources/.env.build` first, falling back to
  `resources/.env` (for operators who hand-edit the packaged file),
  then `<root>/.env.build`, then `<root>/.env`.
- `.env.build.example` documents the build-only keys (SITE_BASE_URL,
  MANIFEST_URL, MUSIC_CONCURRENCY); server-side keys stay in `.env`.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-18 01:03:22 +09:00
c8911a9a62 fix(editor): fabric에서도 platformDownloadUrl 저장되도록 수정
normalizePackDefinition 이 fabric 일 때 downloadUrl 을 의도적으로
스트립하고 있어 저장 후 입력값이 사라지는 문제. vanilla 외에는
모두 보관하도록 조건을 변경하고, 에디터 UI 도 fabric 에서 URL
입력 칸을 다시 보여주도록 되돌렸다.
2026-05-14 00:19:19 +09:00
af884706d4 op: 데이터팩 출력을 실제 music_quiz zip 으로 교체
가이드 (mc_datapack/launcher_datapack_연동_가이드.txt) 에 따라:
- file/datapacks/music_quiz_template/ 에 mc_datapack 의 music_quiz/ 정적
  파일을 미리 동봉 (data/mq/function/init/songs.mcfunction 제외).
- src/server/datapack.ts: list.music → SNBT (`{title, author, alias}`)
  songs.mcfunction 빌더와 archiver 기반 zip 스트리머 추가.
- /op/datapack/:packName/generate 가 텍스트 placeholder 대신
  music_quiz_<key>.zip 을 Content-Disposition attachment 로 내려준다.
- datapack.ejs 의 코드블록·복사 UI 제거, 곡 수는 서버 렌더 시점에 표시.
- 더 이상 쓰이지 않는 locales 의 datapackOutput.* 키 제거, datapack
  버튼 라벨/상태 문구를 zip 다운로드용으로 정리.
2026-05-13 16:34:34 +09:00
2344c4b8d2 site: 음악목록 항목별 별칭 편집 기능 추가
- MusicListEntry 에 aliases: string[] 필드 추가, 저장 시 trim·중복 제거.
- 목록 행에 "별칭" 버튼 표시(개수 있으면 강조), 클릭 시 모달 오픈.
- 모달에서 "별칭 추가" → 입력행 생성, "−" 버튼 → 해당 행 삭제,
  좌상단 "← 돌아가기" 또는 오버레이 클릭으로 저장 후 닫기.
2026-05-13 15:57:35 +09:00
c2fcc2fbbf i18n: 서버 측 모든 UI 문구를 locales/server/ko-kr.json 으로 분리
- src/shared/i18n.ts: 공용 i18n 로더 (dotted-key + {{placeholder}} 보간)
- locales/server/ko-kr.json: 사이트 + 라우터 + 데이터팩 출력 사전
- EJS 뷰는 res.locals.t 미들웨어로 일괄 적용
- listEditor.js 등 클라이언트 JS 는 사전을 inline <script> 로 주입받아 tt() 헬퍼 사용
2026-05-13 03:43:04 +09:00
401d72622e config: 빌드된 .exe 에도 .env 가 적용되도록 보강
- electron-builder.yml
  - dist/shared/** 추가 (env.js 등이 패키지에 들어가도록)
  - extraResources 로 빌드 시점 .env 를 resources/.env 에 배포
- loadEnv: 패키징된 Electron 앱이면 process.resourcesPath/.env 를 먼저 시도하고
  없으면 프로젝트 루트 .env 로 폴백
- docs/admin-site.md: .exe 빌드에도 .env 가 따라가는 동작 설명 추가
2026-05-13 03:29:30 +09:00
69ed4ad744 config: 사이트 도메인·서버 설정을 .env 로 중앙화 + 설치기 자동 종료 복구
- dotenv 도입, src/shared/env.ts 추가
  - loadEnv() 가 프로젝트 루트 .env 를 로드 (override=false: 쉘 env 우선)
  - getSiteBaseUrl() / getManifestUrl() 헬퍼
- 서버/설치기/리소스팩설치기 진입점에서 loadEnv() 호출
- 설치기 두 종의 기본 MANIFEST_URL 을 SITE_BASE_URL 기반으로 변경
  (운영 도메인을 한 곳에서만 바꾸면 됨)
- .env.example 템플릿 + .gitignore 에 .env 추가
- README / docs/admin-site.md 에 환경변수 표·사용법 추가
- installer/renderer.js: 4단계 완료 후 자동 종료 다시 활성화
2026-05-13 02:55:58 +09:00
7d0f1719f3 fabric: 로더 버전 선택 + fabric-installer CLI 자동 설치
관리 사이트에서 모드 플랫폼으로 fabric 을 선택하면 jar 파일 업로드 대신, 선택한 마인크래프트 버전을 기준으로 Fabric Meta v2 API 에서 호환 로더 목록을 가져와 드롭다운으로 선택하도록 했다. 설치기는 platform.loaderVersion 만 보고 최신 fabric-installer.jar 를 받아 CLI 로 자동 설치(GUI 미표시)한다.

스키마:
- PackPlatform 에 loaderVersion?: string 추가. fabric 일 때만 사용.
- normalizePackDefinition: fabric 이면 downloadUrl 무시하고 loaderVersion 만 저장, 그 외에는 기존 downloadUrl 유지.

웹 UI(views/op/editor.ejs):
- platformType 이 fabric 일 때 platformLoaderVersion select 노출. mcVersion 셀렉트 값을 가지고 https://meta.fabricmc.net/v2/versions/loader/<mcVersion> 호출.
- mcVersion 또는 platformType 변경 시 자동 재조회. 동시 요청 경쟁은 sequence 비교로 무시.
- 이전 저장값을 우선 선택하되 목록에 없으면 최신 stable 자동 선택.
- 폼 제출 시 fabric 인데 로더 미선택이면 경고.
- 라우트(op.ts): platformLoaderVersion 폼 필드 수신.

설치기(installer/main.ts):
- client:install 분기 추가. fabric 이면 installFabricLoader 호출.
- installFabricLoader: Fabric Meta installer 메타 조회 → 최신 stable installer jar 캐시 다운로드 → java -jar fabric-installer.jar client -mcversion <ver> -loader <ver> -dir <.mc_custom> -noprofile 실행. launcher_profiles 갱신은 우리 코드(updateLauncherProfile)가 담당하므로 -noprofile.
- findJavaExecutable: JAVA_HOME → .minecraft\runtime 의 번들 자바(델타/감마/베타 등 우선순위) → PATH 폴백.
- runJavaProcess: stdout/stderr 를 로그 뷰어에 prefix 와 함께 스트리밍. 실패 시 stderr 끝부분을 메시지에 포함.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-13 01:28:45 +09:00
635c22c7ad Migrate tsconfig to Node16 module/moduleResolution
VS Code surfaced the TS deprecation:
  'moduleResolution=node10' is deprecated and won't work in TS 7.0.

Fix: switch the root tsconfig.json from
  module: CommonJS, moduleResolution: node
to
  module: Node16, moduleResolution: Node16

TypeScript 7 only supports node16/nodenext/bundler. node16 matches the
runtime semantics we already use (Node ≥ 16, CommonJS output via the
absence of "type": "module" in package.json), so the emit is unchanged.

Side effect of Node16 resolution: relative imports must carry the .js
extension. Added .js to every relative import across src/* (17 sites,
8 files). Bare module specifiers (express, electron, node:fs, ...) are
unaffected.

Verified:
- tsc -p tsconfig.server.json — 0 errors
- tsc -p tsconfig.installer.json — 0 errors
- node dist/server/app.js boots; /op login → 302, /op/list → 200
2026-05-12 13:41:58 +09:00
7349d4e71e Auto-install yt-dlp into %appdata%/.mc_custom on first use
The previous flow required the operator to manually install yt-dlp on the
server. Now the server downloads the correct binary for the current OS/arch
from GitHub Releases (latest) the first time a playlist fetch is requested,
caches it under %appdata%/.mc_custom (resolved per-OS), chmod +x's it on
POSIX, and skips the download on every subsequent call.

- shared/paths.ts: add getAppDataDir() + getMcCustomDir() that map %appdata%
  to ~/.config (Linux), ~/Library/Application Support (macOS), or
  process.env.APPDATA (Windows) so the path is OS-agnostic.
- server/youtube.ts: replace the old "probe PATH and bail" probeYtDlp with
  ensureYtDlp(): picks the right GitHub asset name per platform.arch
  (yt-dlp.exe / yt-dlp_macos / yt-dlp_linux / yt-dlp_linux_aarch64 /
  yt-dlp_linux_armv7l), downloads it with redirect-following https.get to
  the install path, chmods +x, then verifies with `--version` before
  reporting success. Uses an installPromise singleton so concurrent
  requests don't race the download. On any failure it cleans up the
  partial file and throws YtDlpUnavailableError with a real reason.
- docs/yt-dlp-setup.md: note that auto-install is now the default; manual
  install is only for environments where the auto-download fails.

Smoke-tested on this Linux x64 box: first call downloads to
~/.config/.mc_custom/yt-dlp_linux and reports `2026.03.17`; second call
takes ~700ms (just the version probe) and reuses the cached file.
2026-05-12 13:15:25 +09:00
a2817c921d Add /op/list, /op/list/:pack, /op/datapack web admin + spec lock
docs/add.md
 - 사진 PNG 규격을 1024×1024 (4×4 블록 슬롯 × ×16 배율) 로 못박음
 - 짧은 변 기준 가운데 정사각 크롭 + 1024 초과 시만 축소, 미만은 native 유지

신규 라우트 (모두 requireAuth):
 - GET  /op/list                          → manifest 카드 목록
 - GET  /op/list/:pack                    → 음악목록·사진목록 탭 편집기
 - POST /op/list/:pack                    → file/list/<pack>.json 저장 (JSON)
 - POST /op/list/:pack/playlist           → yt-dlp 로 플레이리스트 펼치기
 - GET  /op/datapack                      → 음악퀴즈 선택 + 출력
 - GET  /op/datapack/:pack/generate       → 임시 포맷 mcfunction 텍스트

shared/types.ts: PackList / MusicListEntry / ImageListEntry
shared/store.ts: loadPackList, savePackList, normalizePackList
shared/paths.ts: fileListDirPath = file/list/
server/youtube.ts: yt-dlp 플레이리스트 펼치기 (--flat-playlist --dump-json),
  설치 안 됐을 때 NO_YTDLP 코드로 503.

UI:
 - views/op/list.ejs: 가로 카드 목록 + 돌아가기 버튼
 - views/op/listEditor.ejs + public/listEditor.js: 탭 전환, 드래그 정렬,
   우클릭 컨텍스트 메뉴(수정/삭제), 사진 수정 모달의 [유튜브 / 이미지] 토글,
   목록 저장·초기화·플레이리스트 불러오기 확인 팝업
 - views/op/datapack.ejs: 음악퀴즈 카드 선택 팝업 → 데이터팩 출력 + 복사
 - views/op/dashboard.ejs: 상단에 [음악목록 수정] [데이터팩 수정] 버튼
 - public/styles.css: 탭, 트랙 로우, 이미지 그리드, 컨텍스트 메뉴, 모달, 코드블록

.gitignore: conversations/ 추가.

스모크: login → /op/list 렌더, POST 저장 라운드트립 OK,
/op/datapack/.../generate 텍스트 출력 OK, 플레이리스트 fetch는 yt-dlp 미설치
환경에서 503 NO_YTDLP 메시지 정상.

Section 1 (리소스팩 설치기 EXE: yt-dlp 음악 다운로드, painting variant
텍스처 패키징, 리소스팩 zip 배치) 은 후속 커밋에서 작업.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-11 11:38:30 +09:00
44847b8a55 Switch mods to per-folder auto-discovery and resourcepack to single zip
- PackDefinition: replace mods[]/resourcepacks[] with modsFolder (string) + resourcepackPath (string); drop PackAsset
- Editor: replace dynamic add/remove lists with two single inputs; remove the now-dead JS for adding/removing rows
- Server: expose GET /file/mods/<folder>/index.json that returns the list of .jar names; folder name restricted to [a-zA-Z0-9_-]+
- Installer: fetch the listing JSON and download each jar from /file/mods/<folder>/<file>.jar; download the single resourcepack from /file/resourcepacks/<file>.zip directly into resourcepacks/

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 20:51:44 +09:00
9c4f0e8dbc Switch login to password-only and split pack zip paths
- Login form/route accepts password only; matched account row provides session userId
- PackDefinition: replace packPath with mapPath (.mc_custom/saves) and serverPath (server install dir); editor exposes two .zip fields
- Installer resolves relative platform/map/server URLs against manifest origin under /file/{platforms,maps,servers}/<name>; downloads and extracts the zips

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 20:06:32 +09:00
8fd7cfaaef Build music-quiz installer and management site per spec
Implements the full spec described in README.md:

Management site (Node + TypeScript + Express + EJS):
- Public main page lists packs registered in manifest.json.
- /op login (account.json, internal-only), /op/dashboard manages packs
  with horizontal-scroll cards, add/select-and-delete flow, and the
  /op/dashboard/:packName editor (Mojang release dropdown, dynamic
  mods/resourcepacks lists, platform/RAM fields, file rename).
- Routes for /manifest.json (public) and /file/* (server pack files).
- Middleware blocks /account.json and /manifest/* directory access.

Installer (Electron):
- Five page renderer driven by IPC (preload contextBridge API):
  pack pick → single/multi → server install (path no-Korean check, JDK
  detect, file download, EULA, RAM gating, local web config editor,
  UPnP/port-forward check) → client install (.mc_custom mods +
  resourcepacks + launcher_profiles.json gameDir/javaArgs) → finish
  toggles (server folder, shortcut, server start, launcher start).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 21:34:27 +09:00
cd79378f3c Reset repository to README title only
Approach is changing entirely; clearing prior implementation
to start over from a clean slate.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 20:41:19 +09:00
4453dbd8f3 Add client apply flow and asset uploads 2026-05-08 20:03:07 +09:00
427b708277 Improve installer automation and config editor 2026-05-08 19:29:07 +09:00
5ff4e20b5e Align dashboard and installer flows with spec 2026-05-08 19:02:50 +09:00
af6e559682 Build installer and management site from spec 2026-05-07 23:22:34 +09:00