ttrpg-tools/src/cli/tools/preview-deck.ts

157 lines
3.6 KiB
TypeScript

import { readFileSync, writeFileSync, existsSync } from "fs";
import { relative } from "path";
import { execSync } from "child_process";
import {
buildDeckComponent,
titleFromCsvPath,
defaultMdPath,
readDeckConfig,
generateDeckPreviewMd,
} from "./deck-utils.js";
/**
* 预览 Deck 的参数
*/
export interface PreviewDeckParams {
/**
* CSV 文件路径
*/
csv_file: string;
/**
* Markdown 文件路径(可选,默认与 CSV 同名)
*/
md_file?: string;
/**
* 标题(可选,默认从 CSV 文件名推断)
*/
title?: string;
/**
* 描述(可选)
*/
description?: string;
}
/**
* 预览 Deck 的结果
*/
export interface PreviewDeckResult {
success: boolean;
message: string;
md_file?: string;
created?: boolean;
preview_url?: string;
}
/**
* 打开浏览器
*/
function openBrowser(url: string) {
try {
if (process.platform === "win32") {
execSync(`start "" "${url}"`, { stdio: "ignore" });
} else if (process.platform === "darwin") {
execSync(`open "${url}"`, { stdio: "ignore" });
} else {
execSync(`xdg-open "${url}"`, { stdio: "ignore" });
}
console.error(`已打开浏览器:${url}`);
} catch (error) {
console.error("打开浏览器失败:", error);
}
}
/**
* 计算预览 URL
*/
function computePreviewUrl(mdFilePath: string): string {
const relativeMdPath = relative(process.cwd(), mdFilePath)
.split("\\")
.join("/")
.replace(/\.md$/, "");
return `http://localhost:3000/${relativeMdPath}`;
}
/**
* 预览 Deck - 保存 Markdown 并打开浏览器
*/
export function previewDeck(params: PreviewDeckParams): PreviewDeckResult {
const { csv_file, md_file, title, description } = params;
// 检查 CSV 文件是否存在
if (!existsSync(csv_file)) {
return {
success: false,
message: `CSV 文件不存在:${csv_file}`,
};
}
// 确定 Markdown 文件路径
const mdFilePath = md_file || defaultMdPath(csv_file);
const created = !existsSync(mdFilePath);
// 如果文件已存在,检查是否已有 md-deck 组件
if (!created) {
try {
const existingContent = readFileSync(mdFilePath, "utf-8");
if (
existingContent.includes(`:md-deck[${csv_file}]`) ||
existingContent.includes(`:md-deck[./${csv_file.split("/").pop()}]`)
) {
const previewUrl = computePreviewUrl(mdFilePath);
openBrowser(previewUrl);
return {
success: true,
message: `预览文件 ${mdFilePath} 已存在`,
md_file: mdFilePath,
created: false,
preview_url: previewUrl,
};
}
} catch {
// 读取失败,继续创建
}
}
// 读取 CSV 的 frontmatter 获取配置
const deckConfig = readDeckConfig(csv_file);
// 确定标题
const deckTitle = title || titleFromCsvPath(csv_file);
// 构建 md-deck 组件代码
const deckComponent = buildDeckComponent(
`./${csv_file.split("/").pop()}`,
deckConfig,
);
// 生成 Markdown 内容
const mdContent = generateDeckPreviewMd(
deckTitle,
deckComponent,
description,
);
// 写入文件
try {
writeFileSync(mdFilePath, mdContent, "utf-8");
const previewUrl = computePreviewUrl(mdFilePath);
openBrowser(previewUrl);
return {
success: true,
message: created
? `创建预览文件 ${mdFilePath}`
: `更新预览文件 ${mdFilePath}`,
md_file: mdFilePath,
created,
preview_url: previewUrl,
};
} catch (error) {
return {
success: false,
message: `写入失败:${error instanceof Error ? error.message : "未知错误"}`,
};
}
}