import path from 'node:path' import os from 'node:os' // 컴파일 후 dist/shared/paths.js → 2단계 상위가 프로젝트 루트. export const projectRoot = path.resolve(__dirname, '..', '..') export const manifestRootPath = path.join(projectRoot, 'manifest.json') export const manifestDirPath = path.join(projectRoot, 'manifest') export const manifestTermsDirPath = path.join(manifestDirPath, 'terms') // 추적되는 account.json(과거 평문 노출)을 대체할, gitignore 된 운영 계정 파일. // readAccounts 는 이 파일을 우선 사용하고, 없을 때만 account.json 을 시드로 읽는다. // // TODO(untrack-after-redeploy): account.json 은 아직 git 추적 상태다. 절대 지금 // 같은 커밋에서 `git rm --cached account.json` 하지 말 것 — 서버에 account.local.json // 이 아직 없을 때 시드 소스가 사라져 로그인이 막힌다(chicken-and-egg). // 안전한 순서: (1) 이 커밋 배포 → 서버가 account.local.json(0o600) 자동 생성 확인 → // (2) 그 다음 후속 커밋에서 account.json 추적 해제. // 주의: 추적 해제는 위생일 뿐, 히스토리의 평문 비밀번호는 지워지지 않는다 → 비밀번호 로테이션이 실질 조치. export const accountFilePath = path.join(projectRoot, 'account.json') export const accountLocalFilePath = path.join(projectRoot, 'account.local.json') export const fileDirPath = path.join(projectRoot, 'file') export const fileListDirPath = path.join(fileDirPath, 'list') export const fileDatapacksDirPath = path.join(fileDirPath, 'datapacks') export const viewsDirPath = path.join(projectRoot, 'views') export const publicDirPath = path.join(projectRoot, 'public') /** * 사용자 환경의 "%appdata%" 디렉터리(OS별 표준 사용자 데이터 경로)를 반환. * - Windows : %APPDATA% (보통 C:\Users\\AppData\Roaming) * - macOS : ~/Library/Application Support * - Linux 등 : $XDG_CONFIG_HOME 또는 ~/.config */ export function getAppDataDir(): string { if (process.platform === 'win32') { return process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming') } if (process.platform === 'darwin') { return path.join(os.homedir(), 'Library', 'Application Support') } return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config') } /** * 커스텀 게임 디렉터리의 폴더 이름. 기본은 `.mc_custom` 이지만 환경변수 * `MC_CUSTOM_DIR` 로 다른 이름을 지정할 수 있다(.env / .env.build 로 주입). * 경로 구분자·상위경로 이스케이프(`/`, `\`, `..`)는 제거해 항상 %appdata% * 바로 아래 단일 폴더로 강제한다. */ export function getMcCustomDirName(): string { const raw = (process.env.MC_CUSTOM_DIR ?? '').trim() if (!raw) return '.mc_custom' const sanitized = raw.replace(/[\\/]+/g, '').replace(/\.\.+/g, '.') return sanitized || '.mc_custom' } /** %appdata%/ — 음악퀴즈 관련 게임 폴더/외부 도구/캐시 보관 디렉터리. */ export function getMcCustomDir(): string { return path.join(getAppDataDir(), getMcCustomDirName()) } /** * 사전/문자열 구조 안의 리터럴 `.mc_custom` 을 실제 폴더 이름으로 치환한 깊은 * 복사본을 돌려준다. 기본값(`.mc_custom`)이면 원본을 그대로 반환한다. 렌더러로 * 넘기는 i18n 사전에 적용해 UI 안내 문구가 실제 폴더 이름과 어긋나지 않게 한다. */ export function withCustomDirName(value: T): T { const name = getMcCustomDirName() if (name === '.mc_custom') return value const replace = (v: unknown): unknown => { if (typeof v === 'string') return v.split('.mc_custom').join(name) if (Array.isArray(v)) return v.map(replace) if (v && typeof v === 'object') { const out: Record = {} for (const [k, val] of Object.entries(v as Record)) out[k] = replace(val) return out } return v } return replace(value) as T } /** * %appdata%/.mc_custom/installer — 설치기가 자체적으로 다운로드해 사용하는 * 외부 바이너리(yt-dlp.exe, ffmpeg.exe 등) 보관 위치. .mc_custom 루트가 * 마인크래프트 게임 폴더(`mods/`, `resourcepacks/`, `saves/` 등)와 섞이지 * 않도록 별도 하위 폴더에 둔다. */ export function getMcCustomInstallerDir(): string { return path.join(getMcCustomDir(), 'installer') }