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 +15 -6
- package/README.zh-CN.md +24 -6
- package/dist/index.cjs +44 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -1
- package/dist/index.d.mts +31 -1
- package/dist/index.mjs +44 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
- `@
|
|
88
|
-
- `@
|
|
89
|
-
- `@
|
|
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
|
-
- `@
|
|
53
|
-
- `@
|
|
54
|
-
- `@
|
|
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
|
-
|
|
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
|
|
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
|
package/dist/index.cjs.map
CHANGED
|
@@ -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":"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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,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
|
-
|
|
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
|
|
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
|
package/dist/index.mjs.map
CHANGED
|
@@ -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