diff --git a/src/cli/commands/serve.ts b/src/cli/commands/serve.ts index 5d7c716..e4718ff 100644 --- a/src/cli/commands/serve.ts +++ b/src/cli/commands/serve.ts @@ -13,6 +13,7 @@ import { deriveCompletions, type ContentRegistry, } from "../content-registry.js"; +import { readContentIndex } from "../content-index.js"; import type { CompletionsPayload } from "../completions/types.js"; interface ContentIndex { @@ -86,39 +87,7 @@ function getBestIP(): string { * 扫描目录内的 .md 等文件,构建内容注册表(路径索引 + 每文档内联内容) */ export function buildRegistry(dir: string): ContentRegistry { - const index: Record = {}; - - function scan(currentPath: string, relativePath: string) { - const entries = readdirSync(currentPath); - - for (const entry of entries) { - if (entry.startsWith(".") || entry === "node_modules") continue; - - const fullPath = join(currentPath, entry); - const relPath = relativePath ? join(relativePath, entry) : entry; - const normalizedRelPath = normalizePathKey(relPath); - - const stats = statSync(fullPath); - if (stats.isDirectory()) { - scan(fullPath, relPath); - } else if ( - entry.endsWith(".md") || - entry.endsWith(".csv") || - entry.endsWith(".yarn") || - entry.endsWith(".svg") - ) { - try { - index[normalizedRelPath] = readFileSync(fullPath, "utf-8"); - } catch (e) { - console.error(`读取文件失败:${fullPath}`, e); - } - } - } - } - - scan(dir, ""); - - return buildRegistryFromIndex(index); + return buildRegistryFromIndex(readContentIndex(dir)); } /** diff --git a/src/cli/content-index.ts b/src/cli/content-index.ts new file mode 100644 index 0000000..3c12a44 --- /dev/null +++ b/src/cli/content-index.ts @@ -0,0 +1,52 @@ +/** + * Filesystem walk that builds a raw `{ path: content }` index. + * + * Extracted from `buildRegistry` in `commands/serve.ts` so the validator can + * obtain the *raw* file contents (needed for accurate line numbers) while the + * server gets the scanned registry — both from the same walk. + */ + +import { readdirSync, statSync, readFileSync } from "fs"; +import { join } from "path"; +import { normalizePathKey } from "./content-registry.js"; + +export interface ContentIndex { + [path: string]: string; +} + +/** Recursively read indexable files (`.md`, `.csv`, `.yarn`, `.svg`). */ +export function readContentIndex(dir: string): ContentIndex { + const index: ContentIndex = {}; + + function scan(currentPath: string, relativePath: string) { + const entries = readdirSync(currentPath); + + for (const entry of entries) { + if (entry.startsWith(".") || entry === "node_modules") continue; + + const fullPath = join(currentPath, entry); + const relPath = relativePath ? join(relativePath, entry) : entry; + const normalizedRelPath = normalizePathKey(relPath); + + const stats = statSync(fullPath); + if (stats.isDirectory()) { + scan(fullPath, relPath); + } else if ( + entry.endsWith(".md") || + entry.endsWith(".csv") || + entry.endsWith(".yarn") || + entry.endsWith(".svg") + ) { + try { + index[normalizedRelPath] = readFileSync(fullPath, "utf-8"); + } catch (e) { + console.error(`读取文件失败:${fullPath}`, e); + } + } + } + } + + scan(dir, ""); + + return index; +}