import { readdir } from "node:fs/promises"; import { getBuiltinTemplatesDir, getUserTemplatesDir, isValidTemplateName, } from "./load-template.ts"; const TEMPLATE_FILE_EXTENSION = ".md"; export interface TemplateInfo { name: string; /** 模板来源:用户模板目录或包内内置目录。 */ source: "user" | "builtin"; } /** 读取目录中的合法模板名,目录不存在时返回空列表。 */ async function readTemplateNames(dir: string): Promise { let entries; try { entries = await readdir(dir, { withFileTypes: true }); } catch (error) { if (error instanceof Error && (error as NodeJS.ErrnoException).code === "ENOENT") { return []; } const detail = error instanceof Error ? error.message : String(error); throw new Error(`Failed to read templates directory ${dir}: ${detail}`); } return entries .filter((entry) => entry.isFile() && entry.name.endsWith(TEMPLATE_FILE_EXTENSION)) .map((entry) => entry.name.slice(0, -TEMPLATE_FILE_EXTENSION.length)) .filter((name) => isValidTemplateName(name)); } /** 列出全部可用模板;同名时用户模板覆盖内置模板,用户模板排在内置模板之前,组内按名称排序。 */ export async function listTemplates(): Promise { const [userNames, builtinNames] = await Promise.all([ readTemplateNames(getUserTemplatesDir()), readTemplateNames(getBuiltinTemplatesDir()), ]); const templates = new Map(); for (const name of builtinNames) { templates.set(name, { name, source: "builtin" }); } for (const name of userNames) { templates.set(name, { name, source: "user" }); } return [...templates.values()].sort((a, b) => { if (a.source !== b.source) { return a.source === "user" ? -1 : 1; } return a.name.localeCompare(b.name); }); }