feat: rename project to TTTK

- Update package name and CLI binary to 'tttk'
- Revise documentation to reflect new name
- Add default serve behavior when no subcommand is given
- Add directory existence check in serve command
- Refactor inline comments to Chinese
This commit is contained in:
hyper
2026-07-09 14:14:45 +08:00
parent c38d9bdc4f
commit 06c3916a95
8 changed files with 111 additions and 95 deletions
+28 -24
View File
@@ -22,14 +22,14 @@ interface ContentIndex {
}
/**
* 获取 CLI 脚本文件所在目录路径(用于定位 dist 文件夹)
* 获取 CLI 脚本文件所在目录路径(用于定位 dist 目录)
*/
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const distDir = resolve(__dirname, "..", "..", "..", "dist", "web");
/**
* MIME 类型映射
* MIME 类型映射表
*/
const MIME_TYPES: Record<string, string> = {
".html": "text/html",
@@ -48,7 +48,7 @@ const MIME_TYPES: Record<string, string> = {
};
/**
* 获取文件扩展名对应的 MIME 类型
* 根据文件扩展名获取对应的 MIME 类型
*/
function getMimeType(filePath: string): string {
const ext = extname(filePath).toLowerCase();
@@ -56,7 +56,7 @@ function getMimeType(filePath: string): string {
}
/**
* Get the best network IP for display (prefer LAN IPv4, fallback to localhost).
* 获取最佳网络 IP 用于显示(优先局域网 IPv4,回退到 localhost)
*/
function getBestIP(): string {
const interfaces = networkInterfaces();
@@ -85,7 +85,7 @@ function getBestIP(): string {
}
/**
* Extract the <title> from an SVG string, or null if absent.
* 从 SVG 字符串中提取 <title> 内容,无则返回 null
*/
function extractSvgTitle(svg: string): string | null {
const m = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(svg);
@@ -93,17 +93,15 @@ function extractSvgTitle(svg: string): string | null {
}
/**
* Derive a human-readable label from a filename.
* "character-sheet" -> "Character Sheet"
* 从文件名生成可读标签
* 如 "character-sheet" -> "Character Sheet"
*/
function labelFromId(id: string): string {
return id
.replace(/[-_]/g, " ")
.replace(/\b\w/g, (c) => c.toUpperCase());
return id.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
}
/**
* 扫描目录内的 .md 文件,生成索引
* 扫描目录内的 .md 等文件,生成内容索引与块数据
*/
export function scanDirectory(dir: string): {
index: ContentIndex;
@@ -170,7 +168,7 @@ export function scanDirectory(dir: string): {
}
/**
* 发送文件响应
* 发送文件响应(流式传输)
*/
function sendFile(res: ServerResponse, filePath: string) {
res.writeHead(200, {
@@ -181,7 +179,7 @@ function sendFile(res: ServerResponse, filePath: string) {
}
/**
* 发送 404 响应
* 发送 404 未找到响应
*/
function send404(res: ServerResponse) {
res.writeHead(404, { "Content-Type": "text/plain" });
@@ -189,7 +187,7 @@ function send404(res: ServerResponse) {
}
/**
* 发送 JSON 响应
* 发送 JSON 格式的响应
*/
function sendJson(res: ServerResponse, data: unknown) {
res.writeHead(200, {
@@ -201,7 +199,7 @@ function sendJson(res: ServerResponse, data: unknown) {
/**
* 尝试提供静态文件
* @returns 如果文件存在并已成功发送则返回 true
* @returns 文件存在并成功发送时返回 true
*/
function tryServeStatic(
res: ServerResponse,
@@ -224,7 +222,7 @@ function tryServeStatic(
}
/**
* 创建请求处理器
* 创建 HTTP 请求处理器
*/
function createRequestHandler(
contentDir: string,
@@ -274,7 +272,7 @@ function createRequestHandler(
}
/**
* 内容服务器接口
* 内容服务器实例接口
*/
export interface ContentServer {
/**
@@ -300,7 +298,7 @@ export interface ContentServer {
}
/**
* 创建内容服务器
* 创建内容服务器(HTTP + WebSocket + 文件监听)
*/
export function createContentServer(
contentDir: string,
@@ -324,7 +322,7 @@ export function createContentServer(
statSheets: [],
};
/** Re-scan completions from current content index and collected blocks */
/** 从当前内容索引和已收集的块重新扫描补全数据 */
function recomputeCompletions(): void {
completionsIndex = scanCompletions(contentIndex, collectedBlocks);
console.log(
@@ -333,7 +331,7 @@ export function createContentServer(
}
// 扫描内容目录生成索引
console.log("扫描内容目录...");
console.log("正在扫描内容目录...");
const scanResult = scanDirectory(contentDir);
contentIndex = scanResult.index;
collectedBlocks = scanResult.blocks;
@@ -341,7 +339,7 @@ export function createContentServer(
recomputeCompletions();
// 监听文件变化
console.log("监听文件变化...");
console.log("正在监听文件变化...");
const watcher = watch(contentDir, {
ignored: /(^|[\/\\])\./,
persistent: true,
@@ -431,7 +429,7 @@ export function createContentServer(
// ---- Journal / MQTT broker ----
let journal: Awaited<ReturnType<typeof createJournalServer>> | null = null;
// 创建 HTTP 服务器 BEFORE journal so we can pass it in
// 在 journal 之前创建 HTTP 服务器,以便传入给 journal
const handleRequest = createRequestHandler(
contentDir,
distPath,
@@ -454,7 +452,7 @@ export function createContentServer(
}
console.log(`内容目录:${contentDir}`);
console.log(`静态资源目录:${distPath}`);
console.log(`Journal 连接:ws://${bestIP}:${port}\n`);
console.log(`日志连接:ws://${bestIP}:${port}\n`);
});
return {
@@ -463,7 +461,7 @@ export function createContentServer(
index: contentIndex,
completions: completionsIndex,
close() {
console.log("关闭内容服务器...");
console.log("正在关闭内容服务器...");
server.close();
watcher.close();
if (journal) journal.close();
@@ -476,6 +474,12 @@ export function createContentServer(
*/
export const serveCommand: ServeCommandHandler = async (dir, options) => {
const contentDir = resolve(dir);
if (!existsSync(contentDir)) {
console.error(`错误:目录不存在:${contentDir}`);
process.exit(1);
}
const port = parseInt(options.port, 10);
const host = options.host || "0.0.0.0";