JimLiu-baoyu-skills/skills/baoyu-post-to-weibo/scripts/md-to-html.ts

171 lines
4.8 KiB
TypeScript

import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import process from "node:process";
import {
extractSummaryFromBody,
extractTitleFromMarkdown,
parseFrontmatter,
pickFirstString,
renderMarkdownDocument,
replaceMarkdownImagesWithPlaceholders,
resolveColorToken,
resolveContentImages,
resolveImagePath,
serializeFrontmatter,
stripWrappingQuotes,
} from "baoyu-md";
interface ImageInfo {
placeholder: string;
localPath: string;
originalPath: string;
alt?: string;
}
interface ParsedMarkdown {
title: string;
summary: string;
shortSummary: string;
coverImage: string | null;
contentImages: ImageInfo[];
html: string;
}
export async function parseMarkdown(
markdownPath: string,
options?: {
coverImage?: string;
title?: string;
tempDir?: string;
theme?: string;
color?: string;
citeStatus?: boolean;
},
): Promise<ParsedMarkdown> {
const content = fs.readFileSync(markdownPath, "utf-8");
const baseDir = path.dirname(markdownPath);
const tempDir = options?.tempDir ?? fs.mkdtempSync(path.join(os.tmpdir(), "weibo-article-images-"));
const { frontmatter, body } = parseFrontmatter(content);
let title = stripWrappingQuotes(options?.title ?? "")
|| stripWrappingQuotes(frontmatter.title ?? "")
|| extractTitleFromMarkdown(body);
if (!title) {
title = path.basename(markdownPath, path.extname(markdownPath));
}
let summary = stripWrappingQuotes(frontmatter.summary ?? "")
|| stripWrappingQuotes(frontmatter.description ?? "")
|| stripWrappingQuotes(frontmatter.excerpt ?? "");
if (!summary) {
summary = extractSummaryFromBody(body, 44);
}
const shortSummary = extractSummaryFromBody(body, 44);
const coverImagePath = stripWrappingQuotes(options?.coverImage ?? "")
|| pickFirstString(frontmatter, ["featureImage", "cover_image", "coverImage", "cover", "image"])
|| null;
const { images, markdown: rewrittenBody } = replaceMarkdownImagesWithPlaceholders(
body,
"WBIMGPH_",
);
const rewrittenMarkdown = `${serializeFrontmatter(frontmatter)}${rewrittenBody}`;
const { html } = await renderMarkdownDocument(rewrittenMarkdown, {
citeStatus: options?.citeStatus ?? false,
defaultTitle: title,
keepTitle: false,
primaryColor: resolveColorToken(options?.color),
theme: options?.theme,
});
const contentImages = await resolveContentImages(images, baseDir, tempDir, "md-to-html");
let resolvedCoverImage: string | null = null;
if (coverImagePath) {
resolvedCoverImage = await resolveImagePath(coverImagePath, baseDir, tempDir, "md-to-html");
}
return {
title,
summary,
shortSummary,
coverImage: resolvedCoverImage,
contentImages,
html,
};
}
async function main(): Promise<void> {
const args = process.argv.slice(2);
if (args.length === 0 || args.includes("--help") || args.includes("-h")) {
console.log(`Convert Markdown to HTML for Weibo article publishing
Usage:
npx -y bun md-to-html.ts <markdown_file> [options]
Options:
--title <title> Override title
--cover <image> Override cover image
--output <json|html> Output format (default: json)
--html-only Output only the HTML content
--save-html <path> Save HTML to file
--help Show this help
`);
process.exit(0);
}
let markdownPath: string | undefined;
let title: string | undefined;
let coverImage: string | undefined;
let outputFormat: "json" | "html" = "json";
let htmlOnly = false;
let saveHtmlPath: string | undefined;
for (let i = 0; i < args.length; i++) {
const arg = args[i]!;
if (arg === "--title" && args[i + 1]) {
title = args[++i];
} else if (arg === "--cover" && args[i + 1]) {
coverImage = args[++i];
} else if (arg === "--output" && args[i + 1]) {
outputFormat = args[++i] as "json" | "html";
} else if (arg === "--html-only") {
htmlOnly = true;
} else if (arg === "--save-html" && args[i + 1]) {
saveHtmlPath = args[++i];
} else if (!arg.startsWith("-")) {
markdownPath = arg;
}
}
if (!markdownPath || !fs.existsSync(markdownPath)) {
console.error("Error: Valid markdown file path required");
process.exit(1);
}
const result = await parseMarkdown(markdownPath, { title, coverImage });
if (saveHtmlPath) {
fs.writeFileSync(saveHtmlPath, result.html, "utf-8");
console.error(`[md-to-html] HTML saved to: ${saveHtmlPath}`);
}
if (htmlOnly || outputFormat === "html") {
console.log(result.html);
} else {
console.log(JSON.stringify(result, null, 2));
}
}
if (import.meta.main ?? (process.argv[1] && path.resolve(process.argv[1]) === path.resolve(import.meta.filename ?? ""))) {
await main().catch((error) => {
console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
process.exit(1);
});
}