import { readdirSync, readFileSync, existsSync } from "node:fs"; import { join } from "node:path"; import { PACKAGE_ROOT } from "../../../util"; import { type CommandResult, success, silentFailure } from "../../lib/command-result"; import { MODULE_PATHS } from "../../lib/paths"; const MODULES_DIR = join(PACKAGE_ROOT, "src", "modules"); const DOC_TYPE_MAP: Record = { command: MODULE_PATHS.docs.command, query: MODULE_PATHS.docs.query, model: MODULE_PATHS.docs.model, feature: MODULE_PATHS.docs.feature, }; export interface SearchResult { module: string; type: string; name: string; line: number; excerpt: string; } function searchFile( filePath: string, query: string, module: string, type: string, name: string, ): SearchResult[] { if (!existsSync(filePath)) return []; const content = readFileSync(filePath, "utf-8"); const lowerQuery = query.toLowerCase(); const results: SearchResult[] = []; const lines = content.split("\n"); for (let i = 0; i < lines.length; i++) { if (lines[i].toLowerCase().includes(lowerQuery)) { results.push({ module, type, name, line: i + 1, excerpt: lines[i].trim(), }); } } return results; } export function searchModuleDocs(query: string, typeFilter?: string): SearchResult[] { if (!existsSync(MODULES_DIR)) return []; const results: SearchResult[] = []; const modules = readdirSync(MODULES_DIR, { withFileTypes: true }) .filter((d) => d.isDirectory()) .sort((a, b) => a.name.localeCompare(b.name)); for (const mod of modules) { const modDir = join(MODULES_DIR, mod.name); // Search README if (!typeFilter || typeFilter === "readme") { results.push(...searchFile(join(modDir, "README.md"), query, mod.name, "readme", "README")); } // Search doc type directories for (const [type, subdir] of Object.entries(DOC_TYPE_MAP)) { if (typeFilter && typeFilter !== type) continue; const docsDir = join(modDir, subdir); if (!existsSync(docsDir)) continue; const files = readdirSync(docsDir).filter((f) => f.endsWith(".md")); for (const file of files) { const name = file.replace(/\.md$/, ""); results.push(...searchFile(join(docsDir, file), query, mod.name, type, name)); } } } return results; } export function formatSearchResults(results: SearchResult[]): string { if (results.length === 0) return "No results found."; const lines: string[] = []; for (const r of results) { lines.push(`${r.module}/${r.type}/${r.name}:${r.line}: ${r.excerpt}`); } lines.push(""); lines.push(`${results.length} matches`); return lines.join("\n"); } export function runDocSearch(query: string, type?: string, format = "text"): CommandResult { if (type && type !== "readme" && !(type in DOC_TYPE_MAP)) { console.error(`Unknown doc type: ${type}`); console.error(`Available types: readme, ${Object.keys(DOC_TYPE_MAP).join(", ")}`); return silentFailure(); } const results = searchModuleDocs(query, type); if (format === "json") { console.log(JSON.stringify(results, null, 2)); } else { console.log(formatSearchResults(results)); } return success(); }