import { access, readFile } from 'node:fs/promises' import path from 'node:path' import { pathToFileURL } from 'node:url' export interface EasySlidesConfig { title: string description: string slidesDir: string outDir: string exportDir: string theme?: string } const defaults: EasySlidesConfig = { title: 'easy-slides', description: 'Markdown presentations powered by easy-slides', slidesDir: 'slides', outDir: 'dist', exportDir: 'exports', } export async function loadProjectConfig(cwd = process.cwd()): Promise { const candidates = [ 'easy-slides.config.mjs', 'easy-slides.config.js', 'easy-slides.config.json', ] for (const candidate of candidates) { const filename = path.join(cwd, candidate) if (!(await exists(filename))) continue const loaded = candidate.endsWith('.json') ? JSON.parse(await readFile(filename, 'utf8')) : (await import(`${pathToFileURL(filename).href}?t=${Date.now()}`)).default return normalizeConfig(loaded) } return { ...defaults } } export function normalizeConfig(value: Partial | undefined): EasySlidesConfig { return { ...defaults, ...value, slidesDir: normalizeRelativePath(value?.slidesDir ?? defaults.slidesDir, 'slidesDir'), outDir: normalizeRelativePath(value?.outDir ?? defaults.outDir, 'outDir'), exportDir: normalizeRelativePath(value?.exportDir ?? defaults.exportDir, 'exportDir'), } } function normalizeRelativePath(value: string, field: string) { const normalized = value.trim().replace(/[\\/]+$/, '') const portable = normalized.replaceAll('\\', '/') if (!normalized || path.isAbsolute(normalized) || portable === '..' || portable.startsWith('../')) throw new Error(`${field} 必须是项目内的相对路径`) return normalized } async function exists(filename: string) { try { await access(filename) return true } catch { return false } }