diff --git a/src/cli/commands/serve.ts b/src/cli/commands/serve.ts index 99f0af4..01489b5 100644 --- a/src/cli/commands/serve.ts +++ b/src/cli/commands/serve.ts @@ -1,9 +1,9 @@ -import type { ServeCommandHandler } from '../types.js'; -import { createServer, Server, IncomingMessage, ServerResponse } from 'http'; -import { readdirSync, statSync, readFileSync, existsSync } from 'fs'; -import { createReadStream } from 'fs'; -import { join, resolve, extname, sep, relative } from 'path'; -import { watch } from 'chokidar'; +import type { ServeCommandHandler } from "../types.js"; +import { createServer, Server, IncomingMessage, ServerResponse } from "http"; +import { readdirSync, statSync, readFileSync, existsSync } from "fs"; +import { createReadStream } from "fs"; +import { join, resolve, extname, sep, relative } from "path"; +import { watch } from "chokidar"; interface ContentIndex { [path: string]: string; @@ -13,19 +13,19 @@ interface ContentIndex { * MIME 类型映射 */ const MIME_TYPES: Record = { - '.html': 'text/html', - '.css': 'text/css', - '.js': 'application/javascript', - '.json': 'application/json', - '.png': 'image/png', - '.jpg': 'image/jpeg', - '.jpeg': 'image/jpeg', - '.gif': 'image/gif', - '.svg': 'image/svg+xml', - '.md': 'text/markdown', - '.csv': 'text/csv', - '.woff': 'font/woff', - '.woff2': 'font/woff2', + ".html": "text/html", + ".css": "text/css", + ".js": "application/javascript", + ".json": "application/json", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".gif": "image/gif", + ".svg": "image/svg+xml", + ".md": "text/markdown", + ".csv": "text/csv", + ".woff": "font/woff", + ".woff2": "font/woff2", }; /** @@ -33,11 +33,11 @@ const MIME_TYPES: Record = { */ function getMimeType(filePath: string): string { const ext = extname(filePath).toLowerCase(); - return MIME_TYPES[ext] || 'application/octet-stream'; + return MIME_TYPES[ext] || "application/octet-stream"; } /** - * 扫描目录内的 .md 和 .csv 文件,生成索引 + * 扫描目录内的 .md 文件,生成索引 */ function scanDirectory(dir: string): ContentIndex { const index: ContentIndex = {}; @@ -46,18 +46,18 @@ function scanDirectory(dir: string): ContentIndex { const entries = readdirSync(currentPath); for (const entry of entries) { - if (entry.startsWith('.') || entry === 'node_modules') continue; + if (entry.startsWith(".") || entry === "node_modules") continue; const fullPath = join(currentPath, entry); const relPath = relativePath ? join(relativePath, entry) : entry; - const normalizedRelPath = '/' + relPath.split(sep).join('/'); + const normalizedRelPath = "/" + relPath.split(sep).join("/"); const stats = statSync(fullPath); if (stats.isDirectory()) { scan(fullPath, relPath); - } else if (entry.endsWith('.md') || entry.endsWith('.csv')) { + } else if (entry.endsWith(".md")) { try { - const content = readFileSync(fullPath, 'utf-8'); + const content = readFileSync(fullPath, "utf-8"); index[normalizedRelPath] = content; } catch (e) { console.error(`读取文件失败:${fullPath}`, e); @@ -66,7 +66,7 @@ function scanDirectory(dir: string): ContentIndex { } } - scan(dir, ''); + scan(dir, ""); return index; } @@ -75,8 +75,8 @@ function scanDirectory(dir: string): ContentIndex { */ function sendFile(res: ServerResponse, filePath: string) { res.writeHead(200, { - 'Content-Type': getMimeType(filePath), - 'Access-Control-Allow-Origin': '*', + "Content-Type": getMimeType(filePath), + "Access-Control-Allow-Origin": "*", }); createReadStream(filePath).pipe(res); } @@ -85,8 +85,8 @@ function sendFile(res: ServerResponse, filePath: string) { * 发送 404 响应 */ function send404(res: ServerResponse) { - res.writeHead(404, { 'Content-Type': 'text/plain' }); - res.end('Not Found'); + res.writeHead(404, { "Content-Type": "text/plain" }); + res.end("Not Found"); } /** @@ -94,8 +94,8 @@ function send404(res: ServerResponse) { */ function sendJson(res: ServerResponse, data: unknown) { res.writeHead(200, { - 'Content-Type': 'application/json', - 'Access-Control-Allow-Origin': '*', + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*", }); res.end(JSON.stringify(data, null, 2)); } @@ -104,9 +104,13 @@ function sendJson(res: ServerResponse, data: unknown) { * 尝试提供静态文件 * @returns 如果文件存在并已成功发送则返回 true */ -function tryServeStatic(res: ServerResponse, filePath: string, dir: string): boolean { +function tryServeStatic( + res: ServerResponse, + filePath: string, + dir: string, +): boolean { const fullPath = join(dir, filePath); - + if (!existsSync(fullPath)) { return false; } @@ -129,17 +133,17 @@ function createRequestHandler( getIndex: () => ContentIndex, ) { return (req: IncomingMessage, res: ServerResponse) => { - const url = req.url || '/'; - const filePath = decodeURIComponent(url.split('?')[0]); + const url = req.url || "/"; + const filePath = decodeURIComponent(url.split("?")[0]); // 1. 处理 /__CONTENT_INDEX.json - if (filePath === '/__CONTENT_INDEX.json') { + if (filePath === "/__CONTENT_INDEX.json") { sendJson(res, getIndex()); return; } // 2. 处理 /static/ 目录(从 dist/web) - if (filePath.startsWith('/static/')) { + if (filePath.startsWith("/static/")) { if (tryServeStatic(res, filePath, distDir)) { return; } @@ -154,7 +158,7 @@ function createRequestHandler( } // 4. 处理 SPA 路由:返回 index.html - if (tryServeStatic(res, 'index.html', distDir)) { + if (tryServeStatic(res, "index.html", distDir)) { return; } @@ -168,16 +172,16 @@ function createRequestHandler( */ export const serveCommand: ServeCommandHandler = async (dir, options) => { const contentDir = resolve(dir); - const distDir = resolve(process.cwd(), 'dist/web'); + const distDir = resolve(process.cwd(), "dist/web"); let contentIndex: ContentIndex = {}; // 扫描内容目录生成索引 - console.log('扫描内容目录...'); + console.log("扫描内容目录..."); contentIndex = scanDirectory(contentDir); console.log(`已索引 ${Object.keys(contentIndex).length} 个文件`); // 监听文件变化 - console.log('监听文件变化...'); + console.log("监听文件变化..."); const watcher = watch(contentDir, { ignored: /(^|[\/\\])\../, persistent: true, @@ -185,11 +189,11 @@ export const serveCommand: ServeCommandHandler = async (dir, options) => { }); watcher - .on('add', (path) => { - if (path.endsWith('.md') || path.endsWith('.csv')) { + .on("add", (path) => { + if (path.endsWith(".md")) { try { - const content = readFileSync(path, 'utf-8'); - const relPath = '/' + relative(contentDir, path).split(sep).join('/'); + const content = readFileSync(path, "utf-8"); + const relPath = "/" + relative(contentDir, path).split(sep).join("/"); contentIndex[relPath] = content; console.log(`[新增] ${relPath}`); } catch (e) { @@ -197,11 +201,11 @@ export const serveCommand: ServeCommandHandler = async (dir, options) => { } } }) - .on('change', (path) => { - if (path.endsWith('.md') || path.endsWith('.csv')) { + .on("change", (path) => { + if (path.endsWith(".md")) { try { - const content = readFileSync(path, 'utf-8'); - const relPath = '/' + relative(contentDir, path).split(sep).join('/'); + const content = readFileSync(path, "utf-8"); + const relPath = "/" + relative(contentDir, path).split(sep).join("/"); contentIndex[relPath] = content; console.log(`[更新] ${relPath}`); } catch (e) { @@ -209,9 +213,9 @@ export const serveCommand: ServeCommandHandler = async (dir, options) => { } } }) - .on('unlink', (path) => { - if (path.endsWith('.md') || path.endsWith('.csv')) { - const relPath = '/' + relative(contentDir, path).split(sep).join('/'); + .on("unlink", (path) => { + if (path.endsWith(".md")) { + const relPath = "/" + relative(contentDir, path).split(sep).join("/"); delete contentIndex[relPath]; console.log(`[删除] ${relPath}`); } diff --git a/src/data-loader/index.ts b/src/data-loader/index.ts index 2c3e660..5e449a7 100644 --- a/src/data-loader/index.ts +++ b/src/data-loader/index.ts @@ -23,7 +23,7 @@ async function loadDevIndex(): Promise { if (typeof import.meta !== "undefined" && import.meta.glob) { try { // @ts-ignore - 只加载 .csv 和 .md 文件 - const modules = import.meta.glob("../../content/**/*.{csv,md}", { + const modules = import.meta.glob("../../content/**/*.md", { as: "raw", eager: true, });