// src/main/services/memo/memo-export-file-name.ts // 메모 내보내기 파일명 정책 (순수 함수). 태그는 자유 텍스트이고 폰 동기화로도 들어오므로 // 경로 구분자·NTFS 예약 문자·제어 문자를 그대로 파일명에 넣으면 하위 폴더 ENOENT, // NTFS 대체 데이터 스트림(':'), exports 폴더 이탈('..')이 생긴다. 여기서 한 번에 막는다. /** 파일명에 들어갈 태그 조각의 최대 길이(코드 포인트 기준). */ export const MEMO_EXPORT_TAG_MAX_LENGTH = 64 // Windows 예약 문자 + 경로 구분자 + C0/DEL 제어 문자 // eslint-disable-next-line no-control-regex const UNSAFE_FILE_NAME_CHARS = /[\\/:*?"<>|\u0000-\u001f\u007f]/g /** * 태그를 단일 파일명 세그먼트로 안전하게 바꾼다. * 결과가 비면 빈 문자열을 돌려주고, 호출자는 태그 접미사를 생략한다. */ export function sanitizeMemoExportTag(tag: string): string { const replaced = tag .normalize('NFC') .replace(UNSAFE_FILE_NAME_CHARS, '_') .replace(/\s+/g, '_') .replace(/\.{2,}/g, '.') .replace(/_+/g, '_') .replace(/^[._]+|[._]+$/g, '') const capped = Array.from(replaced).slice(0, MEMO_EXPORT_TAG_MAX_LENGTH).join('') return capped.replace(/[._]+$/g, '') } /** `memo[_]_.md` 형식의 파일명을 만든다. */ export function buildMemoExportFileName(tag: string | undefined, timestamp: number): string { const safeTag = tag ? sanitizeMemoExportTag(tag) : '' const tagSuffix = safeTag ? `_${safeTag}` : '' return `memo${tagSuffix}_${timestamp}.md` }