import { createWriteStream, existsSync, mkdirSync, readFileSync, rmSync, type WriteStream } from 'node:fs' import { readFile } from 'node:fs/promises' import { dirname, join, relative, resolve, sep } from 'node:path' export function projectDir(projectId: string) { return join(dataDir(), 'projects', projectId) } /** 章节文件相对 data 目录的路径,例如 projects/xxx/chapters/0003-v2.md */ export function chapterFileRel(projectId: string, index: number, version: number) { return ['projects', projectId, 'chapters', `${String(index).padStart(4, '0')}-v${version}.md`].join('/') } export function absPath(rel: string) { const root = dataDir() const p = resolve(root, rel) if (p !== root && !p.startsWith(root + sep)) throw new Error('非法路径') return p } export function openChapterWriter(rel: string): WriteStream { const p = absPath(rel) mkdirSync(dirname(p), { recursive: true }) return createWriteStream(p, { encoding: 'utf8' }) } export async function readChapterFile(rel: string) { const p = absPath(rel) if (!existsSync(p)) return '' return readFile(p, 'utf8') } export function readChapterFileSync(rel: string) { const p = absPath(rel) return existsSync(p) ? readFileSync(p, 'utf8') : '' } export function removeProjectFiles(projectId: string) { const dir = projectDir(projectId) if (existsSync(dir)) rmSync(dir, { recursive: true, force: true }) } export function relToData(abs: string) { return relative(dataDir(), abs).split(sep).join('/') } /** 统计字数:中日韩字符按 1 字,英文按单词计 */ export function countWords(text: string) { const cjk = text.match(/[㐀-鿿豈-﫿぀-ヿ가-힯]/g)?.length ?? 0 const latin = text.replace(/[㐀-鿿豈-﫿぀-ヿ가-힯]/g, ' ').match(/[A-Za-z0-9]+(?:['’-][A-Za-z0-9]+)*/g)?.length ?? 0 return cjk + latin }