func2md 0.0.4 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -65,10 +65,14 @@ npx func2md --src-dir=lib --out-dir=documentation
65
65
  在代码中直接调用:
66
66
 
67
67
  ```typescript
68
- import { scanAndGenerateDocs } from 'func2md'
68
+ import { scanAndGenerateDocs, getRecords } from 'func2md'
69
69
 
70
70
  // 生成文档
71
71
  await scanAndGenerateDocs('./src', './docs')
72
+
73
+ // 读取生成的 _pages.js 记录
74
+ const pages = getRecords({ outDir: './docs' })
75
+ const mathGroup = getRecords({ outDir: './docs', text: '数学' })
72
76
  ```
73
77
 
74
78
  ## 配置选项
@@ -81,12 +85,14 @@ await scanAndGenerateDocs('./src', './docs')
81
85
 
82
86
  ## JSDoc 格式
83
87
 
84
- 插件支持以下 JSDoc 标签:
88
+ 插件支持以下 JSDoc 标签与行为:
85
89
 
86
- - `@title` - 函数标题(可选,默认为描述第一行)
87
- - `@param` - 函数参数,包含类型和描述
88
- - `@returns` `@return` - 返回值,包含类型和描述
89
- - `@example` - 代码示例
90
+ - `@title`:生成文档的主标题(H1)。未填写时使用注释第一行作为标题。
91
+ - `@MenuTitle`:生成到 _pages.js 的菜单标题,用于侧边栏条目显示。
92
+ - `@param {type} name - desc`:生成「参数」表格行。
93
+ - `@returns` / `@return`:生成「返回值」区块。
94
+ - `@example`:生成「示例」代码块。
95
+ - 注释中位于第一个标签(@param/@returns/@example)之前的文本,会作为「说明」内容。
90
96
 
91
97
  示例:
92
98
 
@@ -94,6 +100,7 @@ await scanAndGenerateDocs('./src', './docs')
94
100
  /**
95
101
  * @title 两数相加
96
102
  * 将两个数字相加
103
+ * @MenuTitle 数学/加法
97
104
  * @param {number} a - 第一个数字
98
105
  * @param {number} b - 第二个数字
99
106
  * @returns {number} 两数之和
@@ -133,6 +140,8 @@ function add(a, b) {
133
140
  ```ts
134
141
  // 示例代码
135
142
  ```
143
+
144
+ 生成的 _pages.js 仅导出 pages 数组,便于 VitePress 直接引入。需要筛选记录时请使用库导出的 `getRecords()`。
136
145
  ```
137
146
 
138
147
  ## 开发
package/README.zh-CN.md CHANGED
@@ -36,6 +36,19 @@ export default defineConfig({
36
36
  })
37
37
  ```
38
38
 
39
+ ### JavaScript API
40
+
41
+ ```ts
42
+ import { scanAndGenerateDocs, getRecords } from 'func2md'
43
+
44
+ // 生成文档
45
+ await scanAndGenerateDocs('./src', './docs')
46
+
47
+ // 读取生成的 _pages.js 记录
48
+ const pages = getRecords({ outDir: './docs' })
49
+ const mathGroup = getRecords({ outDir: './docs', text: '数学' })
50
+ ```
51
+
39
52
  ### 配置选项
40
53
 
41
54
  | 选项 | 类型 | 默认值 | 说明 |
@@ -46,12 +59,14 @@ export default defineConfig({
46
59
 
47
60
  ## JSDoc 格式
48
61
 
49
- 插件解析具有以下特殊标签的 JSDoc 注释:
62
+ 插件解析具有以下特殊标签的 JSDoc 注释,并按如下规则生成文档:
50
63
 
51
- - `@title` - 函数标题(可选,默认为描述的第一行)
52
- - `@param` - 带类型和描述的函数参数
53
- - `@returns` `@return` - 带类型和描述的返回值
54
- - `@example` - 代码示例
64
+ - `@title`:生成文档主标题(H1),缺省时使用注释第一行。
65
+ - `@MenuTitle`:生成到 _pages.js 的菜单标题,用于侧边栏条目显示。
66
+ - `@param {type} name - desc`:生成参数表格行。
67
+ - `@returns` / `@return`:生成返回值说明。
68
+ - `@example`:生成示例代码块。
69
+ - 标签之前的文本会作为「说明」内容。
55
70
 
56
71
  示例:
57
72
 
@@ -59,6 +74,7 @@ export default defineConfig({
59
74
  /**
60
75
  * @title 两数相加
61
76
  * 这个函数将两个数字相加
77
+ * @MenuTitle 数学/加法
62
78
  * @param {number} a - 第一个数字
63
79
  * @param {number} b - 第二个数字
64
80
  * @returns {number} a 和 b 的和
@@ -98,6 +114,8 @@ function add(a, b) {
98
114
  ```ts
99
115
  // 示例代码
100
116
  ```
117
+
118
+ 生成的 _pages.js 仅导出 pages 数组。如需筛选记录,请使用库导出的 `getRecords()`。
101
119
  ```
102
120
 
103
121
  ## 开发
@@ -122,4 +140,4 @@ npm run test:coverage
122
140
 
123
141
  ## 许可证
124
142
 
125
- MIT
143
+ MIT
package/dist/index.cjs CHANGED
@@ -145,18 +145,10 @@ function generateMarkdown(func) {
145
145
  function toPosixPath(pathValue) {
146
146
  return pathValue.split(path.sep).join("/");
147
147
  }
148
- function findVitepressRoot(outDir) {
148
+ function findVitepressRoot$1(outDir) {
149
149
  let current = (0, path.resolve)(outDir);
150
150
  while (true) {
151
- const configDir = (0, path.join)(current, ".vitepress");
152
- if ((0, fs.existsSync)(configDir)) {
153
- if ([
154
- "config.ts",
155
- "config.js",
156
- "config.mjs",
157
- "config.cjs"
158
- ].some((file) => (0, fs.existsSync)((0, path.join)(configDir, file)))) return current;
159
- }
151
+ if ((0, fs.existsSync)((0, path.join)(current, ".vitepress"))) return current;
160
152
  const parent = (0, path.dirname)(current);
161
153
  if (parent === current) break;
162
154
  current = parent;
@@ -165,7 +157,7 @@ function findVitepressRoot(outDir) {
165
157
  }
166
158
  function resolveLinkPrefix(outDir) {
167
159
  const resolvedOutDir = (0, path.resolve)(outDir);
168
- const vitepressRoot = findVitepressRoot(resolvedOutDir);
160
+ const vitepressRoot = findVitepressRoot$1(resolvedOutDir);
169
161
  if (vitepressRoot) return toPosixPath((0, path.relative)(vitepressRoot, resolvedOutDir));
170
162
  return (0, path.relative)(process.cwd(), resolvedOutDir).split(path.sep).filter(Boolean).slice(1).join("/");
171
163
  }
@@ -201,7 +193,7 @@ function sortPages(nodes) {
201
193
  for (const node of nodes) if (node.items && Array.isArray(node.items)) sortPages(node.items);
202
194
  }
203
195
  function generatePages(functions, srcDir, outDir) {
204
- const pagesDir = (0, path.join)(findVitepressRoot(outDir) ?? outDir, ".vitepress");
196
+ const pagesDir = (0, path.join)(findVitepressRoot$1(outDir) ?? outDir, ".vitepress");
205
197
  if (!(0, fs.existsSync)(pagesDir)) (0, fs.mkdirSync)(pagesDir, { recursive: true });
206
198
  const pagesPath = (0, path.join)(pagesDir, "_pages.js");
207
199
  const existingGroupText = readExistingGroupText(pagesPath);
@@ -237,12 +229,12 @@ function generatePages(functions, srcDir, outDir) {
237
229
  const linkPath = [linkPrefix, func.name].filter(Boolean).join("/");
238
230
  if (!current.items) current.items = [];
239
231
  current.items.push({
240
- text: func.jsdoc.menuTitle ?? "",
232
+ text: func.jsdoc.menuTitle ?? func.jsdoc.title ?? "",
241
233
  link: `/${linkPath}`
242
234
  });
243
235
  }
244
236
  if (root.items) sortPages(root.items);
245
- (0, fs.writeFileSync)(pagesPath, `// @ts-nocheck\n/* eslint-disable */\n/* prettier-ignore-start */\n/**\n * 此文件由 func2md 自动生成。\n * 仅允许修改菜单分组的 text 字段。\n */\nconst pages = ${JSON.stringify(root.items ?? [], null, 2)}\n/* prettier-ignore-end */\n\nexport default pages\n\n/**\n * 获取页面记录。\n * @param text 仅支持一级分组的 text;为空时返回全部。\n */\nexport function getRecords(text?: string) {\n if (!text) {\n return pages\n }\n return pages.filter((item) => item.text === text)\n}\n`);
237
+ (0, fs.writeFileSync)(pagesPath, `// @ts-nocheck\n/* eslint-disable */\n/* prettier-ignore-start */\n/**\n * 此文件由 func2md 自动生成。\n * 仅允许修改菜单分组的 text 字段。\n */\nconst pages = ${JSON.stringify(root.items ?? [], null, 2)}\n/* prettier-ignore-end */\n\nexport default pages\n`);
246
238
  }
