func2md 0.0.5 → 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,7 +145,7 @@ 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
151
  if ((0, fs.existsSync)((0, path.join)(current, ".vitepress"))) return current;
@@ -157,7 +157,7 @@ function findVitepressRoot(outDir) {
157
157
  }
158
158
  function resolveLinkPrefix(outDir) {
159
159
  const resolvedOutDir = (0, path.resolve)(outDir);
160
- const vitepressRoot = findVitepressRoot(resolvedOutDir);
160
+ const vitepressRoot = findVitepressRoot$1(resolvedOutDir);
161
161
  if (vitepressRoot) return toPosixPath((0, path.relative)(vitepressRoot, resolvedOutDir));
162
162
  return (0, path.relative)(process.cwd(), resolvedOutDir).split(path.sep).filter(Boolean).slice(1).join("/");
163
163
  }
@@ -193,7 +193,7 @@ function sortPages(nodes) {
193
193
  for (const node of nodes) if (node.items && Array.isArray(node.items)) sortPages(node.items);
194
194
  }
195
195
  function generatePages(functions, srcDir, outDir) {
196
- const pagesDir = (0, path.join)(findVitepressRoot(outDir) ?? outDir, ".vitepress");
196
+ const pagesDir = (0, path.join)(findVitepressRoot$1(outDir) ?? outDir, ".vitepress");
197
197
  if (!(0, fs.existsSync)(pagesDir)) (0, fs.mkdirSync)(pagesDir, { recursive: true });
198
198
  const pagesPath = (0, path.join)(pagesDir, "_pages.js");
199
199
  const existingGroupText = readExistingGroupText(pagesPath);
@@ -229,12 +229,12 @@ function generatePages(functions, srcDir, outDir) {
229
229
  const linkPath = [linkPrefix, func.name].filter(Boolean).join("/");
230
230
  if (!current.items) current.items = [];
231
231
  current.items.push({
232
- text: func.jsdoc.menuTitle ?? "",
232
+ text: func.jsdoc.menuTitle ?? func.jsdoc.title ?? "",
233
233
  link: `/${linkPath}`
234
234
  });
235
235
  }
236
236
  if (root.items) sortPages(root.items);
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\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`);
238
238
  }
239
239
  async function scanAndGenerateDocs(srcDir, outDir) {
240
240
  console.log(`Scanning ${srcDir} and generating docs to ${outDir}`);
@@ -254,6 +254,43 @@ async function scanAndGenerateDocs(srcDir, outDir) {
254
254
  generatePages(allFunctions, srcDir, outDir);
255
255
  }
256
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
+
257
294
  //#endregion
258
295
  //#region src/index.ts
259
296
  function func2md(options = {}) {
@@ -274,5 +311,6 @@ function func2md(options = {}) {
274
311
 
275
312
  //#endregion
276
313
  exports.default = func2md;
314
+ exports.getRecords = getRecords;
277
315
  exports.scanAndGenerateDocs = scanAndGenerateDocs;
278
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 // 如果存在 .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 ?? '',\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":"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"}
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,7 +144,7 @@ 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
150
  if (existsSync(join(current, ".vitepress"))) return current;
@@ -156,7 +156,7 @@ function findVitepressRoot(outDir) {
156
156
  }
157
157
  function resolveLinkPrefix(outDir) {
158
158
  const resolvedOutDir = resolve(outDir);
159
- const vitepressRoot = findVitepressRoot(resolvedOutDir);
159
+ const vitepressRoot = findVitepressRoot$1(resolvedOutDir);
160
160
  if (vitepressRoot) return toPosixPath(relative(vitepressRoot, resolvedOutDir));
161
161
  return relative(process.cwd(), resolvedOutDir).split(sep).filter(Boolean).slice(1).join("/");
162
162
  }
@@ -192,7 +192,7 @@ function sortPages(nodes) {
192
192
  for (const node of nodes) if (node.items && Array.isArray(node.items)) sortPages(node.items);
193
193
  }
194
194
  function generatePages(functions, srcDir, outDir) {
195
- const pagesDir = join(findVitepressRoot(outDir) ?? outDir, ".vitepress");
195
+ const pagesDir = join(findVitepressRoot$1(outDir) ?? outDir, ".vitepress");
196
196
  if (!existsSync(pagesDir)) mkdirSync(pagesDir, { recursive: true });
197
197
  const pagesPath = join(pagesDir, "_pages.js");
198
198
  const existingGroupText = readExistingGroupText(pagesPath);
@@ -228,12 +228,12 @@ function generatePages(functions, srcDir, outDir) {
228
228
  const linkPath = [linkPrefix, func.name].filter(Boolean).join("/");
229
229
  if (!current.items) current.items = [];
230
230
  current.items.push({
231
- text: func.jsdoc.menuTitle ?? "",
231
+ text: func.jsdoc.menuTitle ?? func.jsdoc.title ?? "",
232
232
  link: `/${linkPath}`
233
233
  });
234
234
  }
235
235
  if (root.items) sortPages(root.items);
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\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`);
237
237
  }
238
238
  async function scanAndGenerateDocs(srcDir, outDir) {
239
239
  console.log(`Scanning ${srcDir} and generating docs to ${outDir}`);
@@ -253,6 +253,43 @@ async function scanAndGenerateDocs(srcDir, outDir) {
253
253
  generatePages(allFunctions, srcDir, outDir);
254
254
  }
255
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
+
256
293
  //#endregion
257
294
  //#region src/index.ts
258
295
  function func2md(options = {}) {
@@ -272,5 +309,5 @@ function func2md(options = {}) {
272
309
  }
273
310
 
274
311
  //#endregion
275
- export { func2md as default, scanAndGenerateDocs };
312
+ export { func2md as default, getRecords, scanAndGenerateDocs };
276
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 // 如果存在 .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 ?? '',\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":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "func2md",
3
3
  "type": "module",
4
- "version": "0.0.5",
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",