op: 데이터팩 출력을 zip 대신 songs.mcfunction 코드 텍스트로 변경

운영자가 mc_datapack 의 init/songs.mcfunction 파일에 직접 복사해 붙여넣
는 워크플로로 단순화. 전체 데이터팩을 패키징할 필요가 없다.

- /op/datapack/:packName/generate 가 buildSongsMcfunction(list) 결과를
  text/plain 으로 반환 (zip 스트리밍 제거).
- file/datapacks/music_quiz_template/ 정적 사본 제거.
- datapack.ejs 에 코드블록·복사 버튼 복원, 안내 문구 추가
  ("data/mq/function/init/songs.mcfunction 에 그대로 덮어쓰세요").
- datapack 로케일 라벨을 "코드 출력 / 복사 / 출력 완료" 로 정리.
This commit is contained in:
2026-05-13 16:41:25 +09:00
parent af884706d4
commit de08f9a810
65 changed files with 44 additions and 817 deletions

View File

@@ -1,14 +1,5 @@
import path from 'node:path'
import { Readable } from 'node:stream'
import archiver from 'archiver'
import type { Response } from 'express'
import { fileDatapacksDirPath } from '../shared/paths.js'
import type { MusicListEntry, PackList } from '../shared/types.js'
/** music_quiz/ 정적 템플릿 디렉터리. (songs.mcfunction 만 동적으로 생성) */
const TEMPLATE_DIR = path.join(fileDatapacksDirPath, 'music_quiz_template')
const SONGS_PATH_IN_ZIP = 'music_quiz/data/mq/function/init/songs.mcfunction'
/** SNBT 문자열 리터럴 안에 들어갈 문자열을 escape. */
function escapeSnbtString(input: string): string {
return input.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
@@ -30,7 +21,11 @@ function entrySnbt(entry: MusicListEntry): string {
return `{title:"${title}", author:"${author}", alias:${alias}}`
}
/** list.music 으로부터 `data/mq/function/init/songs.mcfunction` 본문을 생성. */
/**
* list.music 으로부터 `data/mq/function/init/songs.mcfunction` 본문을 생성.
* 운영자는 mc_datapack 의 music_quiz 데이터팩에서 이 파일만 이 내용으로
* 덮어쓰면 된다 — 나머지 파일은 launcher 가 관여하지 않는다.
*/
export function buildSongsMcfunction(list: PackList): string {
const lines: string[] = []
lines.push('# 곡 한 개 = 한 줄.')
@@ -48,32 +43,3 @@ export function buildSongsMcfunction(list: PackList): string {
lines.push('execute store result storage mq:main max_index int 1 run data get storage mq:main songs')
return lines.join('\n') + '\n'
}
/** music_quiz 데이터팩 zip 을 Response 로 스트리밍. */
export function streamMusicQuizZip(res: Response, packKey: string, list: PackList): void {
const fileName = `music_quiz_${packKey}.zip`
res.setHeader('Content-Type', 'application/zip')
res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`)
const archive = archiver('zip', { zlib: { level: 9 } })
archive.on('warning', (err) => {
if (err.code !== 'ENOENT') res.destroy(err)
})
archive.on('error', (err) => {
res.destroy(err)
})
archive.pipe(res)
// 정적 템플릿 전체를 music_quiz/ 아래로 묶되 songs.mcfunction 만 제외.
archive.glob('**/*', {
cwd: TEMPLATE_DIR,
dot: false,
ignore: ['data/mq/function/init/songs.mcfunction']
}, { prefix: 'music_quiz/' })
// 동적으로 만든 songs.mcfunction 을 추가.
const songsText = buildSongsMcfunction(list)
archive.append(Readable.from([songsText]), { name: SONGS_PATH_IN_ZIP })
void archive.finalize()
}