247
239
  async function scanAndGenerateDocs(srcDir, outDir) {
248
240
  console.log(`Scanning ${srcDir} and generating docs to ${outDir}`);
@@ -262,6 +254,43 @@ async function scanAndGenerateDocs(srcDir, outDir) {
262
254
  generatePages(allFunctions, srcDir, outDir);
263
255
  }
264
256
 
257
+ //#endregion
258
+ //#region src/core/pages.ts
259
+ function findVitepressRoot(outDir) {
260
+ let current = (0, path.resolve)(outDir);
261
+ while (true) {
262
+ if ((0, fs.existsSync)((0, path.join)(current, ".vitepress"))) return current;
263
+ const parent = (0, path.resolve)(current, "..");
264
+ if (parent === current) break;
265
+ current = parent;
266
+ }
267
+ return null;
268
+ }
269
+ function resolvePagesPath(outDir) {
270
+ return (0, path.join)(findVitepressRoot(outDir) ?? (0, path.resolve)(outDir), ".vitepress", "_pages.js");
271
+ }
272
+ function readPages(pagesPath) {
273
+ if (!(0, fs.existsSync)(pagesPath)) return [];
274
+ const raw = (0, fs.readFileSync)(pagesPath, "utf-8").trim();
275
+ const arrayMatch = raw.match(/const\s+pages\s*=\s*(\[[\s\S]*\])\s*;?/) ?? raw.match(/export\s+default\s+(\[[\s\S]*\])\s*;?\s*$/);
276
+ if (!arrayMatch) return [];
277
+ try {
278
+ return JSON.parse(arrayMatch[1]);
279
+ } catch {
280
+ return [];
281
+ }
282
+ }
283
+ /**
284
+ * 获取 _pages.js 中的页面记录。
285
+ * @param options 读取选项
286
+ */
287
+ function getRecords(options = {}) {
288
+ const { pagesPath, outDir = "docs", text } = options;
289
+ const pages = readPages(pagesPath ? (0, path.resolve)(pagesPath) : resolvePagesPath(outDir));
290
+ if (!text) return pages;
291
+ return pages.filter((item) => item.text === text);
292
+ }
293
+
265
294
  //#endregion
266
295
  //#region src/index.ts
267
296
  function func2md(options = {}) {
@@ -282,5 +311,6 @@ function func2md(options = {}) {
282
311
 
283
312
  //#endregion
284
313
  exports.default = func2md;
314
+ exports.getRecords = getRecords;
285
315
  exports.scanAndGenerateDocs = scanAndGenerateDocs;
286
316
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["sep"],"sources":["../src/utils/file-utils.ts","../src/utils/jsdoc-parser.ts","../src/utils/function-extractor.ts","../src/generators/markdown-generator.ts","../src/core/scanner.ts","../src/index.ts"],"sourcesContent":["import { readdirSync, statSync, existsSync } from 'fs'\nimport { join, extname } from 'path'\n\nexport function findAllFiles(dir: string, extensions: string[]): string[] {\n if (!existsSync(dir)) {\n return []\n }\n\n let results: string[] = []\n\n function traverse(currentDir: string) {\n const files = readdirSync(currentDir)\n\n for (const file of files) {\n const filePath = join(currentDir, file)\n const stat = statSync(filePath)\n\n if (stat.isDirectory()) {\n traverse(filePath)\n } else if (stat.isFile() && extensions.includes(extname(file))) {\n results.push(filePath)\n }\n }\n }\n\n traverse(dir)\n return results\n}","export interface JSDocInfo {\n title?: string\n menuTitle?: string\n desc?: string\n example?: string\n params?: Array<{\n name: string\n type: string\n description: string\n }>\n returns?: {\n type: string\n description: string\n }\n signature?: string\n}\n\nexport function parseJSDoc(jsdoc: string): JSDocInfo {\n const lines = jsdoc.trim().split('\\n').map(line => line.replace(/^\\s*\\*\\s?/, ''))\n \n const info: JSDocInfo = {}\n \n // Extract title (first line or @title tag)\n const titleMatch = lines.find(line => line.startsWith('@title'))\n if (titleMatch) {\n info.title = titleMatch.replace('@title', '').trim()\n } else if (lines.length > 0) {\n info.title = lines[0]\n }\n\n // Extract menu title (@MenuTitle)\n const menuTitleMatch = lines.find(line => /@MenuTitle\\b/i.test(line))\n if (menuTitleMatch) {\n info.menuTitle = menuTitleMatch.replace(/@MenuTitle/i, '').trim()\n }\n \n // Extract description - all lines that don't start with @\n const descLines: string[] = []\n let inExample = false\n for (const line of lines) {\n // Stop collecting description when we hit @example or @param or @returns\n if (line.startsWith('@example') || line.startsWith('@param') || line.startsWith('@returns') || line.startsWith('@return')) {\n break\n }\n \n // Only add non-empty lines that don't start with @title to description\n if (!line.startsWith('@title') && line.trim() !== '') {\n descLines.push(line)\n }\n }\n \n if (descLines.length > 0) {\n info.desc = descLines.join(' ').trim()\n }\n \n // Extract example\n const exampleLines: string[] = []\n let collectingExample = false\n for (const line of lines) {\n if (line.startsWith('@example')) {\n collectingExample = true\n const exampleContent = line.replace('@example', '').trim()\n if (exampleContent) {\n exampleLines.push(exampleContent)\n }\n continue\n }\n \n // Continue collecting example lines until we hit another tag\n if (collectingExample) {\n if (line.startsWith('@')) {\n collectingExample = false\n } else {\n exampleLines.push(line)\n }\n }\n }\n \n if (exampleLines.length > 0) {\n info.example = exampleLines.join('\\n').trim()\n }\n \n // Extract parameters\n info.params = []\n lines.forEach(line => {\n // Match @param with or without type: @param {string} name - description or @param name - description\n const paramMatch = line.match(/@param\\s+(?:{([^}]+)})?\\s*(\\w+)\\s*-\\s*(.+)/)\n if (paramMatch) {\n info.params!.push({\n name: paramMatch[2],\n type: paramMatch[1] || 'any',\n description: paramMatch[3]\n })\n }\n })\n \n // Extract return value\n const returnMatch = lines.find(line => line.startsWith('@returns') || line.startsWith('@return'))\n if (returnMatch) {\n const returnInfo = returnMatch.replace(/@(returns?)/, '').trim()\n const typeMatch = returnInfo.match(/^{([^}]+)}\\s*(.*)/)\n if (typeMatch) {\n info.returns = {\n type: typeMatch[1],\n description: typeMatch[2]\n }\n } else {\n info.returns = {\n type: 'unknown',\n description: returnInfo\n }\n }\n }\n \n return info\n}\n","import { parseJSDoc, JSDocInfo } from './jsdoc-parser'\n\nexport interface FunctionInfo {\n name: string\n jsdoc: JSDocInfo\n signature?: string\n filePath: string\n}\n\nexport function extractFunctions(content: string, filePath: string): FunctionInfo[] {\n // This is a simplified implementation\n // In a real implementation, we would use AST parsing (e.g., with TypeScript Compiler API)\n \n const functions: FunctionInfo[] = []\n \n // Match function declarations with JSDoc comments\n // Using a string to avoid regex escaping issues\n const functionRegex = new RegExp(\n String.raw`/\\*\\*((?:.|\\n|\\r)*?)\\*/\\s*(export\\s+)?(async\\s+)?function\\s+(\\w+)`,\n 'g'\n )\n let match\n \n while ((match = functionRegex.exec(content)) !== null) {\n const [, jsdocRaw, , , functionName] = match\n const jsdoc = parseJSDoc(jsdocRaw)\n \n functions.push({\n name: functionName,\n jsdoc,\n filePath\n })\n }\n \n return functions\n}","import { FunctionInfo } from '../utils/function-extractor'\n\n// Generate the main title section\nexport function generateTitleSection(func: FunctionInfo): string {\n const { name, jsdoc } = func\n const title = jsdoc.title || name\n return `# ${title}\\n\\n`\n}\n\n// Generate the description section\nexport function generateDescriptionSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.desc) {\n return `## 说明\\n\\n${jsdoc.desc}\\n\\n`\n }\n return ''\n}\n\n// Generate the signature section\nexport function generateSignatureSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.signature) {\n return `## 签名\\n\\n\\`\\`\\`ts\\n${jsdoc.signature}\\n\\`\\`\\`\\n\\n`\n }\n return ''\n}\n\n// Generate the parameters section\nexport function generateParametersSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.params && jsdoc.params.length > 0) {\n let section = `## 参数\\n\\n`\n section += `| 参数名 | 类型 | 说明 |\\n|--------|------|------|\\n`\n jsdoc.params.forEach(param => {\n section += `| ${param.name} | \\`${param.type}\\` | ${param.description} |\\n`\n })\n section += `\\n`\n return section\n }\n return ''\n}\n\n// Generate the returns section\nexport function generateReturnsSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.returns) {\n return `## 返回值\\n\\n- 类型: \\`${jsdoc.returns.type}\\`\\n- 说明: ${jsdoc.returns.description}\\n\\n`\n }\n return ''\n}\n\n// Generate the example section\nexport function generateExampleSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.example) {\n return `## 示例\\n\\n\\`\\`\\`ts\\n${jsdoc.example}\\n\\`\\`\\`\\n\\n`\n }\n return ''\n}\n\n// Main function to generate markdown content\nexport function generateMarkdown(func: FunctionInfo): string {\n let md = generateTitleSection(func)\n md += generateDescriptionSection(func)\n md += generateSignatureSection(func)\n md += generateParametersSection(func)\n md += generateReturnsSection(func)\n md += generateExampleSection(func)\n return md\n}","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'\nimport { join, relative, dirname, resolve, sep } from 'path'\nimport { findAllFiles } from '../utils/file-utils'\nimport { extractFunctions } from '../utils/function-extractor'\nimport { generateMarkdown } from '../generators/markdown-generator'\n\ninterface PageNode {\n text: string\n items?: PageNode[]\n link?: string\n dir?: string\n}\n\nfunction toPosixPath(pathValue: string) {\n return pathValue.split(sep).join('/')\n}\n\nfunction findVitepressRoot(outDir: string): string | null {\n let current = resolve(outDir)\n while (true) {\n const configDir = join(current, '.vitepress')\n if (existsSync(configDir)) {\n const hasConfig = ['config.ts', 'config.js', 'config.mjs', 'config.cjs']\n .some(file => existsSync(join(configDir, file)))\n if (hasConfig) {\n return current\n }\n }\n const parent = dirname(current)\n if (parent === current) {\n break\n }\n current = parent\n }\n return null\n}\n\nfunction resolveLinkPrefix(outDir: string): string {\n const resolvedOutDir = resolve(outDir)\n const vitepressRoot = findVitepressRoot(resolvedOutDir)\n if (vitepressRoot) {\n const rel = relative(vitepressRoot, resolvedOutDir)\n return toPosixPath(rel)\n }\n const rel = relative(process.cwd(), resolvedOutDir)\n const parts = rel.split(sep).filter(Boolean)\n return parts.slice(1).join('/')\n}\n\nfunction readExistingGroupText(pagesPath: string): Map<string, string> {\n if (!existsSync(pagesPath)) {\n return new Map()\n }\n\n const raw = readFileSync(pagesPath, 'utf-8').trim()\n const arrayMatch = raw.match(/const\\s+pages\\s*=\\s*(\\[[\\s\\S]*\\])\\s*;?/)\n ?? raw.match(/export\\s+default\\s+(\\[[\\s\\S]*\\])\\s*;?\\s*$/)\n if (!arrayMatch) {\n return new Map()\n }\n\n try {\n const data = JSON.parse(arrayMatch[1]) as PageNode[]\n const map = new Map<string, string>()\n const walk = (nodes: PageNode[]) => {\n for (const node of nodes) {\n if (node.items && Array.isArray(node.items)) {\n if (typeof node.dir === 'string' && typeof node.text === 'string' && node.text.trim() !== '') {\n map.set(node.dir, node.text)\n }\n walk(node.items)\n }\n }\n }\n walk(data)\n return map\n } catch {\n return new Map()\n }\n}\n\nfunction sortPages(nodes: PageNode[]) {\n nodes.sort((a, b) => {\n const aIsGroup = Array.isArray(a.items)\n const bIsGroup = Array.isArray(b.items)\n if (aIsGroup && !bIsGroup) return -1\n if (!aIsGroup && bIsGroup) return 1\n if (aIsGroup && bIsGroup) {\n return (a.dir || '').localeCompare(b.dir || '')\n }\n return (a.link || '').localeCompare(b.link || '')\n })\n\n for (const node of nodes) {\n if (node.items && Array.isArray(node.items)) {\n sortPages(node.items)\n }\n }\n}\n\nfunction generatePages(functions: ReturnType<typeof extractFunctions>, srcDir: string, outDir: string) {\n const vitepressRoot = findVitepressRoot(outDir)\n const pagesBaseDir = vitepressRoot ?? outDir\n const pagesDir = join(pagesBaseDir, '.vitepress')\n if (!existsSync(pagesDir)) {\n mkdirSync(pagesDir, { recursive: true })\n }\n\n const pagesPath = join(pagesDir, '_pages.js')\n const existingGroupText = readExistingGroupText(pagesPath)\n const linkPrefix = resolveLinkPrefix(outDir)\n\n const root: PageNode = { text: '', items: [] }\n const sortedFunctions = [...functions].sort((a, b) => {\n const fileCompare = a.filePath.localeCompare(b.filePath)\n if (fileCompare !== 0) return fileCompare\n return a.name.localeCompare(b.name)\n })\n\n for (const func of sortedFunctions) {\n const relativePath = relative(srcDir, func.filePath)\n const dirPath = dirname(relativePath)\n const parts = dirPath === '.' ? [] : dirPath.split(sep)\n\n let current = root\n let currentDir = ''\n for (const part of parts) {\n currentDir = currentDir ? `${currentDir}/${part}` : part\n if (!current.items) {\n current.items = []\n }\n let next = current.items.find(item => item.items && item.dir === currentDir)\n if (!next) {\n const preservedText = existingGroupText.get(currentDir)\n next = {\n text: preservedText ?? '',\n items: [],\n dir: currentDir\n }\n current.items.push(next)\n }\n current = next\n }\n\n const linkPath = [linkPrefix, func.name].filter(Boolean).join('/')\n if (!current.items) {\n current.items = []\n }\n current.items.push({\n text: func.jsdoc.menuTitle ?? '',\n link: `/${linkPath}`\n })\n }\n\n if (root.items) {\n sortPages(root.items)\n }\n\n const pagesJson = JSON.stringify(root.items ?? [], null, 2)\n const output = `// @ts-nocheck\\n/* eslint-disable */\\n/* prettier-ignore-start */\\n/**\\n * 此文件由 func2md 自动生成。\\n * 仅允许修改菜单分组的 text 字段。\\n */\\nconst pages = ${pagesJson}\\n/* prettier-ignore-end */\\n\\nexport default pages\\n\\n/**\\n * 获取页面记录。\\n * @param text 仅支持一级分组的 text;为空时返回全部。\\n */\\nexport function getRecords(text?: string) {\\n if (!text) {\\n return pages\\n }\\n return pages.filter((item) => item.text === text)\\n}\\n`\n writeFileSync(pagesPath, output)\n}\n\nexport async function scanAndGenerateDocs(srcDir: string, outDir: string) {\n console.log(`Scanning ${srcDir} and generating docs to ${outDir}`)\n\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true })\n }\n \n // Find all .ts and .js files in srcDir\n const files = findAllFiles(srcDir, ['.ts', '.js'])\n \n const allFunctions = [] as ReturnType<typeof extractFunctions>\n\n for (const file of files) {\n const content = readFileSync(file, 'utf-8')\n const functions = extractFunctions(content, file)\n\n allFunctions.push(...functions)\n \n for (const func of functions) {\n const mdContent = generateMarkdown(func)\n const outputPath = join(outDir, `${func.name}.md`)\n writeFileSync(outputPath, mdContent)\n console.log(`Generated documentation: ${outputPath}`)\n }\n }\n\n generatePages(allFunctions, srcDir, outDir)\n}\n","import type { Plugin } from 'vite'\nimport { existsSync, mkdirSync } from 'fs'\nimport { resolve } from 'path'\nimport { scanAndGenerateDocs } from './core/scanner'\n\nexport { scanAndGenerateDocs }\n\nexport interface Func2MdOptions {\n /**\n * Source directory to scan for functions\n * @default 'src'\n */\n srcDir?: string\n \n /**\n * Output directory for generated markdown files\n * @default 'docs'\n */\n outDir?: string\n \n /**\n * Whether to watch for changes\n * @default false\n */\n watch?: boolean\n}\n\nexport default function func2md(options: Func2MdOptions = {}): Plugin {\n const { \n srcDir = 'src', \n outDir = 'docs',\n watch = false\n } = options\n \n const resolvedSrcDir = resolve(srcDir)\n const resolvedOutDir = resolve(outDir)\n \n return {\n name: 'func2md',\n \n async buildStart() {\n if (!existsSync(resolvedOutDir)) {\n mkdirSync(resolvedOutDir, { recursive: true })\n }\n \n // Scan files and generate docs\n await scanAndGenerateDocs(resolvedSrcDir, resolvedOutDir)\n },\n \n async handleHotUpdate(ctx) {\n if (watch && ctx.file.includes(resolvedSrcDir)) {\n await scanAndGenerateDocs(resolvedSrcDir, resolvedOutDir)\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["sep","findVitepressRoot"],"sources":["../src/utils/file-utils.ts","../src/utils/jsdoc-parser.ts","../src/utils/function-extractor.ts","../src/generators/markdown-generator.ts","../src/core/scanner.ts","../src/core/pages.ts","../src/index.ts"],"sourcesContent":["import { readdirSync, statSync, existsSync } from 'fs'\nimport { join, extname } from 'path'\n\nexport function findAllFiles(dir: string, extensions: string[]): string[] {\n if (!existsSync(dir)) {\n return []\n }\n\n let results: string[] = []\n\n function traverse(currentDir: string) {\n const files = readdirSync(currentDir)\n\n for (const file of files) {\n const filePath = join(currentDir, file)\n const stat = statSync(filePath)\n\n if (stat.isDirectory()) {\n traverse(filePath)\n } else if (stat.isFile() && extensions.includes(extname(file))) {\n results.push(filePath)\n }\n }\n }\n\n traverse(dir)\n return results\n}","export interface JSDocInfo {\n title?: string\n menuTitle?: string\n desc?: string\n example?: string\n params?: Array<{\n name: string\n type: string\n description: string\n }>\n returns?: {\n type: string\n description: string\n }\n signature?: string\n}\n\nexport function parseJSDoc(jsdoc: string): JSDocInfo {\n const lines = jsdoc.trim().split('\\n').map(line => line.replace(/^\\s*\\*\\s?/, ''))\n \n const info: JSDocInfo = {}\n \n // Extract title (first line or @title tag)\n const titleMatch = lines.find(line => line.startsWith('@title'))\n if (titleMatch) {\n info.title = titleMatch.replace('@title', '').trim()\n } else if (lines.length > 0) {\n info.title = lines[0]\n }\n\n // Extract menu title (@MenuTitle)\n const menuTitleMatch = lines.find(line => /@MenuTitle\\b/i.test(line))\n if (menuTitleMatch) {\n info.menuTitle = menuTitleMatch.replace(/@MenuTitle/i, '').trim()\n }\n \n // Extract description - all lines that don't start with @\n const descLines: string[] = []\n let inExample = false\n for (const line of lines) {\n // Stop collecting description when we hit @example or @param or @returns\n if (line.startsWith('@example') || line.startsWith('@param') || line.startsWith('@returns') || line.startsWith('@return')) {\n break\n }\n \n // Only add non-empty lines that don't start with @title to description\n if (!line.startsWith('@title') && line.trim() !== '') {\n descLines.push(line)\n }\n }\n \n if (descLines.length > 0) {\n info.desc = descLines.join(' ').trim()\n }\n \n // Extract example\n const exampleLines: string[] = []\n let collectingExample = false\n for (const line of lines) {\n if (line.startsWith('@example')) {\n collectingExample = true\n const exampleContent = line.replace('@example', '').trim()\n if (exampleContent) {\n exampleLines.push(exampleContent)\n }\n continue\n }\n \n // Continue collecting example lines until we hit another tag\n if (collectingExample) {\n if (line.startsWith('@')) {\n collectingExample = false\n } else {\n exampleLines.push(line)\n }\n }\n }\n \n if (exampleLines.length > 0) {\n info.example = exampleLines.join('\\n').trim()\n }\n \n // Extract parameters\n info.params = []\n lines.forEach(line => {\n // Match @param with or without type: @param {string} name - description or @param name - description\n const paramMatch = line.match(/@param\\s+(?:{([^}]+)})?\\s*(\\w+)\\s*-\\s*(.+)/)\n if (paramMatch) {\n info.params!.push({\n name: paramMatch[2],\n type: paramMatch[1] || 'any',\n description: paramMatch[3]\n })\n }\n })\n \n // Extract return value\n const returnMatch = lines.find(line => line.startsWith('@returns') || line.startsWith('@return'))\n if (returnMatch) {\n const returnInfo = returnMatch.replace(/@(returns?)/, '').trim()\n const typeMatch = returnInfo.match(/^{([^}]+)}\\s*(.*)/)\n if (typeMatch) {\n info.returns = {\n type: typeMatch[1],\n description: typeMatch[2]\n }\n } else {\n info.returns = {\n type: 'unknown',\n description: returnInfo\n }\n }\n }\n \n return info\n}\n","import { parseJSDoc, JSDocInfo } from './jsdoc-parser'\n\nexport interface FunctionInfo {\n name: string\n jsdoc: JSDocInfo\n signature?: string\n filePath: string\n}\n\nexport function extractFunctions(content: string, filePath: string): FunctionInfo[] {\n // This is a simplified implementation\n // In a real implementation, we would use AST parsing (e.g., with TypeScript Compiler API)\n \n const functions: FunctionInfo[] = []\n \n // Match function declarations with JSDoc comments\n // Using a string to avoid regex escaping issues\n const functionRegex = new RegExp(\n String.raw`/\\*\\*((?:.|\\n|\\r)*?)\\*/\\s*(export\\s+)?(async\\s+)?function\\s+(\\w+)`,\n 'g'\n )\n let match\n \n while ((match = functionRegex.exec(content)) !== null) {\n const [, jsdocRaw, , , functionName] = match\n const jsdoc = parseJSDoc(jsdocRaw)\n \n functions.push({\n name: functionName,\n jsdoc,\n filePath\n })\n }\n \n return functions\n}","import { FunctionInfo } from '../utils/function-extractor'\n\n// Generate the main title section\nexport function generateTitleSection(func: FunctionInfo): string {\n const { name, jsdoc } = func\n const title = jsdoc.title || name\n return `# ${title}\\n\\n`\n}\n\n// Generate the description section\nexport function generateDescriptionSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.desc) {\n return `## 说明\\n\\n${jsdoc.desc}\\n\\n`\n }\n return ''\n}\n\n// Generate the signature section\nexport function generateSignatureSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.signature) {\n return `## 签名\\n\\n\\`\\`\\`ts\\n${jsdoc.signature}\\n\\`\\`\\`\\n\\n`\n }\n return ''\n}\n\n// Generate the parameters section\nexport function generateParametersSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.params && jsdoc.params.length > 0) {\n let section = `## 参数\\n\\n`\n section += `| 参数名 | 类型 | 说明 |\\n|--------|------|------|\\n`\n jsdoc.params.forEach(param => {\n section += `| ${param.name} | \\`${param.type}\\` | ${param.description} |\\n`\n })\n section += `\\n`\n return section\n }\n return ''\n}\n\n// Generate the returns section\nexport function generateReturnsSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.returns) {\n return `## 返回值\\n\\n- 类型: \\`${jsdoc.returns.type}\\`\\n- 说明: ${jsdoc.returns.description}\\n\\n`\n }\n return ''\n}\n\n// Generate the example section\nexport function generateExampleSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.example) {\n return `## 示例\\n\\n\\`\\`\\`ts\\n${jsdoc.example}\\n\\`\\`\\`\\n\\n`\n }\n return ''\n}\n\n// Main function to generate markdown content\nexport function generateMarkdown(func: FunctionInfo): string {\n let md = generateTitleSection(func)\n md += generateDescriptionSection(func)\n md += generateSignatureSection(func)\n md += generateParametersSection(func)\n md += generateReturnsSection(func)\n md += generateExampleSection(func)\n return md\n}","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'\nimport { join, relative, dirname, resolve, sep } from 'path'\nimport { findAllFiles } from '../utils/file-utils'\nimport { extractFunctions } from '../utils/function-extractor'\nimport { generateMarkdown } from '../generators/markdown-generator'\n\ninterface PageNode {\n text: string\n items?: PageNode[]\n link?: string\n dir?: string\n}\n\nfunction toPosixPath(pathValue: string) {\n return pathValue.split(sep).join('/')\n}\n\nfunction findVitepressRoot(outDir: string): string | null {\n let current = resolve(outDir)\n while (true) {\n const configDir = join(current, '.vitepress')\n // 如果存在 .vitepress 目录,无论是否有配置文件都认为这是 VitePress 根目录\n if (existsSync(configDir)) {\n return current\n }\n const parent = dirname(current)\n if (parent === current) {\n break\n }\n current = parent\n }\n return null\n}\n\nfunction resolveLinkPrefix(outDir: string): string {\n const resolvedOutDir = resolve(outDir)\n const vitepressRoot = findVitepressRoot(resolvedOutDir)\n if (vitepressRoot) {\n const rel = relative(vitepressRoot, resolvedOutDir)\n return toPosixPath(rel)\n }\n const rel = relative(process.cwd(), resolvedOutDir)\n const parts = rel.split(sep).filter(Boolean)\n return parts.slice(1).join('/')\n}\n\nfunction readExistingGroupText(pagesPath: string): Map<string, string> {\n if (!existsSync(pagesPath)) {\n return new Map()\n }\n\n const raw = readFileSync(pagesPath, 'utf-8').trim()\n const arrayMatch = raw.match(/const\\s+pages\\s*=\\s*(\\[[\\s\\S]*\\])\\s*;?/)\n ?? raw.match(/export\\s+default\\s+(\\[[\\s\\S]*\\])\\s*;?\\s*$/)\n if (!arrayMatch) {\n return new Map()\n }\n\n try {\n const data = JSON.parse(arrayMatch[1]) as PageNode[]\n const map = new Map<string, string>()\n const walk = (nodes: PageNode[]) => {\n for (const node of nodes) {\n if (node.items && Array.isArray(node.items)) {\n if (typeof node.dir === 'string' && typeof node.text === 'string' && node.text.trim() !== '') {\n map.set(node.dir, node.text)\n }\n walk(node.items)\n }\n }\n }\n walk(data)\n return map\n } catch {\n return new Map()\n }\n}\n\nfunction sortPages(nodes: PageNode[]) {\n nodes.sort((a, b) => {\n const aIsGroup = Array.isArray(a.items)\n const bIsGroup = Array.isArray(b.items)\n if (aIsGroup && !bIsGroup) return -1\n if (!aIsGroup && bIsGroup) return 1\n if (aIsGroup && bIsGroup) {\n return (a.dir || '').localeCompare(b.dir || '')\n }\n return (a.link || '').localeCompare(b.link || '')\n })\n\n for (const node of nodes) {\n if (node.items && Array.isArray(node.items)) {\n sortPages(node.items)\n }\n }\n}\n\nfunction generatePages(functions: ReturnType<typeof extractFunctions>, srcDir: string, outDir: string) {\n const vitepressRoot = findVitepressRoot(outDir)\n const pagesBaseDir = vitepressRoot ?? outDir\n const pagesDir = join(pagesBaseDir, '.vitepress')\n if (!existsSync(pagesDir)) {\n mkdirSync(pagesDir, { recursive: true })\n }\n\n const pagesPath = join(pagesDir, '_pages.js')\n const existingGroupText = readExistingGroupText(pagesPath)\n const linkPrefix = resolveLinkPrefix(outDir)\n\n const root: PageNode = { text: '', items: [] }\n const sortedFunctions = [...functions].sort((a, b) => {\n const fileCompare = a.filePath.localeCompare(b.filePath)\n if (fileCompare !== 0) return fileCompare\n return a.name.localeCompare(b.name)\n })\n\n for (const func of sortedFunctions) {\n const relativePath = relative(srcDir, func.filePath)\n const dirPath = dirname(relativePath)\n const parts = dirPath === '.' ? [] : dirPath.split(sep)\n\n let current = root\n let currentDir = ''\n for (const part of parts) {\n currentDir = currentDir ? `${currentDir}/${part}` : part\n if (!current.items) {\n current.items = []\n }\n let next = current.items.find(item => item.items && item.dir === currentDir)\n if (!next) {\n const preservedText = existingGroupText.get(currentDir)\n next = {\n text: preservedText ?? '',\n items: [],\n dir: currentDir\n }\n current.items.push(next)\n }\n current = next\n }\n\n const linkPath = [linkPrefix, func.name].filter(Boolean).join('/')\n if (!current.items) {\n current.items = []\n }\n current.items.push({\n text: func.jsdoc.menuTitle ?? func.jsdoc.title ?? '',\n link: `/${linkPath}`\n })\n }\n\n if (root.items) {\n sortPages(root.items)\n }\n\n const pagesJson = JSON.stringify(root.items ?? [], null, 2)\n const output = `// @ts-nocheck\\n/* eslint-disable */\\n/* prettier-ignore-start */\\n/**\\n * 此文件由 func2md 自动生成。\\n * 仅允许修改菜单分组的 text 字段。\\n */\\nconst pages = ${pagesJson}\\n/* prettier-ignore-end */\\n\\nexport default pages\\n`\n writeFileSync(pagesPath, output)\n}\n\nexport async function scanAndGenerateDocs(srcDir: string, outDir: string) {\n console.log(`Scanning ${srcDir} and generating docs to ${outDir}`)\n\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true })\n }\n \n // Find all .ts and .js files in srcDir\n const files = findAllFiles(srcDir, ['.ts', '.js'])\n \n const allFunctions = [] as ReturnType<typeof extractFunctions>\n\n for (const file of files) {\n const content = readFileSync(file, 'utf-8')\n const functions = extractFunctions(content, file)\n\n allFunctions.push(...functions)\n \n for (const func of functions) {\n const mdContent = generateMarkdown(func)\n const outputPath = join(outDir, `${func.name}.md`)\n writeFileSync(outputPath, mdContent)\n console.log(`Generated documentation: ${outputPath}`)\n }\n }\n\n generatePages(allFunctions, srcDir, outDir)\n}\n","import { readFileSync, existsSync } from 'fs'\nimport { resolve, join } from 'path'\n\nexport interface PageNode {\n text: string\n items?: PageNode[]\n link?: string\n dir?: string\n}\n\nexport interface GetRecordsOptions {\n /**\n * 指定 _pages.js 的完整路径。\n * 当提供该值时,会忽略 outDir。\n */\n pagesPath?: string\n /**\n * 指定生成文档的输出目录(默认 docs)。\n * 会自动定位到 VitePress 根目录下的 .vitepress/_pages.js。\n */\n outDir?: string\n /**\n * 仅返回一级分组的 text 匹配项。\n * 为空时返回全部。\n */\n text?: string\n}\n\nfunction findVitepressRoot(outDir: string): string | null {\n let current = resolve(outDir)\n while (true) {\n const configDir = join(current, '.vitepress')\n if (existsSync(configDir)) {\n return current\n }\n const parent = resolve(current, '..')\n if (parent === current) {\n break\n }\n current = parent\n }\n return null\n}\n\nfunction resolvePagesPath(outDir: string): string {\n const vitepressRoot = findVitepressRoot(outDir)\n const baseDir = vitepressRoot ?? resolve(outDir)\n return join(baseDir, '.vitepress', '_pages.js')\n}\n\nfunction readPages(pagesPath: string): PageNode[] {\n if (!existsSync(pagesPath)) {\n return []\n }\n\n const raw = readFileSync(pagesPath, 'utf-8').trim()\n const arrayMatch = raw.match(/const\\s+pages\\s*=\\s*(\\[[\\s\\S]*\\])\\s*;?/) ?? raw.match(/export\\s+default\\s+(\\[[\\s\\S]*\\])\\s*;?\\s*$/)\n if (!arrayMatch) {\n return []\n }\n\n try {\n return JSON.parse(arrayMatch[1]) as PageNode[]\n } catch {\n return []\n }\n}\n\n/**\n * 获取 _pages.js 中的页面记录。\n * @param options 读取选项\n */\nexport function getRecords(options: GetRecordsOptions = {}): PageNode[] {\n const { pagesPath, outDir = 'docs', text } = options\n const targetPath = pagesPath ? resolve(pagesPath) : resolvePagesPath(outDir)\n const pages = readPages(targetPath)\n\n if (!text) {\n return pages\n }\n return pages.filter(item => item.text === text)\n}\n","import type { Plugin } from 'vite'\nimport { existsSync, mkdirSync } from 'fs'\nimport { resolve } from 'path'\nimport { scanAndGenerateDocs } from './core/scanner'\nimport { getRecords } from './core/pages'\n\nexport { scanAndGenerateDocs, getRecords }\n\nexport interface Func2MdOptions {\n /**\n * Source directory to scan for functions\n * @default 'src'\n */\n srcDir?: string\n \n /**\n * Output directory for generated markdown files\n * @default 'docs'\n */\n outDir?: string\n \n /**\n * Whether to watch for changes\n * @default false\n */\n watch?: boolean\n}\n\nexport default function func2md(options: Func2MdOptions = {}): Plugin {\n const { \n srcDir = 'src', \n outDir = 'docs',\n watch = false\n } = options\n \n const resolvedSrcDir = resolve(srcDir)\n const resolvedOutDir = resolve(outDir)\n \n return {\n name: 'func2md',\n \n async buildStart() {\n if (!existsSync(resolvedOutDir)) {\n mkdirSync(resolvedOutDir, { recursive: true })\n }\n \n // Scan files and generate docs\n await scanAndGenerateDocs(resolvedSrcDir, resolvedOutDir)\n },\n \n async handleHotUpdate(ctx) {\n if (watch && ctx.file.includes(resolvedSrcDir)) {\n await scanAndGenerateDocs(resolvedSrcDir, resolvedOutDir)\n }\n }\n }\n}\n"],"mappings":";;;;;AAGA,SAAgB,aAAa,KAAa,YAAgC;AACxE,KAAI,oBAAY,IAAI,CAClB,QAAO,EAAE;CAGX,IAAI,UAAoB,EAAE;CAE1B,SAAS,SAAS,YAAoB;EACpC,MAAM,4BAAoB,WAAW;AAErC,OAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,0BAAgB,YAAY,KAAK;GACvC,MAAM,wBAAgB,SAAS;AAE/B,OAAI,KAAK,aAAa,CACpB,UAAS,SAAS;YACT,KAAK,QAAQ,IAAI,WAAW,2BAAiB,KAAK,CAAC,CAC5D,SAAQ,KAAK,SAAS;;;AAK5B,UAAS,IAAI;AACb,QAAO;;;;;ACTT,SAAgB,WAAW,OAA0B;CACnD,MAAM,QAAQ,MAAM,MAAM,CAAC,MAAM,KAAK,CAAC,KAAI,SAAQ,KAAK,QAAQ,aAAa,GAAG,CAAC;CAEjF,MAAM,OAAkB,EAAE;CAG1B,MAAM,aAAa,MAAM,MAAK,SAAQ,KAAK,WAAW,SAAS,CAAC;AAChE,KAAI,WACF,MAAK,QAAQ,WAAW,QAAQ,UAAU,GAAG,CAAC,MAAM;UAC3C,MAAM,SAAS,EACxB,MAAK,QAAQ,MAAM;CAIrB,MAAM,iBAAiB,MAAM,MAAK,SAAQ,gBAAgB,KAAK,KAAK,CAAC;AACrE,KAAI,eACF,MAAK,YAAY,eAAe,QAAQ,eAAe,GAAG,CAAC,MAAM;CAInE,MAAM,YAAsB,EAAE;AAE9B,MAAK,MAAM,QAAQ,OAAO;AAExB,MAAI,KAAK,WAAW,WAAW,IAAI,KAAK,WAAW,SAAS,IAAI,KAAK,WAAW,WAAW,IAAI,KAAK,WAAW,UAAU,CACvH;AAIF,MAAI,CAAC,KAAK,WAAW,SAAS,IAAI,KAAK,MAAM,KAAK,GAChD,WAAU,KAAK,KAAK;;AAIxB,KAAI,UAAU,SAAS,EACrB,MAAK,OAAO,UAAU,KAAK,IAAI,CAAC,MAAM;CAIxC,MAAM,eAAyB,EAAE;CACjC,IAAI,oBAAoB;AACxB,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,KAAK,WAAW,WAAW,EAAE;AAC/B,uBAAoB;GACpB,MAAM,iBAAiB,KAAK,QAAQ,YAAY,GAAG,CAAC,MAAM;AAC1D,OAAI,eACF,cAAa,KAAK,eAAe;AAEnC;;AAIF,MAAI,kBACF,KAAI,KAAK,WAAW,IAAI,CACtB,qBAAoB;MAEpB,cAAa,KAAK,KAAK;;AAK7B,KAAI,aAAa,SAAS,EACxB,MAAK,UAAU,aAAa,KAAK,KAAK,CAAC,MAAM;AAI/C,MAAK,SAAS,EAAE;AAChB,OAAM,SAAQ,SAAQ;EAEpB,MAAM,aAAa,KAAK,MAAM,6CAA6C;AAC3E,MAAI,WACF,MAAK,OAAQ,KAAK;GAChB,MAAM,WAAW;GACjB,MAAM,WAAW,MAAM;GACvB,aAAa,WAAW;GACzB,CAAC;GAEJ;CAGF,MAAM,cAAc,MAAM,MAAK,SAAQ,KAAK,WAAW,WAAW,IAAI,KAAK,WAAW,UAAU,CAAC;AACjG,KAAI,aAAa;EACf,MAAM,aAAa,YAAY,QAAQ,eAAe,GAAG,CAAC,MAAM;EAChE,MAAM,YAAY,WAAW,MAAM,oBAAoB;AACvD,MAAI,UACF,MAAK,UAAU;GACb,MAAM,UAAU;GAChB,aAAa,UAAU;GACxB;MAED,MAAK,UAAU;GACb,MAAM;GACN,aAAa;GACd;;AAIL,QAAO;;;;;ACzGT,SAAgB,iBAAiB,SAAiB,UAAkC;CAIlF,MAAM,YAA4B,EAAE;CAIpC,MAAM,gBAAgB,IAAI,OACxB,OAAO,GAAG,qEACV,IACD;CACD,IAAI;AAEJ,SAAQ,QAAQ,cAAc,KAAK,QAAQ,MAAM,MAAM;EACrD,MAAM,GAAG,cAAc,gBAAgB;EACvC,MAAM,QAAQ,WAAW,SAAS;AAElC,YAAU,KAAK;GACb,MAAM;GACN;GACA;GACD,CAAC;;AAGJ,QAAO;;;;;AC/BT,SAAgB,qBAAqB,MAA4B;CAC/D,MAAM,EAAE,MAAM,UAAU;AAExB,QAAO,KADO,MAAM,SAAS,KACX;;AAIpB,SAAgB,2BAA2B,MAA4B;CACrE,MAAM,EAAE,UAAU;AAClB,KAAI,MAAM,KACR,QAAO,YAAY,MAAM,KAAK;AAEhC,QAAO;;AAIT,SAAgB,yBAAyB,MAA4B;CACnE,MAAM,EAAE,UAAU;AAClB,KAAI,MAAM,UACR,QAAO,sBAAsB,MAAM,UAAU;AAE/C,QAAO;;AAIT,SAAgB,0BAA0B,MAA4B;CACpE,MAAM,EAAE,UAAU;AAClB,KAAI,MAAM,UAAU,MAAM,OAAO,SAAS,GAAG;EAC3C,IAAI,UAAU;AACd,aAAW;AACX,QAAM,OAAO,SAAQ,UAAS;AAC5B,cAAW,KAAK,MAAM,KAAK,OAAO,MAAM,KAAK,OAAO,MAAM,YAAY;IACtE;AACF,aAAW;AACX,SAAO;;AAET,QAAO;;AAIT,SAAgB,uBAAuB,MAA4B;CACjE,MAAM,EAAE,UAAU;AAClB,KAAI,MAAM,QACR,QAAO,qBAAqB,MAAM,QAAQ,KAAK,YAAY,MAAM,QAAQ,YAAY;AAEvF,QAAO;;AAIT,SAAgB,uBAAuB,MAA4B;CACjE,MAAM,EAAE,UAAU;AAClB,KAAI,MAAM,QACR,QAAO,sBAAsB,MAAM,QAAQ;AAE7C,QAAO;;AAIT,SAAgB,iBAAiB,MAA4B;CAC3D,IAAI,KAAK,qBAAqB,KAAK;AACnC,OAAM,2BAA2B,KAAK;AACtC,OAAM,yBAAyB,KAAK;AACpC,OAAM,0BAA0B,KAAK;AACrC,OAAM,uBAAuB,KAAK;AAClC,OAAM,uBAAuB,KAAK;AAClC,QAAO;;;;;ACvDT,SAAS,YAAY,WAAmB;AACtC,QAAO,UAAU,MAAMA,SAAI,CAAC,KAAK,IAAI;;AAGvC,SAASC,oBAAkB,QAA+B;CACxD,IAAI,4BAAkB,OAAO;AAC7B,QAAO,MAAM;AAGX,wCAFuB,SAAS,aAAa,CAEpB,CACvB,QAAO;EAET,MAAM,2BAAiB,QAAQ;AAC/B,MAAI,WAAW,QACb;AAEF,YAAU;;AAEZ,QAAO;;AAGT,SAAS,kBAAkB,QAAwB;CACjD,MAAM,mCAAyB,OAAO;CACtC,MAAM,gBAAgBA,oBAAkB,eAAe;AACvD,KAAI,cAEF,QAAO,+BADc,eAAe,eAAe,CAC5B;AAIzB,2BAFqB,QAAQ,KAAK,EAAE,eAAe,CACjC,MAAMD,SAAI,CAAC,OAAO,QAAQ,CAC/B,MAAM,EAAE,CAAC,KAAK,IAAI;;AAGjC,SAAS,sBAAsB,WAAwC;AACrE,KAAI,oBAAY,UAAU,CACxB,wBAAO,IAAI,KAAK;CAGlB,MAAM,2BAAmB,WAAW,QAAQ,CAAC,MAAM;CACnD,MAAM,aAAa,IAAI,MAAM,yCAAyC,IACjE,IAAI,MAAM,4CAA4C;AAC3D,KAAI,CAAC,WACH,wBAAO,IAAI,KAAK;AAGlB,KAAI;EACF,MAAM,OAAO,KAAK,MAAM,WAAW,GAAG;EACtC,MAAM,sBAAM,IAAI,KAAqB;EACrC,MAAM,QAAQ,UAAsB;AAClC,QAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,SAAS,MAAM,QAAQ,KAAK,MAAM,EAAE;AAC3C,QAAI,OAAO,KAAK,QAAQ,YAAY,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,MAAM,KAAK,GACxF,KAAI,IAAI,KAAK,KAAK,KAAK,KAAK;AAE9B,SAAK,KAAK,MAAM;;;AAItB,OAAK,KAAK;AACV,SAAO;SACD;AACN,yBAAO,IAAI,KAAK;;;AAIpB,SAAS,UAAU,OAAmB;AACpC,OAAM,MAAM,GAAG,MAAM;EACnB,MAAM,WAAW,MAAM,QAAQ,EAAE,MAAM;EACvC,MAAM,WAAW,MAAM,QAAQ,EAAE,MAAM;AACvC,MAAI,YAAY,CAAC,SAAU,QAAO;AAClC,MAAI,CAAC,YAAY,SAAU,QAAO;AAClC,MAAI,YAAY,SACd,SAAQ,EAAE,OAAO,IAAI,cAAc,EAAE,OAAO,GAAG;AAEjD,UAAQ,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG;GACjD;AAEF,MAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,SAAS,MAAM,QAAQ,KAAK,MAAM,CACzC,WAAU,KAAK,MAAM;;AAK3B,SAAS,cAAc,WAAgD,QAAgB,QAAgB;CAGrG,MAAM,0BAFgBC,oBAAkB,OAAO,IACT,QACF,aAAa;AACjD,KAAI,oBAAY,SAAS,CACvB,mBAAU,UAAU,EAAE,WAAW,MAAM,CAAC;CAG1C,MAAM,2BAAiB,UAAU,YAAY;CAC7C,MAAM,oBAAoB,sBAAsB,UAAU;CAC1D,MAAM,aAAa,kBAAkB,OAAO;CAE5C,MAAM,OAAiB;EAAE,MAAM;EAAI,OAAO,EAAE;EAAE;CAC9C,MAAM,kBAAkB,CAAC,GAAG,UAAU,CAAC,MAAM,GAAG,MAAM;EACpD,MAAM,cAAc,EAAE,SAAS,cAAc,EAAE,SAAS;AACxD,MAAI,gBAAgB,EAAG,QAAO;AAC9B,SAAO,EAAE,KAAK,cAAc,EAAE,KAAK;GACnC;AAEF,MAAK,MAAM,QAAQ,iBAAiB;EAElC,MAAM,+CADwB,QAAQ,KAAK,SAAS,CACf;EACrC,MAAM,QAAQ,YAAY,MAAM,EAAE,GAAG,QAAQ,MAAMD,SAAI;EAEvD,IAAI,UAAU;EACd,IAAI,aAAa;AACjB,OAAK,MAAM,QAAQ,OAAO;AACxB,gBAAa,aAAa,GAAG,WAAW,GAAG,SAAS;AACpD,OAAI,CAAC,QAAQ,MACX,SAAQ,QAAQ,EAAE;GAEpB,IAAI,OAAO,QAAQ,MAAM,MAAK,SAAQ,KAAK,SAAS,KAAK,QAAQ,WAAW;AAC5E,OAAI,CAAC,MAAM;AAET,WAAO;KACL,MAFoB,kBAAkB,IAAI,WAAW,IAE9B;KACvB,OAAO,EAAE;KACT,KAAK;KACN;AACD,YAAQ,MAAM,KAAK,KAAK;;AAE1B,aAAU;;EAGZ,MAAM,WAAW,CAAC,YAAY,KAAK,KAAK,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;AAClE,MAAI,CAAC,QAAQ,MACX,SAAQ,QAAQ,EAAE;AAEpB,UAAQ,MAAM,KAAK;GACjB,MAAM,KAAK,MAAM,aAAa,KAAK,MAAM,SAAS;GAClD,MAAM,IAAI;GACX,CAAC;;AAGJ,KAAI,KAAK,MACP,WAAU,KAAK,MAAM;AAKvB,uBAAc,WADC,6IADG,KAAK,UAAU,KAAK,SAAS,EAAE,EAAE,MAAM,EAAE,CAC2G,uDACtI;;AAGlC,eAAsB,oBAAoB,QAAgB,QAAgB;AACxE,SAAQ,IAAI,YAAY,OAAO,0BAA0B,SAAS;AAElE,KAAI,oBAAY,OAAO,CACrB,mBAAU,QAAQ,EAAE,WAAW,MAAM,CAAC;CAIxC,MAAM,QAAQ,aAAa,QAAQ,CAAC,OAAO,MAAM,CAAC;CAElD,MAAM,eAAe,EAAE;AAEvB,MAAK,MAAM,QAAQ,OAAO;EAExB,MAAM,YAAY,sCADW,MAAM,QAAQ,EACC,KAAK;AAEjD,eAAa,KAAK,GAAG,UAAU;AAE/B,OAAK,MAAM,QAAQ,WAAW;GAC5B,MAAM,YAAY,iBAAiB,KAAK;GACxC,MAAM,4BAAkB,QAAQ,GAAG,KAAK,KAAK,KAAK;AAClD,yBAAc,YAAY,UAAU;AACpC,WAAQ,IAAI,4BAA4B,aAAa;;;AAIzD,eAAc,cAAc,QAAQ,OAAO;;;;;AC9J7C,SAAS,kBAAkB,QAA+B;CACxD,IAAI,4BAAkB,OAAO;AAC7B,QAAO,MAAM;AAEX,wCADuB,SAAS,aAAa,CACpB,CACvB,QAAO;EAET,MAAM,2BAAiB,SAAS,KAAK;AACrC,MAAI,WAAW,QACb;AAEF,YAAU;;AAEZ,QAAO;;AAGT,SAAS,iBAAiB,QAAwB;AAGhD,uBAFsB,kBAAkB,OAAO,sBACN,OAAO,EAC3B,cAAc,YAAY;;AAGjD,SAAS,UAAU,WAA+B;AAChD,KAAI,oBAAY,UAAU,CACxB,QAAO,EAAE;CAGX,MAAM,2BAAmB,WAAW,QAAQ,CAAC,MAAM;CACnD,MAAM,aAAa,IAAI,MAAM,yCAAyC,IAAI,IAAI,MAAM,4CAA4C;AAChI,KAAI,CAAC,WACH,QAAO,EAAE;AAGX,KAAI;AACF,SAAO,KAAK,MAAM,WAAW,GAAG;SAC1B;AACN,SAAO,EAAE;;;;;;;AAQb,SAAgB,WAAW,UAA6B,EAAE,EAAc;CACtE,MAAM,EAAE,WAAW,SAAS,QAAQ,SAAS;CAE7C,MAAM,QAAQ,UADK,8BAAoB,UAAU,GAAG,iBAAiB,OAAO,CACzC;AAEnC,KAAI,CAAC,KACH,QAAO;AAET,QAAO,MAAM,QAAO,SAAQ,KAAK,SAAS,KAAK;;;;;ACpDjD,SAAwB,QAAQ,UAA0B,EAAE,EAAU;CACpE,MAAM,EACJ,SAAS,OACT,SAAS,QACT,QAAQ,UACN;CAEJ,MAAM,mCAAyB,OAAO;CACtC,MAAM,mCAAyB,OAAO;AAEtC,QAAO;EACL,MAAM;EAEN,MAAM,aAAa;AACjB,OAAI,oBAAY,eAAe,CAC7B,mBAAU,gBAAgB,EAAE,WAAW,MAAM,CAAC;AAIhD,SAAM,oBAAoB,gBAAgB,eAAe;;EAG3D,MAAM,gBAAgB,KAAK;AACzB,OAAI,SAAS,IAAI,KAAK,SAAS,eAAe,CAC5C,OAAM,oBAAoB,gBAAgB,eAAe;;EAG9D"}
package/dist/index.d.cts CHANGED
@@ -3,6 +3,36 @@ import { Plugin } from "vite";
3
3
  //#region src/core/scanner.d.ts
4
4
  declare function scanAndGenerateDocs(srcDir: string, outDir: string): Promise<void>;
5
5
  //#endregion
6
+ //#region src/core/pages.d.ts
7
+ interface PageNode {
8
+ text: string;
9
+ items?: PageNode[];
10
+ link?: string;
11
+ dir?: string;
12
+ }
13
+ interface GetRecordsOptions {
14
+ /**
15
+ * 指定 _pages.js 的完整路径。
16
+ * 当提供该值时,会忽略 outDir。
17
+ */
18
+ pagesPath?: string;
19
+ /**
20
+ * 指定生成文档的输出目录(默认 docs)。
21
+ * 会自动定位到 VitePress 根目录下的 .vitepress/_pages.js。
22
+ */
23
+ outDir?: string;
24
+ /**
25
+ * 仅返回一级分组的 text 匹配项。
26
+ * 为空时返回全部。
27
+ */
28
+ text?: string;
29
+ }
30
+ /**
31
+ * 获取 _pages.js 中的页面记录。
32
+ * @param options 读取选项
33
+ */
34
+ declare function getRecords(options?: GetRecordsOptions): PageNode[];
35
+ //#endregion
6
36
  //#region src/index.d.ts
7
37
  interface Func2MdOptions {
8
38
  /**
@@ -23,5 +53,5 @@ interface Func2MdOptions {
23
53
  }
24
54
  declare function func2md(options?: Func2MdOptions): Plugin;
25
55
  //#endregion
26
- export { Func2MdOptions, func2md as default, scanAndGenerateDocs };
56
+ export { Func2MdOptions, func2md as default, getRecords, scanAndGenerateDocs };
27
57
  //# sourceMappingURL=index.d.cts.map
package/dist/index.d.mts CHANGED
@@ -3,6 +3,36 @@ import { Plugin } from "vite";
3
3
  //#region src/core/scanner.d.ts
4
4
  declare function scanAndGenerateDocs(srcDir: string, outDir: string): Promise<void>;
5
5
  //#endregion
6
+ //#region src/core/pages.d.ts
7
+ interface PageNode {
8
+ text: string;
9
+ items?: PageNode[];
10
+ link?: string;
11
+ dir?: string;
12
+ }
13
+ interface GetRecordsOptions {
14
+ /**
15
+ * 指定 _pages.js 的完整路径。
16
+ * 当提供该值时,会忽略 outDir。
17
+ */
18
+ pagesPath?: string;
19
+ /**
20
+ * 指定生成文档的输出目录(默认 docs)。
21
+ * 会自动定位到 VitePress 根目录下的 .vitepress/_pages.js。
22
+ */
23
+ outDir?: string;
24
+ /**
25
+ * 仅返回一级分组的 text 匹配项。
26
+ * 为空时返回全部。
27
+ */
28
+ text?: string;
29
+ }
30
+ /**
31
+ * 获取 _pages.js 中的页面记录。
32
+ * @param options 读取选项
33
+ */
34
+ declare function getRecords(options?: GetRecordsOptions): PageNode[];
35
+ //#endregion
6
36
  //#region src/index.d.ts
7
37
  interface Func2MdOptions {
8
38
  /**
@@ -23,5 +53,5 @@ interface Func2MdOptions {
23
53
  }
24
54
  declare function func2md(options?: Func2MdOptions): Plugin;
25
55
  //#endregion
26
- export { Func2MdOptions, func2md as default, scanAndGenerateDocs };
56
+ export { Func2MdOptions, func2md as default, getRecords, scanAndGenerateDocs };
27
57
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -144,18 +144,10 @@ function generateMarkdown(func) {
144
144
  function toPosixPath(pathValue) {
145
145
  return pathValue.split(sep).join("/");
146
146
  }
147
- function findVitepressRoot(outDir) {
147
+ function findVitepressRoot$1(outDir) {
148
148
  let current = resolve(outDir);
149
149
  while (true) {
150
- const configDir = join(current, ".vitepress");
151
- if (existsSync(configDir)) {
152
- if ([
153
- "config.ts",
154
- "config.js",
155
- "config.mjs",
156
- "config.cjs"
157
- ].some((file) => existsSync(join(configDir, file)))) return current;
158
- }
150
+ if (existsSync(join(current, ".vitepress"))) return current;
159
151
  const parent = dirname(current);
160
152
  if (parent === current) break;
161
153
  current = parent;
@@ -164,7 +156,7 @@ function findVitepressRoot(outDir) {
164
156
  }
165
157
  function resolveLinkPrefix(outDir) {
166
158
  const resolvedOutDir = resolve(outDir);
167
- const vitepressRoot = findVitepressRoot(resolvedOutDir);
159
+ const vitepressRoot = findVitepressRoot$1(resolvedOutDir);
168
160
  if (vitepressRoot) return toPosixPath(relative(vitepressRoot, resolvedOutDir));
169
161
  return relative(process.cwd(), resolvedOutDir).split(sep).filter(Boolean).slice(1).join("/");
170
162
  }
@@ -200,7 +192,7 @@ function sortPages(nodes) {
200
192
  for (const node of nodes) if (node.items && Array.isArray(node.items)) sortPages(node.items);
201
193
  }
202
194
  function generatePages(functions, srcDir, outDir) {
203
- const pagesDir = join(findVitepressRoot(outDir) ?? outDir, ".vitepress");
195
+ const pagesDir = join(findVitepressRoot$1(outDir) ?? outDir, ".vitepress");
204
196
  if (!existsSync(pagesDir)) mkdirSync(pagesDir, { recursive: true });
205
197
  const pagesPath = join(pagesDir, "_pages.js");
206
198
  const existingGroupText = readExistingGroupText(pagesPath);
@@ -236,12 +228,12 @@ function generatePages(functions, srcDir, outDir) {
236
228
  const linkPath = [linkPrefix, func.name].filter(Boolean).join("/");
237
229
  if (!current.items) current.items = [];
238
230
  current.items.push({
239
- text: func.jsdoc.menuTitle ?? "",
231
+ text: func.jsdoc.menuTitle ?? func.jsdoc.title ?? "",
240
232
  link: `/${linkPath}`
241
233
  });
242
234
  }
243
235
  if (root.items) sortPages(root.items);
244
- writeFileSync(pagesPath, `// @ts-nocheck\n/* eslint-disable */\n/* prettier-ignore-start */\n/**\n * 此文件由 func2md 自动生成。\n * 仅允许修改菜单分组的 text 字段。\n */\nconst pages = ${JSON.stringify(root.items ?? [], null, 2)}\n/* prettier-ignore-end */\n\nexport default pages\n\n/**\n * 获取页面记录。\n * @param text 仅支持一级分组的 text;为空时返回全部。\n */\nexport function getRecords(text?: string) {\n if (!text) {\n return pages\n }\n return pages.filter((item) => item.text === text)\n}\n`);
236
+ writeFileSync(pagesPath, `// @ts-nocheck\n/* eslint-disable */\n/* prettier-ignore-start */\n/**\n * 此文件由 func2md 自动生成。\n * 仅允许修改菜单分组的 text 字段。\n */\nconst pages = ${JSON.stringify(root.items ?? [], null, 2)}\n/* prettier-ignore-end */\n\nexport default pages\n`);
245
237
  }
246
238
  async function scanAndGenerateDocs(srcDir, outDir) {
247
239
  console.log(`Scanning ${srcDir} and generating docs to ${outDir}`);
@@ -261,6 +253,43 @@ async function scanAndGenerateDocs(srcDir, outDir) {
261
253
  generatePages(allFunctions, srcDir, outDir);
262
254
  }
263
255
 
256
+ //#endregion
257
+ //#region src/core/pages.ts
258
+ function findVitepressRoot(outDir) {
259
+ let current = resolve(outDir);
260
+ while (true) {
261
+ if (existsSync(join(current, ".vitepress"))) return current;
262
+ const parent = resolve(current, "..");
263
+ if (parent === current) break;
264
+ current = parent;
265
+ }
266
+ return null;
267
+ }
268
+ function resolvePagesPath(outDir) {
269
+ return join(findVitepressRoot(outDir) ?? resolve(outDir), ".vitepress", "_pages.js");
270
+ }
271
+ function readPages(pagesPath) {
272
+ if (!existsSync(pagesPath)) return [];
273
+ const raw = readFileSync(pagesPath, "utf-8").trim();
274
+ const arrayMatch = raw.match(/const\s+pages\s*=\s*(\[[\s\S]*\])\s*;?/) ?? raw.match(/export\s+default\s+(\[[\s\S]*\])\s*;?\s*$/);
275
+ if (!arrayMatch) return [];
276
+ try {
277
+ return JSON.parse(arrayMatch[1]);
278
+ } catch {
279
+ return [];
280
+ }
281
+ }
282
+ /**
283
+ * 获取 _pages.js 中的页面记录。
284
+ * @param options 读取选项
285
+ */
286
+ function getRecords(options = {}) {
287
+ const { pagesPath, outDir = "docs", text } = options;
288
+ const pages = readPages(pagesPath ? resolve(pagesPath) : resolvePagesPath(outDir));
289
+ if (!text) return pages;
290
+ return pages.filter((item) => item.text === text);
291
+ }
292
+
264
293
  //#endregion
265
294
  //#region src/index.ts
266
295
  function func2md(options = {}) {
@@ -280,5 +309,5 @@ function func2md(options = {}) {
280
309
  }
281
310
 
282
311
  //#endregion
283
- export { func2md as default, scanAndGenerateDocs };
312
+ export { func2md as default, getRecords, scanAndGenerateDocs };
284
313
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/utils/file-utils.ts","../src/utils/jsdoc-parser.ts","../src/utils/function-extractor.ts","../src/generators/markdown-generator.ts","../src/core/scanner.ts","../src/index.ts"],"sourcesContent":["import { readdirSync, statSync, existsSync } from 'fs'\nimport { join, extname } from 'path'\n\nexport function findAllFiles(dir: string, extensions: string[]): string[] {\n if (!existsSync(dir)) {\n return []\n }\n\n let results: string[] = []\n\n function traverse(currentDir: string) {\n const files = readdirSync(currentDir)\n\n for (const file of files) {\n const filePath = join(currentDir, file)\n const stat = statSync(filePath)\n\n if (stat.isDirectory()) {\n traverse(filePath)\n } else if (stat.isFile() && extensions.includes(extname(file))) {\n results.push(filePath)\n }\n }\n }\n\n traverse(dir)\n return results\n}","export interface JSDocInfo {\n title?: string\n menuTitle?: string\n desc?: string\n example?: string\n params?: Array<{\n name: string\n type: string\n description: string\n }>\n returns?: {\n type: string\n description: string\n }\n signature?: string\n}\n\nexport function parseJSDoc(jsdoc: string): JSDocInfo {\n const lines = jsdoc.trim().split('\\n').map(line => line.replace(/^\\s*\\*\\s?/, ''))\n \n const info: JSDocInfo = {}\n \n // Extract title (first line or @title tag)\n const titleMatch = lines.find(line => line.startsWith('@title'))\n if (titleMatch) {\n info.title = titleMatch.replace('@title', '').trim()\n } else if (lines.length > 0) {\n info.title = lines[0]\n }\n\n // Extract menu title (@MenuTitle)\n const menuTitleMatch = lines.find(line => /@MenuTitle\\b/i.test(line))\n if (menuTitleMatch) {\n info.menuTitle = menuTitleMatch.replace(/@MenuTitle/i, '').trim()\n }\n \n // Extract description - all lines that don't start with @\n const descLines: string[] = []\n let inExample = false\n for (const line of lines) {\n // Stop collecting description when we hit @example or @param or @returns\n if (line.startsWith('@example') || line.startsWith('@param') || line.startsWith('@returns') || line.startsWith('@return')) {\n break\n }\n \n // Only add non-empty lines that don't start with @title to description\n if (!line.startsWith('@title') && line.trim() !== '') {\n descLines.push(line)\n }\n }\n \n if (descLines.length > 0) {\n info.desc = descLines.join(' ').trim()\n }\n \n // Extract example\n const exampleLines: string[] = []\n let collectingExample = false\n for (const line of lines) {\n if (line.startsWith('@example')) {\n collectingExample = true\n const exampleContent = line.replace('@example', '').trim()\n if (exampleContent) {\n exampleLines.push(exampleContent)\n }\n continue\n }\n \n // Continue collecting example lines until we hit another tag\n if (collectingExample) {\n if (line.startsWith('@')) {\n collectingExample = false\n } else {\n exampleLines.push(line)\n }\n }\n }\n \n if (exampleLines.length > 0) {\n info.example = exampleLines.join('\\n').trim()\n }\n \n // Extract parameters\n info.params = []\n lines.forEach(line => {\n // Match @param with or without type: @param {string} name - description or @param name - description\n const paramMatch = line.match(/@param\\s+(?:{([^}]+)})?\\s*(\\w+)\\s*-\\s*(.+)/)\n if (paramMatch) {\n info.params!.push({\n name: paramMatch[2],\n type: paramMatch[1] || 'any',\n description: paramMatch[3]\n })\n }\n })\n \n // Extract return value\n const returnMatch = lines.find(line => line.startsWith('@returns') || line.startsWith('@return'))\n if (returnMatch) {\n const returnInfo = returnMatch.replace(/@(returns?)/, '').trim()\n const typeMatch = returnInfo.match(/^{([^}]+)}\\s*(.*)/)\n if (typeMatch) {\n info.returns = {\n type: typeMatch[1],\n description: typeMatch[2]\n }\n } else {\n info.returns = {\n type: 'unknown',\n description: returnInfo\n }\n }\n }\n \n return info\n}\n","import { parseJSDoc, JSDocInfo } from './jsdoc-parser'\n\nexport interface FunctionInfo {\n name: string\n jsdoc: JSDocInfo\n signature?: string\n filePath: string\n}\n\nexport function extractFunctions(content: string, filePath: string): FunctionInfo[] {\n // This is a simplified implementation\n // In a real implementation, we would use AST parsing (e.g., with TypeScript Compiler API)\n \n const functions: FunctionInfo[] = []\n \n // Match function declarations with JSDoc comments\n // Using a string to avoid regex escaping issues\n const functionRegex = new RegExp(\n String.raw`/\\*\\*((?:.|\\n|\\r)*?)\\*/\\s*(export\\s+)?(async\\s+)?function\\s+(\\w+)`,\n 'g'\n )\n let match\n \n while ((match = functionRegex.exec(content)) !== null) {\n const [, jsdocRaw, , , functionName] = match\n const jsdoc = parseJSDoc(jsdocRaw)\n \n functions.push({\n name: functionName,\n jsdoc,\n filePath\n })\n }\n \n return functions\n}","import { FunctionInfo } from '../utils/function-extractor'\n\n// Generate the main title section\nexport function generateTitleSection(func: FunctionInfo): string {\n const { name, jsdoc } = func\n const title = jsdoc.title || name\n return `# ${title}\\n\\n`\n}\n\n// Generate the description section\nexport function generateDescriptionSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.desc) {\n return `## 说明\\n\\n${jsdoc.desc}\\n\\n`\n }\n return ''\n}\n\n// Generate the signature section\nexport function generateSignatureSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.signature) {\n return `## 签名\\n\\n\\`\\`\\`ts\\n${jsdoc.signature}\\n\\`\\`\\`\\n\\n`\n }\n return ''\n}\n\n// Generate the parameters section\nexport function generateParametersSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.params && jsdoc.params.length > 0) {\n let section = `## 参数\\n\\n`\n section += `| 参数名 | 类型 | 说明 |\\n|--------|------|------|\\n`\n jsdoc.params.forEach(param => {\n section += `| ${param.name} | \\`${param.type}\\` | ${param.description} |\\n`\n })\n section += `\\n`\n return section\n }\n return ''\n}\n\n// Generate the returns section\nexport function generateReturnsSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.returns) {\n return `## 返回值\\n\\n- 类型: \\`${jsdoc.returns.type}\\`\\n- 说明: ${jsdoc.returns.description}\\n\\n`\n }\n return ''\n}\n\n// Generate the example section\nexport function generateExampleSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.example) {\n return `## 示例\\n\\n\\`\\`\\`ts\\n${jsdoc.example}\\n\\`\\`\\`\\n\\n`\n }\n return ''\n}\n\n// Main function to generate markdown content\nexport function generateMarkdown(func: FunctionInfo): string {\n let md = generateTitleSection(func)\n md += generateDescriptionSection(func)\n md += generateSignatureSection(func)\n md += generateParametersSection(func)\n md += generateReturnsSection(func)\n md += generateExampleSection(func)\n return md\n}","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'\nimport { join, relative, dirname, resolve, sep } from 'path'\nimport { findAllFiles } from '../utils/file-utils'\nimport { extractFunctions } from '../utils/function-extractor'\nimport { generateMarkdown } from '../generators/markdown-generator'\n\ninterface PageNode {\n text: string\n items?: PageNode[]\n link?: string\n dir?: string\n}\n\nfunction toPosixPath(pathValue: string) {\n return pathValue.split(sep).join('/')\n}\n\nfunction findVitepressRoot(outDir: string): string | null {\n let current = resolve(outDir)\n while (true) {\n const configDir = join(current, '.vitepress')\n if (existsSync(configDir)) {\n const hasConfig = ['config.ts', 'config.js', 'config.mjs', 'config.cjs']\n .some(file => existsSync(join(configDir, file)))\n if (hasConfig) {\n return current\n }\n }\n const parent = dirname(current)\n if (parent === current) {\n break\n }\n current = parent\n }\n return null\n}\n\nfunction resolveLinkPrefix(outDir: string): string {\n const resolvedOutDir = resolve(outDir)\n const vitepressRoot = findVitepressRoot(resolvedOutDir)\n if (vitepressRoot) {\n const rel = relative(vitepressRoot, resolvedOutDir)\n return toPosixPath(rel)\n }\n const rel = relative(process.cwd(), resolvedOutDir)\n const parts = rel.split(sep).filter(Boolean)\n return parts.slice(1).join('/')\n}\n\nfunction readExistingGroupText(pagesPath: string): Map<string, string> {\n if (!existsSync(pagesPath)) {\n return new Map()\n }\n\n const raw = readFileSync(pagesPath, 'utf-8').trim()\n const arrayMatch = raw.match(/const\\s+pages\\s*=\\s*(\\[[\\s\\S]*\\])\\s*;?/)\n ?? raw.match(/export\\s+default\\s+(\\[[\\s\\S]*\\])\\s*;?\\s*$/)\n if (!arrayMatch) {\n return new Map()\n }\n\n try {\n const data = JSON.parse(arrayMatch[1]) as PageNode[]\n const map = new Map<string, string>()\n const walk = (nodes: PageNode[]) => {\n for (const node of nodes) {\n if (node.items && Array.isArray(node.items)) {\n if (typeof node.dir === 'string' && typeof node.text === 'string' && node.text.trim() !== '') {\n map.set(node.dir, node.text)\n }\n walk(node.items)\n }\n }\n }\n walk(data)\n return map\n } catch {\n return new Map()\n }\n}\n\nfunction sortPages(nodes: PageNode[]) {\n nodes.sort((a, b) => {\n const aIsGroup = Array.isArray(a.items)\n const bIsGroup = Array.isArray(b.items)\n if (aIsGroup && !bIsGroup) return -1\n if (!aIsGroup && bIsGroup) return 1\n if (aIsGroup && bIsGroup) {\n return (a.dir || '').localeCompare(b.dir || '')\n }\n return (a.link || '').localeCompare(b.link || '')\n })\n\n for (const node of nodes) {\n if (node.items && Array.isArray(node.items)) {\n sortPages(node.items)\n }\n }\n}\n\nfunction generatePages(functions: ReturnType<typeof extractFunctions>, srcDir: string, outDir: string) {\n const vitepressRoot = findVitepressRoot(outDir)\n const pagesBaseDir = vitepressRoot ?? outDir\n const pagesDir = join(pagesBaseDir, '.vitepress')\n if (!existsSync(pagesDir)) {\n mkdirSync(pagesDir, { recursive: true })\n }\n\n const pagesPath = join(pagesDir, '_pages.js')\n const existingGroupText = readExistingGroupText(pagesPath)\n const linkPrefix = resolveLinkPrefix(outDir)\n\n const root: PageNode = { text: '', items: [] }\n const sortedFunctions = [...functions].sort((a, b) => {\n const fileCompare = a.filePath.localeCompare(b.filePath)\n if (fileCompare !== 0) return fileCompare\n return a.name.localeCompare(b.name)\n })\n\n for (const func of sortedFunctions) {\n const relativePath = relative(srcDir, func.filePath)\n const dirPath = dirname(relativePath)\n const parts = dirPath === '.' ? [] : dirPath.split(sep)\n\n let current = root\n let currentDir = ''\n for (const part of parts) {\n currentDir = currentDir ? `${currentDir}/${part}` : part\n if (!current.items) {\n current.items = []\n }\n let next = current.items.find(item => item.items && item.dir === currentDir)\n if (!next) {\n const preservedText = existingGroupText.get(currentDir)\n next = {\n text: preservedText ?? '',\n items: [],\n dir: currentDir\n }\n current.items.push(next)\n }\n current = next\n }\n\n const linkPath = [linkPrefix, func.name].filter(Boolean).join('/')\n if (!current.items) {\n current.items = []\n }\n current.items.push({\n text: func.jsdoc.menuTitle ?? '',\n link: `/${linkPath}`\n })\n }\n\n if (root.items) {\n sortPages(root.items)\n }\n\n const pagesJson = JSON.stringify(root.items ?? [], null, 2)\n const output = `// @ts-nocheck\\n/* eslint-disable */\\n/* prettier-ignore-start */\\n/**\\n * 此文件由 func2md 自动生成。\\n * 仅允许修改菜单分组的 text 字段。\\n */\\nconst pages = ${pagesJson}\\n/* prettier-ignore-end */\\n\\nexport default pages\\n\\n/**\\n * 获取页面记录。\\n * @param text 仅支持一级分组的 text;为空时返回全部。\\n */\\nexport function getRecords(text?: string) {\\n if (!text) {\\n return pages\\n }\\n return pages.filter((item) => item.text === text)\\n}\\n`\n writeFileSync(pagesPath, output)\n}\n\nexport async function scanAndGenerateDocs(srcDir: string, outDir: string) {\n console.log(`Scanning ${srcDir} and generating docs to ${outDir}`)\n\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true })\n }\n \n // Find all .ts and .js files in srcDir\n const files = findAllFiles(srcDir, ['.ts', '.js'])\n \n const allFunctions = [] as ReturnType<typeof extractFunctions>\n\n for (const file of files) {\n const content = readFileSync(file, 'utf-8')\n const functions = extractFunctions(content, file)\n\n allFunctions.push(...functions)\n \n for (const func of functions) {\n const mdContent = generateMarkdown(func)\n const outputPath = join(outDir, `${func.name}.md`)\n writeFileSync(outputPath, mdContent)\n console.log(`Generated documentation: ${outputPath}`)\n }\n }\n\n generatePages(allFunctions, srcDir, outDir)\n}\n","import type { Plugin } from 'vite'\nimport { existsSync, mkdirSync } from 'fs'\nimport { resolve } from 'path'\nimport { scanAndGenerateDocs } from './core/scanner'\n\nexport { scanAndGenerateDocs }\n\nexport interface Func2MdOptions {\n /**\n * Source directory to scan for functions\n * @default 'src'\n */\n srcDir?: string\n \n /**\n * Output directory for generated markdown files\n * @default 'docs'\n */\n outDir?: string\n \n /**\n * Whether to watch for changes\n * @default false\n */\n watch?: boolean\n}\n\nexport default function func2md(options: Func2MdOptions = {}): Plugin {\n const { \n srcDir = 'src', \n outDir = 'docs',\n watch = false\n } = options\n \n const resolvedSrcDir = resolve(srcDir)\n const resolvedOutDir = resolve(outDir)\n \n return {\n name: 'func2md',\n \n async buildStart() {\n if (!existsSync(resolvedOutDir)) {\n mkdirSync(resolvedOutDir, { recursive: true })\n }\n \n // Scan files and generate docs\n await scanAndGenerateDocs(resolvedSrcDir, resolvedOutDir)\n },\n \n async handleHotUpdate(ctx) {\n if (watch && ctx.file.includes(resolvedSrcDir)) {\n await scanAndGenerateDocs(resolvedSrcDir, resolvedOutDir)\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["findVitepressRoot"],"sources":["../src/utils/file-utils.ts","../src/utils/jsdoc-parser.ts","../src/utils/function-extractor.ts","../src/generators/markdown-generator.ts","../src/core/scanner.ts","../src/core/pages.ts","../src/index.ts"],"sourcesContent":["import { readdirSync, statSync, existsSync } from 'fs'\nimport { join, extname } from 'path'\n\nexport function findAllFiles(dir: string, extensions: string[]): string[] {\n if (!existsSync(dir)) {\n return []\n }\n\n let results: string[] = []\n\n function traverse(currentDir: string) {\n const files = readdirSync(currentDir)\n\n for (const file of files) {\n const filePath = join(currentDir, file)\n const stat = statSync(filePath)\n\n if (stat.isDirectory()) {\n traverse(filePath)\n } else if (stat.isFile() && extensions.includes(extname(file))) {\n results.push(filePath)\n }\n }\n }\n\n traverse(dir)\n return results\n}","export interface JSDocInfo {\n title?: string\n menuTitle?: string\n desc?: string\n example?: string\n params?: Array<{\n name: string\n type: string\n description: string\n }>\n returns?: {\n type: string\n description: string\n }\n signature?: string\n}\n\nexport function parseJSDoc(jsdoc: string): JSDocInfo {\n const lines = jsdoc.trim().split('\\n').map(line => line.replace(/^\\s*\\*\\s?/, ''))\n \n const info: JSDocInfo = {}\n \n // Extract title (first line or @title tag)\n const titleMatch = lines.find(line => line.startsWith('@title'))\n if (titleMatch) {\n info.title = titleMatch.replace('@title', '').trim()\n } else if (lines.length > 0) {\n info.title = lines[0]\n }\n\n // Extract menu title (@MenuTitle)\n const menuTitleMatch = lines.find(line => /@MenuTitle\\b/i.test(line))\n if (menuTitleMatch) {\n info.menuTitle = menuTitleMatch.replace(/@MenuTitle/i, '').trim()\n }\n \n // Extract description - all lines that don't start with @\n const descLines: string[] = []\n let inExample = false\n for (const line of lines) {\n // Stop collecting description when we hit @example or @param or @returns\n if (line.startsWith('@example') || line.startsWith('@param') || line.startsWith('@returns') || line.startsWith('@return')) {\n break\n }\n \n // Only add non-empty lines that don't start with @title to description\n if (!line.startsWith('@title') && line.trim() !== '') {\n descLines.push(line)\n }\n }\n \n if (descLines.length > 0) {\n info.desc = descLines.join(' ').trim()\n }\n \n // Extract example\n const exampleLines: string[] = []\n let collectingExample = false\n for (const line of lines) {\n if (line.startsWith('@example')) {\n collectingExample = true\n const exampleContent = line.replace('@example', '').trim()\n if (exampleContent) {\n exampleLines.push(exampleContent)\n }\n continue\n }\n \n // Continue collecting example lines until we hit another tag\n if (collectingExample) {\n if (line.startsWith('@')) {\n collectingExample = false\n } else {\n exampleLines.push(line)\n }\n }\n }\n \n if (exampleLines.length > 0) {\n info.example = exampleLines.join('\\n').trim()\n }\n \n // Extract parameters\n info.params = []\n lines.forEach(line => {\n // Match @param with or without type: @param {string} name - description or @param name - description\n const paramMatch = line.match(/@param\\s+(?:{([^}]+)})?\\s*(\\w+)\\s*-\\s*(.+)/)\n if (paramMatch) {\n info.params!.push({\n name: paramMatch[2],\n type: paramMatch[1] || 'any',\n description: paramMatch[3]\n })\n }\n })\n \n // Extract return value\n const returnMatch = lines.find(line => line.startsWith('@returns') || line.startsWith('@return'))\n if (returnMatch) {\n const returnInfo = returnMatch.replace(/@(returns?)/, '').trim()\n const typeMatch = returnInfo.match(/^{([^}]+)}\\s*(.*)/)\n if (typeMatch) {\n info.returns = {\n type: typeMatch[1],\n description: typeMatch[2]\n }\n } else {\n info.returns = {\n type: 'unknown',\n description: returnInfo\n }\n }\n }\n \n return info\n}\n","import { parseJSDoc, JSDocInfo } from './jsdoc-parser'\n\nexport interface FunctionInfo {\n name: string\n jsdoc: JSDocInfo\n signature?: string\n filePath: string\n}\n\nexport function extractFunctions(content: string, filePath: string): FunctionInfo[] {\n // This is a simplified implementation\n // In a real implementation, we would use AST parsing (e.g., with TypeScript Compiler API)\n \n const functions: FunctionInfo[] = []\n \n // Match function declarations with JSDoc comments\n // Using a string to avoid regex escaping issues\n const functionRegex = new RegExp(\n String.raw`/\\*\\*((?:.|\\n|\\r)*?)\\*/\\s*(export\\s+)?(async\\s+)?function\\s+(\\w+)`,\n 'g'\n )\n let match\n \n while ((match = functionRegex.exec(content)) !== null) {\n const [, jsdocRaw, , , functionName] = match\n const jsdoc = parseJSDoc(jsdocRaw)\n \n functions.push({\n name: functionName,\n jsdoc,\n filePath\n })\n }\n \n return functions\n}","import { FunctionInfo } from '../utils/function-extractor'\n\n// Generate the main title section\nexport function generateTitleSection(func: FunctionInfo): string {\n const { name, jsdoc } = func\n const title = jsdoc.title || name\n return `# ${title}\\n\\n`\n}\n\n// Generate the description section\nexport function generateDescriptionSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.desc) {\n return `## 说明\\n\\n${jsdoc.desc}\\n\\n`\n }\n return ''\n}\n\n// Generate the signature section\nexport function generateSignatureSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.signature) {\n return `## 签名\\n\\n\\`\\`\\`ts\\n${jsdoc.signature}\\n\\`\\`\\`\\n\\n`\n }\n return ''\n}\n\n// Generate the parameters section\nexport function generateParametersSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.params && jsdoc.params.length > 0) {\n let section = `## 参数\\n\\n`\n section += `| 参数名 | 类型 | 说明 |\\n|--------|------|------|\\n`\n jsdoc.params.forEach(param => {\n section += `| ${param.name} | \\`${param.type}\\` | ${param.description} |\\n`\n })\n section += `\\n`\n return section\n }\n return ''\n}\n\n// Generate the returns section\nexport function generateReturnsSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.returns) {\n return `## 返回值\\n\\n- 类型: \\`${jsdoc.returns.type}\\`\\n- 说明: ${jsdoc.returns.description}\\n\\n`\n }\n return ''\n}\n\n// Generate the example section\nexport function generateExampleSection(func: FunctionInfo): string {\n const { jsdoc } = func\n if (jsdoc.example) {\n return `## 示例\\n\\n\\`\\`\\`ts\\n${jsdoc.example}\\n\\`\\`\\`\\n\\n`\n }\n return ''\n}\n\n// Main function to generate markdown content\nexport function generateMarkdown(func: FunctionInfo): string {\n let md = generateTitleSection(func)\n md += generateDescriptionSection(func)\n md += generateSignatureSection(func)\n md += generateParametersSection(func)\n md += generateReturnsSection(func)\n md += generateExampleSection(func)\n return md\n}","import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'\nimport { join, relative, dirname, resolve, sep } from 'path'\nimport { findAllFiles } from '../utils/file-utils'\nimport { extractFunctions } from '../utils/function-extractor'\nimport { generateMarkdown } from '../generators/markdown-generator'\n\ninterface PageNode {\n text: string\n items?: PageNode[]\n link?: string\n dir?: string\n}\n\nfunction toPosixPath(pathValue: string) {\n return pathValue.split(sep).join('/')\n}\n\nfunction findVitepressRoot(outDir: string): string | null {\n let current = resolve(outDir)\n while (true) {\n const configDir = join(current, '.vitepress')\n // 如果存在 .vitepress 目录,无论是否有配置文件都认为这是 VitePress 根目录\n if (existsSync(configDir)) {\n return current\n }\n const parent = dirname(current)\n if (parent === current) {\n break\n }\n current = parent\n }\n return null\n}\n\nfunction resolveLinkPrefix(outDir: string): string {\n const resolvedOutDir = resolve(outDir)\n const vitepressRoot = findVitepressRoot(resolvedOutDir)\n if (vitepressRoot) {\n const rel = relative(vitepressRoot, resolvedOutDir)\n return toPosixPath(rel)\n }\n const rel = relative(process.cwd(), resolvedOutDir)\n const parts = rel.split(sep).filter(Boolean)\n return parts.slice(1).join('/')\n}\n\nfunction readExistingGroupText(pagesPath: string): Map<string, string> {\n if (!existsSync(pagesPath)) {\n return new Map()\n }\n\n const raw = readFileSync(pagesPath, 'utf-8').trim()\n const arrayMatch = raw.match(/const\\s+pages\\s*=\\s*(\\[[\\s\\S]*\\])\\s*;?/)\n ?? raw.match(/export\\s+default\\s+(\\[[\\s\\S]*\\])\\s*;?\\s*$/)\n if (!arrayMatch) {\n return new Map()\n }\n\n try {\n const data = JSON.parse(arrayMatch[1]) as PageNode[]\n const map = new Map<string, string>()\n const walk = (nodes: PageNode[]) => {\n for (const node of nodes) {\n if (node.items && Array.isArray(node.items)) {\n if (typeof node.dir === 'string' && typeof node.text === 'string' && node.text.trim() !== '') {\n map.set(node.dir, node.text)\n }\n walk(node.items)\n }\n }\n }\n walk(data)\n return map\n } catch {\n return new Map()\n }\n}\n\nfunction sortPages(nodes: PageNode[]) {\n nodes.sort((a, b) => {\n const aIsGroup = Array.isArray(a.items)\n const bIsGroup = Array.isArray(b.items)\n if (aIsGroup && !bIsGroup) return -1\n if (!aIsGroup && bIsGroup) return 1\n if (aIsGroup && bIsGroup) {\n return (a.dir || '').localeCompare(b.dir || '')\n }\n return (a.link || '').localeCompare(b.link || '')\n })\n\n for (const node of nodes) {\n if (node.items && Array.isArray(node.items)) {\n sortPages(node.items)\n }\n }\n}\n\nfunction generatePages(functions: ReturnType<typeof extractFunctions>, srcDir: string, outDir: string) {\n const vitepressRoot = findVitepressRoot(outDir)\n const pagesBaseDir = vitepressRoot ?? outDir\n const pagesDir = join(pagesBaseDir, '.vitepress')\n if (!existsSync(pagesDir)) {\n mkdirSync(pagesDir, { recursive: true })\n }\n\n const pagesPath = join(pagesDir, '_pages.js')\n const existingGroupText = readExistingGroupText(pagesPath)\n const linkPrefix = resolveLinkPrefix(outDir)\n\n const root: PageNode = { text: '', items: [] }\n const sortedFunctions = [...functions].sort((a, b) => {\n const fileCompare = a.filePath.localeCompare(b.filePath)\n if (fileCompare !== 0) return fileCompare\n return a.name.localeCompare(b.name)\n })\n\n for (const func of sortedFunctions) {\n const relativePath = relative(srcDir, func.filePath)\n const dirPath = dirname(relativePath)\n const parts = dirPath === '.' ? [] : dirPath.split(sep)\n\n let current = root\n let currentDir = ''\n for (const part of parts) {\n currentDir = currentDir ? `${currentDir}/${part}` : part\n if (!current.items) {\n current.items = []\n }\n let next = current.items.find(item => item.items && item.dir === currentDir)\n if (!next) {\n const preservedText = existingGroupText.get(currentDir)\n next = {\n text: preservedText ?? '',\n items: [],\n dir: currentDir\n }\n current.items.push(next)\n }\n current = next\n }\n\n const linkPath = [linkPrefix, func.name].filter(Boolean).join('/')\n if (!current.items) {\n current.items = []\n }\n current.items.push({\n text: func.jsdoc.menuTitle ?? func.jsdoc.title ?? '',\n link: `/${linkPath}`\n })\n }\n\n if (root.items) {\n sortPages(root.items)\n }\n\n const pagesJson = JSON.stringify(root.items ?? [], null, 2)\n const output = `// @ts-nocheck\\n/* eslint-disable */\\n/* prettier-ignore-start */\\n/**\\n * 此文件由 func2md 自动生成。\\n * 仅允许修改菜单分组的 text 字段。\\n */\\nconst pages = ${pagesJson}\\n/* prettier-ignore-end */\\n\\nexport default pages\\n`\n writeFileSync(pagesPath, output)\n}\n\nexport async function scanAndGenerateDocs(srcDir: string, outDir: string) {\n console.log(`Scanning ${srcDir} and generating docs to ${outDir}`)\n\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true })\n }\n \n // Find all .ts and .js files in srcDir\n const files = findAllFiles(srcDir, ['.ts', '.js'])\n \n const allFunctions = [] as ReturnType<typeof extractFunctions>\n\n for (const file of files) {\n const content = readFileSync(file, 'utf-8')\n const functions = extractFunctions(content, file)\n\n allFunctions.push(...functions)\n \n for (const func of functions) {\n const mdContent = generateMarkdown(func)\n const outputPath = join(outDir, `${func.name}.md`)\n writeFileSync(outputPath, mdContent)\n console.log(`Generated documentation: ${outputPath}`)\n }\n }\n\n generatePages(allFunctions, srcDir, outDir)\n}\n","import { readFileSync, existsSync } from 'fs'\nimport { resolve, join } from 'path'\n\nexport interface PageNode {\n text: string\n items?: PageNode[]\n link?: string\n dir?: string\n}\n\nexport interface GetRecordsOptions {\n /**\n * 指定 _pages.js 的完整路径。\n * 当提供该值时,会忽略 outDir。\n */\n pagesPath?: string\n /**\n * 指定生成文档的输出目录(默认 docs)。\n * 会自动定位到 VitePress 根目录下的 .vitepress/_pages.js。\n */\n outDir?: string\n /**\n * 仅返回一级分组的 text 匹配项。\n * 为空时返回全部。\n */\n text?: string\n}\n\nfunction findVitepressRoot(outDir: string): string | null {\n let current = resolve(outDir)\n while (true) {\n const configDir = join(current, '.vitepress')\n if (existsSync(configDir)) {\n return current\n }\n const parent = resolve(current, '..')\n if (parent === current) {\n break\n }\n current = parent\n }\n return null\n}\n\nfunction resolvePagesPath(outDir: string): string {\n const vitepressRoot = findVitepressRoot(outDir)\n const baseDir = vitepressRoot ?? resolve(outDir)\n return join(baseDir, '.vitepress', '_pages.js')\n}\n\nfunction readPages(pagesPath: string): PageNode[] {\n if (!existsSync(pagesPath)) {\n return []\n }\n\n const raw = readFileSync(pagesPath, 'utf-8').trim()\n const arrayMatch = raw.match(/const\\s+pages\\s*=\\s*(\\[[\\s\\S]*\\])\\s*;?/) ?? raw.match(/export\\s+default\\s+(\\[[\\s\\S]*\\])\\s*;?\\s*$/)\n if (!arrayMatch) {\n return []\n }\n\n try {\n return JSON.parse(arrayMatch[1]) as PageNode[]\n } catch {\n return []\n }\n}\n\n/**\n * 获取 _pages.js 中的页面记录。\n * @param options 读取选项\n */\nexport function getRecords(options: GetRecordsOptions = {}): PageNode[] {\n const { pagesPath, outDir = 'docs', text } = options\n const targetPath = pagesPath ? resolve(pagesPath) : resolvePagesPath(outDir)\n const pages = readPages(targetPath)\n\n if (!text) {\n return pages\n }\n return pages.filter(item => item.text === text)\n}\n","import type { Plugin } from 'vite'\nimport { existsSync, mkdirSync } from 'fs'\nimport { resolve } from 'path'\nimport { scanAndGenerateDocs } from './core/scanner'\nimport { getRecords } from './core/pages'\n\nexport { scanAndGenerateDocs, getRecords }\n\nexport interface Func2MdOptions {\n /**\n * Source directory to scan for functions\n * @default 'src'\n */\n srcDir?: string\n \n /**\n * Output directory for generated markdown files\n * @default 'docs'\n */\n outDir?: string\n \n /**\n * Whether to watch for changes\n * @default false\n */\n watch?: boolean\n}\n\nexport default function func2md(options: Func2MdOptions = {}): Plugin {\n const { \n srcDir = 'src', \n outDir = 'docs',\n watch = false\n } = options\n \n const resolvedSrcDir = resolve(srcDir)\n const resolvedOutDir = resolve(outDir)\n \n return {\n name: 'func2md',\n \n async buildStart() {\n if (!existsSync(resolvedOutDir)) {\n mkdirSync(resolvedOutDir, { recursive: true })\n }\n \n // Scan files and generate docs\n await scanAndGenerateDocs(resolvedSrcDir, resolvedOutDir)\n },\n \n async handleHotUpdate(ctx) {\n if (watch && ctx.file.includes(resolvedSrcDir)) {\n await scanAndGenerateDocs(resolvedSrcDir, resolvedOutDir)\n }\n }\n }\n}\n"],"mappings":";;;;AAGA,SAAgB,aAAa,KAAa,YAAgC;AACxE,KAAI,CAAC,WAAW,IAAI,CAClB,QAAO,EAAE;CAGX,IAAI,UAAoB,EAAE;CAE1B,SAAS,SAAS,YAAoB;EACpC,MAAM,QAAQ,YAAY,WAAW;AAErC,OAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,WAAW,KAAK,YAAY,KAAK;GACvC,MAAM,OAAO,SAAS,SAAS;AAE/B,OAAI,KAAK,aAAa,CACpB,UAAS,SAAS;YACT,KAAK,QAAQ,IAAI,WAAW,SAAS,QAAQ,KAAK,CAAC,CAC5D,SAAQ,KAAK,SAAS;;;AAK5B,UAAS,IAAI;AACb,QAAO;;;;;ACTT,SAAgB,WAAW,OAA0B;CACnD,MAAM,QAAQ,MAAM,MAAM,CAAC,MAAM,KAAK,CAAC,KAAI,SAAQ,KAAK,QAAQ,aAAa,GAAG,CAAC;CAEjF,MAAM,OAAkB,EAAE;CAG1B,MAAM,aAAa,MAAM,MAAK,SAAQ,KAAK,WAAW,SAAS,CAAC;AAChE,KAAI,WACF,MAAK,QAAQ,WAAW,QAAQ,UAAU,GAAG,CAAC,MAAM;UAC3C,MAAM,SAAS,EACxB,MAAK,QAAQ,MAAM;CAIrB,MAAM,iBAAiB,MAAM,MAAK,SAAQ,gBAAgB,KAAK,KAAK,CAAC;AACrE,KAAI,eACF,MAAK,YAAY,eAAe,QAAQ,eAAe,GAAG,CAAC,MAAM;CAInE,MAAM,YAAsB,EAAE;AAE9B,MAAK,MAAM,QAAQ,OAAO;AAExB,MAAI,KAAK,WAAW,WAAW,IAAI,KAAK,WAAW,SAAS,IAAI,KAAK,WAAW,WAAW,IAAI,KAAK,WAAW,UAAU,CACvH;AAIF,MAAI,CAAC,KAAK,WAAW,SAAS,IAAI,KAAK,MAAM,KAAK,GAChD,WAAU,KAAK,KAAK;;AAIxB,KAAI,UAAU,SAAS,EACrB,MAAK,OAAO,UAAU,KAAK,IAAI,CAAC,MAAM;CAIxC,MAAM,eAAyB,EAAE;CACjC,IAAI,oBAAoB;AACxB,MAAK,MAAM,QAAQ,OAAO;AACxB,MAAI,KAAK,WAAW,WAAW,EAAE;AAC/B,uBAAoB;GACpB,MAAM,iBAAiB,KAAK,QAAQ,YAAY,GAAG,CAAC,MAAM;AAC1D,OAAI,eACF,cAAa,KAAK,eAAe;AAEnC;;AAIF,MAAI,kBACF,KAAI,KAAK,WAAW,IAAI,CACtB,qBAAoB;MAEpB,cAAa,KAAK,KAAK;;AAK7B,KAAI,aAAa,SAAS,EACxB,MAAK,UAAU,aAAa,KAAK,KAAK,CAAC,MAAM;AAI/C,MAAK,SAAS,EAAE;AAChB,OAAM,SAAQ,SAAQ;EAEpB,MAAM,aAAa,KAAK,MAAM,6CAA6C;AAC3E,MAAI,WACF,MAAK,OAAQ,KAAK;GAChB,MAAM,WAAW;GACjB,MAAM,WAAW,MAAM;GACvB,aAAa,WAAW;GACzB,CAAC;GAEJ;CAGF,MAAM,cAAc,MAAM,MAAK,SAAQ,KAAK,WAAW,WAAW,IAAI,KAAK,WAAW,UAAU,CAAC;AACjG,KAAI,aAAa;EACf,MAAM,aAAa,YAAY,QAAQ,eAAe,GAAG,CAAC,MAAM;EAChE,MAAM,YAAY,WAAW,MAAM,oBAAoB;AACvD,MAAI,UACF,MAAK,UAAU;GACb,MAAM,UAAU;GAChB,aAAa,UAAU;GACxB;MAED,MAAK,UAAU;GACb,MAAM;GACN,aAAa;GACd;;AAIL,QAAO;;;;;ACzGT,SAAgB,iBAAiB,SAAiB,UAAkC;CAIlF,MAAM,YAA4B,EAAE;CAIpC,MAAM,gBAAgB,IAAI,OACxB,OAAO,GAAG,qEACV,IACD;CACD,IAAI;AAEJ,SAAQ,QAAQ,cAAc,KAAK,QAAQ,MAAM,MAAM;EACrD,MAAM,GAAG,cAAc,gBAAgB;EACvC,MAAM,QAAQ,WAAW,SAAS;AAElC,YAAU,KAAK;GACb,MAAM;GACN;GACA;GACD,CAAC;;AAGJ,QAAO;;;;;AC/BT,SAAgB,qBAAqB,MAA4B;CAC/D,MAAM,EAAE,MAAM,UAAU;AAExB,QAAO,KADO,MAAM,SAAS,KACX;;AAIpB,SAAgB,2BAA2B,MAA4B;CACrE,MAAM,EAAE,UAAU;AAClB,KAAI,MAAM,KACR,QAAO,YAAY,MAAM,KAAK;AAEhC,QAAO;;AAIT,SAAgB,yBAAyB,MAA4B;CACnE,MAAM,EAAE,UAAU;AAClB,KAAI,MAAM,UACR,QAAO,sBAAsB,MAAM,UAAU;AAE/C,QAAO;;AAIT,SAAgB,0BAA0B,MAA4B;CACpE,MAAM,EAAE,UAAU;AAClB,KAAI,MAAM,UAAU,MAAM,OAAO,SAAS,GAAG;EAC3C,IAAI,UAAU;AACd,aAAW;AACX,QAAM,OAAO,SAAQ,UAAS;AAC5B,cAAW,KAAK,MAAM,KAAK,OAAO,MAAM,KAAK,OAAO,MAAM,YAAY;IACtE;AACF,aAAW;AACX,SAAO;;AAET,QAAO;;AAIT,SAAgB,uBAAuB,MAA4B;CACjE,MAAM,EAAE,UAAU;AAClB,KAAI,MAAM,QACR,QAAO,qBAAqB,MAAM,QAAQ,KAAK,YAAY,MAAM,QAAQ,YAAY;AAEvF,QAAO;;AAIT,SAAgB,uBAAuB,MAA4B;CACjE,MAAM,EAAE,UAAU;AAClB,KAAI,MAAM,QACR,QAAO,sBAAsB,MAAM,QAAQ;AAE7C,QAAO;;AAIT,SAAgB,iBAAiB,MAA4B;CAC3D,IAAI,KAAK,qBAAqB,KAAK;AACnC,OAAM,2BAA2B,KAAK;AACtC,OAAM,yBAAyB,KAAK;AACpC,OAAM,0BAA0B,KAAK;AACrC,OAAM,uBAAuB,KAAK;AAClC,OAAM,uBAAuB,KAAK;AAClC,QAAO;;;;;ACvDT,SAAS,YAAY,WAAmB;AACtC,QAAO,UAAU,MAAM,IAAI,CAAC,KAAK,IAAI;;AAGvC,SAASA,oBAAkB,QAA+B;CACxD,IAAI,UAAU,QAAQ,OAAO;AAC7B,QAAO,MAAM;AAGX,MAAI,WAFc,KAAK,SAAS,aAAa,CAEpB,CACvB,QAAO;EAET,MAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,WAAW,QACb;AAEF,YAAU;;AAEZ,QAAO;;AAGT,SAAS,kBAAkB,QAAwB;CACjD,MAAM,iBAAiB,QAAQ,OAAO;CACtC,MAAM,gBAAgBA,oBAAkB,eAAe;AACvD,KAAI,cAEF,QAAO,YADK,SAAS,eAAe,eAAe,CAC5B;AAIzB,QAFY,SAAS,QAAQ,KAAK,EAAE,eAAe,CACjC,MAAM,IAAI,CAAC,OAAO,QAAQ,CAC/B,MAAM,EAAE,CAAC,KAAK,IAAI;;AAGjC,SAAS,sBAAsB,WAAwC;AACrE,KAAI,CAAC,WAAW,UAAU,CACxB,wBAAO,IAAI,KAAK;CAGlB,MAAM,MAAM,aAAa,WAAW,QAAQ,CAAC,MAAM;CACnD,MAAM,aAAa,IAAI,MAAM,yCAAyC,IACjE,IAAI,MAAM,4CAA4C;AAC3D,KAAI,CAAC,WACH,wBAAO,IAAI,KAAK;AAGlB,KAAI;EACF,MAAM,OAAO,KAAK,MAAM,WAAW,GAAG;EACtC,MAAM,sBAAM,IAAI,KAAqB;EACrC,MAAM,QAAQ,UAAsB;AAClC,QAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,SAAS,MAAM,QAAQ,KAAK,MAAM,EAAE;AAC3C,QAAI,OAAO,KAAK,QAAQ,YAAY,OAAO,KAAK,SAAS,YAAY,KAAK,KAAK,MAAM,KAAK,GACxF,KAAI,IAAI,KAAK,KAAK,KAAK,KAAK;AAE9B,SAAK,KAAK,MAAM;;;AAItB,OAAK,KAAK;AACV,SAAO;SACD;AACN,yBAAO,IAAI,KAAK;;;AAIpB,SAAS,UAAU,OAAmB;AACpC,OAAM,MAAM,GAAG,MAAM;EACnB,MAAM,WAAW,MAAM,QAAQ,EAAE,MAAM;EACvC,MAAM,WAAW,MAAM,QAAQ,EAAE,MAAM;AACvC,MAAI,YAAY,CAAC,SAAU,QAAO;AAClC,MAAI,CAAC,YAAY,SAAU,QAAO;AAClC,MAAI,YAAY,SACd,SAAQ,EAAE,OAAO,IAAI,cAAc,EAAE,OAAO,GAAG;AAEjD,UAAQ,EAAE,QAAQ,IAAI,cAAc,EAAE,QAAQ,GAAG;GACjD;AAEF,MAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,SAAS,MAAM,QAAQ,KAAK,MAAM,CACzC,WAAU,KAAK,MAAM;;AAK3B,SAAS,cAAc,WAAgD,QAAgB,QAAgB;CAGrG,MAAM,WAAW,KAFKA,oBAAkB,OAAO,IACT,QACF,aAAa;AACjD,KAAI,CAAC,WAAW,SAAS,CACvB,WAAU,UAAU,EAAE,WAAW,MAAM,CAAC;CAG1C,MAAM,YAAY,KAAK,UAAU,YAAY;CAC7C,MAAM,oBAAoB,sBAAsB,UAAU;CAC1D,MAAM,aAAa,kBAAkB,OAAO;CAE5C,MAAM,OAAiB;EAAE,MAAM;EAAI,OAAO,EAAE;EAAE;CAC9C,MAAM,kBAAkB,CAAC,GAAG,UAAU,CAAC,MAAM,GAAG,MAAM;EACpD,MAAM,cAAc,EAAE,SAAS,cAAc,EAAE,SAAS;AACxD,MAAI,gBAAgB,EAAG,QAAO;AAC9B,SAAO,EAAE,KAAK,cAAc,EAAE,KAAK;GACnC;AAEF,MAAK,MAAM,QAAQ,iBAAiB;EAElC,MAAM,UAAU,QADK,SAAS,QAAQ,KAAK,SAAS,CACf;EACrC,MAAM,QAAQ,YAAY,MAAM,EAAE,GAAG,QAAQ,MAAM,IAAI;EAEvD,IAAI,UAAU;EACd,IAAI,aAAa;AACjB,OAAK,MAAM,QAAQ,OAAO;AACxB,gBAAa,aAAa,GAAG,WAAW,GAAG,SAAS;AACpD,OAAI,CAAC,QAAQ,MACX,SAAQ,QAAQ,EAAE;GAEpB,IAAI,OAAO,QAAQ,MAAM,MAAK,SAAQ,KAAK,SAAS,KAAK,QAAQ,WAAW;AAC5E,OAAI,CAAC,MAAM;AAET,WAAO;KACL,MAFoB,kBAAkB,IAAI,WAAW,IAE9B;KACvB,OAAO,EAAE;KACT,KAAK;KACN;AACD,YAAQ,MAAM,KAAK,KAAK;;AAE1B,aAAU;;EAGZ,MAAM,WAAW,CAAC,YAAY,KAAK,KAAK,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;AAClE,MAAI,CAAC,QAAQ,MACX,SAAQ,QAAQ,EAAE;AAEpB,UAAQ,MAAM,KAAK;GACjB,MAAM,KAAK,MAAM,aAAa,KAAK,MAAM,SAAS;GAClD,MAAM,IAAI;GACX,CAAC;;AAGJ,KAAI,KAAK,MACP,WAAU,KAAK,MAAM;AAKvB,eAAc,WADC,6IADG,KAAK,UAAU,KAAK,SAAS,EAAE,EAAE,MAAM,EAAE,CAC2G,uDACtI;;AAGlC,eAAsB,oBAAoB,QAAgB,QAAgB;AACxE,SAAQ,IAAI,YAAY,OAAO,0BAA0B,SAAS;AAElE,KAAI,CAAC,WAAW,OAAO,CACrB,WAAU,QAAQ,EAAE,WAAW,MAAM,CAAC;CAIxC,MAAM,QAAQ,aAAa,QAAQ,CAAC,OAAO,MAAM,CAAC;CAElD,MAAM,eAAe,EAAE;AAEvB,MAAK,MAAM,QAAQ,OAAO;EAExB,MAAM,YAAY,iBADF,aAAa,MAAM,QAAQ,EACC,KAAK;AAEjD,eAAa,KAAK,GAAG,UAAU;AAE/B,OAAK,MAAM,QAAQ,WAAW;GAC5B,MAAM,YAAY,iBAAiB,KAAK;GACxC,MAAM,aAAa,KAAK,QAAQ,GAAG,KAAK,KAAK,KAAK;AAClD,iBAAc,YAAY,UAAU;AACpC,WAAQ,IAAI,4BAA4B,aAAa;;;AAIzD,eAAc,cAAc,QAAQ,OAAO;;;;;AC9J7C,SAAS,kBAAkB,QAA+B;CACxD,IAAI,UAAU,QAAQ,OAAO;AAC7B,QAAO,MAAM;AAEX,MAAI,WADc,KAAK,SAAS,aAAa,CACpB,CACvB,QAAO;EAET,MAAM,SAAS,QAAQ,SAAS,KAAK;AACrC,MAAI,WAAW,QACb;AAEF,YAAU;;AAEZ,QAAO;;AAGT,SAAS,iBAAiB,QAAwB;AAGhD,QAAO,KAFe,kBAAkB,OAAO,IACd,QAAQ,OAAO,EAC3B,cAAc,YAAY;;AAGjD,SAAS,UAAU,WAA+B;AAChD,KAAI,CAAC,WAAW,UAAU,CACxB,QAAO,EAAE;CAGX,MAAM,MAAM,aAAa,WAAW,QAAQ,CAAC,MAAM;CACnD,MAAM,aAAa,IAAI,MAAM,yCAAyC,IAAI,IAAI,MAAM,4CAA4C;AAChI,KAAI,CAAC,WACH,QAAO,EAAE;AAGX,KAAI;AACF,SAAO,KAAK,MAAM,WAAW,GAAG;SAC1B;AACN,SAAO,EAAE;;;;;;;AAQb,SAAgB,WAAW,UAA6B,EAAE,EAAc;CACtE,MAAM,EAAE,WAAW,SAAS,QAAQ,SAAS;CAE7C,MAAM,QAAQ,UADK,YAAY,QAAQ,UAAU,GAAG,iBAAiB,OAAO,CACzC;AAEnC,KAAI,CAAC,KACH,QAAO;AAET,QAAO,MAAM,QAAO,SAAQ,KAAK,SAAS,KAAK;;;;;ACpDjD,SAAwB,QAAQ,UAA0B,EAAE,EAAU;CACpE,MAAM,EACJ,SAAS,OACT,SAAS,QACT,QAAQ,UACN;CAEJ,MAAM,iBAAiB,QAAQ,OAAO;CACtC,MAAM,iBAAiB,QAAQ,OAAO;AAEtC,QAAO;EACL,MAAM;EAEN,MAAM,aAAa;AACjB,OAAI,CAAC,WAAW,eAAe,CAC7B,WAAU,gBAAgB,EAAE,WAAW,MAAM,CAAC;AAIhD,SAAM,oBAAoB,gBAAgB,eAAe;;EAG3D,MAAM,gBAAgB,KAAK;AACzB,OAAI,SAAS,IAAI,KAAK,SAAS,eAAe,CAC5C,OAAM,oBAAoB,gBAAgB,eAAe;;EAG9D"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "func2md",
3
3
  "type": "module",
4
- "version": "0.0.4",
4
+ "version": "0.1.0",
5
5
  "description": "A Vite plugin to generate VitePress documentation from JSDoc and TypeScript types",
6
6
  "main": "dist/index.js",
7
7
  "module": "dist/index.mjs",