func2md 0.0.1
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/LICENSE +21 -0
- package/README.md +125 -0
- package/README.zh-CN.md +125 -0
- package/dist/index.cjs +175 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +24 -0
- package/dist/index.d.mts +24 -0
- package/dist/index.mjs +175 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 青菜白玉汤
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# func2md
|
|
2
|
+
|
|
3
|
+
A Vite plugin to generate VitePress documentation from JSDoc and TypeScript types.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Scans specified directories for JavaScript/TypeScript functions
|
|
8
|
+
- Parses JSDoc comments to extract function information
|
|
9
|
+
- Generates Markdown documentation compatible with VitePress
|
|
10
|
+
- Supports customizable scanning directories and output locations
|
|
11
|
+
- Integrates with Vite's build process
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install func2md
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
### Basic usage
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// vite.config.ts
|
|
25
|
+
import { defineConfig } from 'vite'
|
|
26
|
+
import func2md from 'func2md'
|
|
27
|
+
|
|
28
|
+
export default defineConfig({
|
|
29
|
+
plugins: [
|
|
30
|
+
func2md({
|
|
31
|
+
srcDir: 'src', // Source directory to scan for functions
|
|
32
|
+
outDir: 'docs', // Output directory for generated markdown files
|
|
33
|
+
watch: false // Whether to watch for changes
|
|
34
|
+
})
|
|
35
|
+
]
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Options
|
|
40
|
+
|
|
41
|
+
| Option | Type | Default | Description |
|
|
42
|
+
|--------|------|---------|-------------|
|
|
43
|
+
| srcDir | string | 'src' | Source directory to scan for functions |
|
|
44
|
+
| outDir | string | 'docs' | Output directory for generated markdown files |
|
|
45
|
+
| watch | boolean | false | Whether to watch for changes |
|
|
46
|
+
|
|
47
|
+
## JSDoc Format
|
|
48
|
+
|
|
49
|
+
The plugin parses JSDoc comments with the following special tags:
|
|
50
|
+
|
|
51
|
+
- `@title` - Function title (optional, defaults to first line of description)
|
|
52
|
+
- `@param` - Function parameters with type and description
|
|
53
|
+
- `@returns` or `@return` - Return value with type and description
|
|
54
|
+
- `@example` - Code example
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
/**
|
|
60
|
+
* @title Add two numbers
|
|
61
|
+
* This function adds two numbers together
|
|
62
|
+
* @param {number} a - First number
|
|
63
|
+
* @param {number} b - Second number
|
|
64
|
+
* @returns {number} The sum of a and b
|
|
65
|
+
* @example
|
|
66
|
+
* import { add } from './math'
|
|
67
|
+
* const result = add(1, 2) // returns 3
|
|
68
|
+
*/
|
|
69
|
+
function add(a, b) {
|
|
70
|
+
return a + b
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Generated Documentation
|
|
75
|
+
|
|
76
|
+
The plugin generates Markdown files with the following structure:
|
|
77
|
+
|
|
78
|
+
```markdown
|
|
79
|
+
# Function Title
|
|
80
|
+
|
|
81
|
+
## INFO
|
|
82
|
+
|
|
83
|
+
Function description
|
|
84
|
+
|
|
85
|
+
## Parameters
|
|
86
|
+
|
|
87
|
+
| Name | Type | Description |
|
|
88
|
+
|------|------|-------------|
|
|
89
|
+
| paramName | `type` | Parameter description |
|
|
90
|
+
|
|
91
|
+
## Return Value
|
|
92
|
+
|
|
93
|
+
- Type: `returnType`
|
|
94
|
+
- Description: Return value description
|
|
95
|
+
|
|
96
|
+
## Examples
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
// Example code
|
|
100
|
+
```
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Development
|
|
104
|
+
|
|
105
|
+
### Build the project
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npm run build
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Run tests
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm run test
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Run tests with coverage
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm run test:coverage
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# func2md
|
|
2
|
+
|
|
3
|
+
一个用于从 JSDoc 和 TypeScript 类型生成 VitePress 文档的 Vite 插件。
|
|
4
|
+
|
|
5
|
+
## 功能特性
|
|
6
|
+
|
|
7
|
+
- 扫描指定目录中的 JavaScript/TypeScript 函数
|
|
8
|
+
- 解析 JSDoc 注释以提取函数信息
|
|
9
|
+
- 生成与 VitePress 兼容的 Markdown 文档
|
|
10
|
+
- 支持自定义扫描目录和输出位置
|
|
11
|
+
- 与 Vite 构建过程集成
|
|
12
|
+
|
|
13
|
+
## 安装
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install func2md
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 使用方法
|
|
20
|
+
|
|
21
|
+
### 基本用法
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// vite.config.ts
|
|
25
|
+
import { defineConfig } from 'vite'
|
|
26
|
+
import func2md from 'func2md'
|
|
27
|
+
|
|
28
|
+
export default defineConfig({
|
|
29
|
+
plugins: [
|
|
30
|
+
func2md({
|
|
31
|
+
srcDir: 'src', // 要扫描函数的源目录
|
|
32
|
+
outDir: 'docs', // 生成的 markdown 文件的输出目录
|
|
33
|
+
watch: false // 是否监听变化
|
|
34
|
+
})
|
|
35
|
+
]
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### 配置选项
|
|
40
|
+
|
|
41
|
+
| 选项 | 类型 | 默认值 | 说明 |
|
|
42
|
+
|------|------|--------|------|
|
|
43
|
+
| srcDir | string | 'src' | 要扫描函数的源目录 |
|
|
44
|
+
| outDir | string | 'docs' | 生成的 markdown 文件的输出目录 |
|
|
45
|
+
| watch | boolean | false | 是否监听变化 |
|
|
46
|
+
|
|
47
|
+
## JSDoc 格式
|
|
48
|
+
|
|
49
|
+
插件解析具有以下特殊标签的 JSDoc 注释:
|
|
50
|
+
|
|
51
|
+
- `@title` - 函数标题(可选,默认为描述的第一行)
|
|
52
|
+
- `@param` - 带类型和描述的函数参数
|
|
53
|
+
- `@returns` 或 `@return` - 带类型和描述的返回值
|
|
54
|
+
- `@example` - 代码示例
|
|
55
|
+
|
|
56
|
+
示例:
|
|
57
|
+
|
|
58
|
+
```js
|
|
59
|
+
/**
|
|
60
|
+
* @title 两数相加
|
|
61
|
+
* 这个函数将两个数字相加
|
|
62
|
+
* @param {number} a - 第一个数字
|
|
63
|
+
* @param {number} b - 第二个数字
|
|
64
|
+
* @returns {number} a 和 b 的和
|
|
65
|
+
* @example
|
|
66
|
+
* import { add } from './math'
|
|
67
|
+
* const result = add(1, 2) // 返回 3
|
|
68
|
+
*/
|
|
69
|
+
function add(a, b) {
|
|
70
|
+
return a + b
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## 生成的文档
|
|
75
|
+
|
|
76
|
+
插件生成具有以下结构的 Markdown 文件:
|
|
77
|
+
|
|
78
|
+
```markdown
|
|
79
|
+
# 函数标题
|
|
80
|
+
|
|
81
|
+
## INFO
|
|
82
|
+
|
|
83
|
+
函数描述
|
|
84
|
+
|
|
85
|
+
## 参数
|
|
86
|
+
|
|
87
|
+
| 参数名 | 类型 | 说明 |
|
|
88
|
+
|--------|------|------|
|
|
89
|
+
| paramName | `type` | 参数描述 |
|
|
90
|
+
|
|
91
|
+
## 返回值
|
|
92
|
+
|
|
93
|
+
- 类型: `returnType`
|
|
94
|
+
- 说明: 返回值描述
|
|
95
|
+
|
|
96
|
+
## 示例
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
// 示例代码
|
|
100
|
+
```
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 开发
|
|
104
|
+
|
|
105
|
+
### 构建项目
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npm run build
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 运行测试
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm run test
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 运行带覆盖率的测试
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm run test:coverage
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 许可证
|
|
124
|
+
|
|
125
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
let fs = require("fs");
|
|
2
|
+
let path = require("path");
|
|
3
|
+
|
|
4
|
+
//#region src/utils/file-utils.ts
|
|
5
|
+
function findAllFiles(dir, extensions) {
|
|
6
|
+
let results = [];
|
|
7
|
+
function traverse(currentDir) {
|
|
8
|
+
const files = (0, fs.readdirSync)(currentDir);
|
|
9
|
+
for (const file of files) {
|
|
10
|
+
const filePath = (0, path.join)(currentDir, file);
|
|
11
|
+
const stat = (0, fs.statSync)(filePath);
|
|
12
|
+
if (stat.isDirectory()) traverse(filePath);
|
|
13
|
+
else if (stat.isFile() && extensions.includes((0, path.extname)(file))) results.push(filePath);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
traverse(dir);
|
|
17
|
+
return results;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/utils/jsdoc-parser.ts
|
|
22
|
+
function parseJSDoc(jsdoc) {
|
|
23
|
+
const lines = jsdoc.trim().split("\n").map((line) => line.replace(/^\s*\*\s?/, ""));
|
|
24
|
+
const info = {};
|
|
25
|
+
const titleMatch = lines.find((line) => line.startsWith("@title"));
|
|
26
|
+
if (titleMatch) info.title = titleMatch.replace("@title", "").trim();
|
|
27
|
+
else if (lines.length > 0) info.title = lines[0];
|
|
28
|
+
const descLines = [];
|
|
29
|
+
for (const line of lines) {
|
|
30
|
+
if (line.startsWith("@example") || line.startsWith("@param") || line.startsWith("@returns") || line.startsWith("@return")) break;
|
|
31
|
+
if (!line.startsWith("@title") && line.trim() !== "") descLines.push(line);
|
|
32
|
+
}
|
|
33
|
+
if (descLines.length > 0) info.desc = descLines.join(" ").trim();
|
|
34
|
+
const exampleLines = [];
|
|
35
|
+
let collectingExample = false;
|
|
36
|
+
for (const line of lines) {
|
|
37
|
+
if (line.startsWith("@example")) {
|
|
38
|
+
collectingExample = true;
|
|
39
|
+
const exampleContent = line.replace("@example", "").trim();
|
|
40
|
+
if (exampleContent) exampleLines.push(exampleContent);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (collectingExample) if (line.startsWith("@")) collectingExample = false;
|
|
44
|
+
else exampleLines.push(line);
|
|
45
|
+
}
|
|
46
|
+
if (exampleLines.length > 0) info.example = exampleLines.join("\n").trim();
|
|
47
|
+
info.params = [];
|
|
48
|
+
lines.forEach((line) => {
|
|
49
|
+
const paramMatch = line.match(/@param\s+{([^}]+)}\s+(\w+)\s+(.+)/);
|
|
50
|
+
if (paramMatch) info.params.push({
|
|
51
|
+
name: paramMatch[2],
|
|
52
|
+
type: paramMatch[1],
|
|
53
|
+
description: paramMatch[3]
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
const returnMatch = lines.find((line) => line.startsWith("@returns") || line.startsWith("@return"));
|
|
57
|
+
if (returnMatch) {
|
|
58
|
+
const returnInfo = returnMatch.replace(/@(returns?)/, "").trim();
|
|
59
|
+
const typeMatch = returnInfo.match(/^{([^}]+)}\s*(.*)/);
|
|
60
|
+
if (typeMatch) info.returns = {
|
|
61
|
+
type: typeMatch[1],
|
|
62
|
+
description: typeMatch[2]
|
|
63
|
+
};
|
|
64
|
+
else info.returns = {
|
|
65
|
+
type: "unknown",
|
|
66
|
+
description: returnInfo
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return info;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/utils/function-extractor.ts
|
|
74
|
+
function extractFunctions(content, filePath) {
|
|
75
|
+
const functions = [];
|
|
76
|
+
const functionRegex = new RegExp(String.raw`/\*\*((?:.|\n|\r)*?)\*/\s*(export\s+)?(async\s+)?function\s+(\w+)`, "g");
|
|
77
|
+
let match;
|
|
78
|
+
while ((match = functionRegex.exec(content)) !== null) {
|
|
79
|
+
const [, jsdocRaw, , , functionName] = match;
|
|
80
|
+
const jsdoc = parseJSDoc(jsdocRaw);
|
|
81
|
+
functions.push({
|
|
82
|
+
name: functionName,
|
|
83
|
+
jsdoc,
|
|
84
|
+
filePath
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return functions;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/generators/markdown-generator.ts
|
|
92
|
+
function generateTitleSection(func) {
|
|
93
|
+
const { name, jsdoc } = func;
|
|
94
|
+
return `# ${jsdoc.title || name}\n\n`;
|
|
95
|
+
}
|
|
96
|
+
function generateDescriptionSection(func) {
|
|
97
|
+
const { jsdoc } = func;
|
|
98
|
+
if (jsdoc.desc) return `## 说明\n\n${jsdoc.desc}\n\n`;
|
|
99
|
+
return "";
|
|
100
|
+
}
|
|
101
|
+
function generateSignatureSection(func) {
|
|
102
|
+
const { jsdoc } = func;
|
|
103
|
+
if (jsdoc.signature) return `## 签名\n\n\`\`\`ts\n${jsdoc.signature}\n\`\`\`\n\n`;
|
|
104
|
+
return "";
|
|
105
|
+
}
|
|
106
|
+
function generateParametersSection(func) {
|
|
107
|
+
const { jsdoc } = func;
|
|
108
|
+
if (jsdoc.params && jsdoc.params.length > 0) {
|
|
109
|
+
let section = `## 参数\n\n`;
|
|
110
|
+
section += `| 参数名 | 类型 | 说明 |\n|--------|------|------|\n`;
|
|
111
|
+
jsdoc.params.forEach((param) => {
|
|
112
|
+
section += `| ${param.name} | \`${param.type}\` | ${param.description} |\n`;
|
|
113
|
+
});
|
|
114
|
+
section += `\n`;
|
|
115
|
+
return section;
|
|
116
|
+
}
|
|
117
|
+
return "";
|
|
118
|
+
}
|
|
119
|
+
function generateReturnsSection(func) {
|
|
120
|
+
const { jsdoc } = func;
|
|
121
|
+
if (jsdoc.returns) return `## 返回值\n\n- 类型: \`${jsdoc.returns.type}\`\n- 说明: ${jsdoc.returns.description}\n\n`;
|
|
122
|
+
return "";
|
|
123
|
+
}
|
|
124
|
+
function generateExampleSection(func) {
|
|
125
|
+
const { jsdoc } = func;
|
|
126
|
+
if (jsdoc.example) return `## 示例\n\n\`\`\`ts\n${jsdoc.example}\n\`\`\`\n\n`;
|
|
127
|
+
return "";
|
|
128
|
+
}
|
|
129
|
+
function generateMarkdown(func) {
|
|
130
|
+
let md = generateTitleSection(func);
|
|
131
|
+
md += generateDescriptionSection(func);
|
|
132
|
+
md += generateSignatureSection(func);
|
|
133
|
+
md += generateParametersSection(func);
|
|
134
|
+
md += generateReturnsSection(func);
|
|
135
|
+
md += generateExampleSection(func);
|
|
136
|
+
return md;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/core/scanner.ts
|
|
141
|
+
async function scanAndGenerateDocs(srcDir, outDir) {
|
|
142
|
+
console.log(`Scanning ${srcDir} and generating docs to ${outDir}`);
|
|
143
|
+
const files = findAllFiles(srcDir, [".ts", ".js"]);
|
|
144
|
+
for (const file of files) {
|
|
145
|
+
const functions = extractFunctions((0, fs.readFileSync)(file, "utf-8"), file);
|
|
146
|
+
for (const func of functions) {
|
|
147
|
+
const mdContent = generateMarkdown(func);
|
|
148
|
+
const outputPath = (0, path.join)(outDir, `${func.name}.md`);
|
|
149
|
+
(0, fs.writeFileSync)(outputPath, mdContent);
|
|
150
|
+
console.log(`Generated documentation: ${outputPath}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/index.ts
|
|
157
|
+
function func2md(options = {}) {
|
|
158
|
+
const { srcDir = "src", outDir = "docs", watch = false } = options;
|
|
159
|
+
const resolvedSrcDir = (0, path.resolve)(srcDir);
|
|
160
|
+
const resolvedOutDir = (0, path.resolve)(outDir);
|
|
161
|
+
return {
|
|
162
|
+
name: "func2md",
|
|
163
|
+
async buildStart() {
|
|
164
|
+
if (!(0, fs.existsSync)(resolvedOutDir)) (0, fs.mkdirSync)(resolvedOutDir, { recursive: true });
|
|
165
|
+
await scanAndGenerateDocs(resolvedSrcDir, resolvedOutDir);
|
|
166
|
+
},
|
|
167
|
+
async handleHotUpdate(ctx) {
|
|
168
|
+
if (watch && ctx.file.includes(resolvedSrcDir)) await scanAndGenerateDocs(resolvedSrcDir, resolvedOutDir);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
//#endregion
|
|
174
|
+
module.exports = func2md;
|
|
175
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","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 } from 'fs'\nimport { join, extname } from 'path'\n\nexport function findAllFiles(dir: string, extensions: string[]): string[] {\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 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 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 const paramMatch = line.match(/@param\\s+{([^}]+)}\\s+(\\w+)\\s+(.+)/)\n if (paramMatch) {\n info.params!.push({\n name: paramMatch[2],\n type: paramMatch[1],\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}","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 } from 'fs'\nimport { join } from 'path'\nimport { findAllFiles } from '../utils/file-utils'\nimport { extractFunctions } from '../utils/function-extractor'\nimport { generateMarkdown } from '../generators/markdown-generator'\n\nexport async function scanAndGenerateDocs(srcDir: string, outDir: string) {\n console.log(`Scanning ${srcDir} and generating docs to ${outDir}`)\n \n // Find all .ts and .js files in srcDir\n const files = findAllFiles(srcDir, ['.ts', '.js'])\n \n for (const file of files) {\n const content = readFileSync(file, 'utf-8')\n const functions = extractFunctions(content, file)\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}","import type { Plugin } from 'vite'\nimport { existsSync, mkdirSync } from 'fs'\nimport { resolve } from 'path'\nimport { scanAndGenerateDocs } from './core/scanner'\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}"],"mappings":"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"}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Plugin } from "vite";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
interface Func2MdOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Source directory to scan for functions
|
|
7
|
+
* @default 'src'
|
|
8
|
+
*/
|
|
9
|
+
srcDir?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Output directory for generated markdown files
|
|
12
|
+
* @default 'docs'
|
|
13
|
+
*/
|
|
14
|
+
outDir?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to watch for changes
|
|
17
|
+
* @default false
|
|
18
|
+
*/
|
|
19
|
+
watch?: boolean;
|
|
20
|
+
}
|
|
21
|
+
declare function func2md(options?: Func2MdOptions): Plugin;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { Func2MdOptions, func2md as default };
|
|
24
|
+
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Plugin } from "vite";
|
|
2
|
+
|
|
3
|
+
//#region src/index.d.ts
|
|
4
|
+
interface Func2MdOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Source directory to scan for functions
|
|
7
|
+
* @default 'src'
|
|
8
|
+
*/
|
|
9
|
+
srcDir?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Output directory for generated markdown files
|
|
12
|
+
* @default 'docs'
|
|
13
|
+
*/
|
|
14
|
+
outDir?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Whether to watch for changes
|
|
17
|
+
* @default false
|
|
18
|
+
*/
|
|
19
|
+
watch?: boolean;
|
|
20
|
+
}
|
|
21
|
+
declare function func2md(options?: Func2MdOptions): Plugin;
|
|
22
|
+
//#endregion
|
|
23
|
+
export { Func2MdOptions, func2md as default };
|
|
24
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
|
|
2
|
+
import { extname, join, resolve } from "path";
|
|
3
|
+
|
|
4
|
+
//#region src/utils/file-utils.ts
|
|
5
|
+
function findAllFiles(dir, extensions) {
|
|
6
|
+
let results = [];
|
|
7
|
+
function traverse(currentDir) {
|
|
8
|
+
const files = readdirSync(currentDir);
|
|
9
|
+
for (const file of files) {
|
|
10
|
+
const filePath = join(currentDir, file);
|
|
11
|
+
const stat = statSync(filePath);
|
|
12
|
+
if (stat.isDirectory()) traverse(filePath);
|
|
13
|
+
else if (stat.isFile() && extensions.includes(extname(file))) results.push(filePath);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
traverse(dir);
|
|
17
|
+
return results;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//#endregion
|
|
21
|
+
//#region src/utils/jsdoc-parser.ts
|
|
22
|
+
function parseJSDoc(jsdoc) {
|
|
23
|
+
const lines = jsdoc.trim().split("\n").map((line) => line.replace(/^\s*\*\s?/, ""));
|
|
24
|
+
const info = {};
|
|
25
|
+
const titleMatch = lines.find((line) => line.startsWith("@title"));
|
|
26
|
+
if (titleMatch) info.title = titleMatch.replace("@title", "").trim();
|
|
27
|
+
else if (lines.length > 0) info.title = lines[0];
|
|
28
|
+
const descLines = [];
|
|
29
|
+
for (const line of lines) {
|
|
30
|
+
if (line.startsWith("@example") || line.startsWith("@param") || line.startsWith("@returns") || line.startsWith("@return")) break;
|
|
31
|
+
if (!line.startsWith("@title") && line.trim() !== "") descLines.push(line);
|
|
32
|
+
}
|
|
33
|
+
if (descLines.length > 0) info.desc = descLines.join(" ").trim();
|
|
34
|
+
const exampleLines = [];
|
|
35
|
+
let collectingExample = false;
|
|
36
|
+
for (const line of lines) {
|
|
37
|
+
if (line.startsWith("@example")) {
|
|
38
|
+
collectingExample = true;
|
|
39
|
+
const exampleContent = line.replace("@example", "").trim();
|
|
40
|
+
if (exampleContent) exampleLines.push(exampleContent);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (collectingExample) if (line.startsWith("@")) collectingExample = false;
|
|
44
|
+
else exampleLines.push(line);
|
|
45
|
+
}
|
|
46
|
+
if (exampleLines.length > 0) info.example = exampleLines.join("\n").trim();
|
|
47
|
+
info.params = [];
|
|
48
|
+
lines.forEach((line) => {
|
|
49
|
+
const paramMatch = line.match(/@param\s+{([^}]+)}\s+(\w+)\s+(.+)/);
|
|
50
|
+
if (paramMatch) info.params.push({
|
|
51
|
+
name: paramMatch[2],
|
|
52
|
+
type: paramMatch[1],
|
|
53
|
+
description: paramMatch[3]
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
const returnMatch = lines.find((line) => line.startsWith("@returns") || line.startsWith("@return"));
|
|
57
|
+
if (returnMatch) {
|
|
58
|
+
const returnInfo = returnMatch.replace(/@(returns?)/, "").trim();
|
|
59
|
+
const typeMatch = returnInfo.match(/^{([^}]+)}\s*(.*)/);
|
|
60
|
+
if (typeMatch) info.returns = {
|
|
61
|
+
type: typeMatch[1],
|
|
62
|
+
description: typeMatch[2]
|
|
63
|
+
};
|
|
64
|
+
else info.returns = {
|
|
65
|
+
type: "unknown",
|
|
66
|
+
description: returnInfo
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return info;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/utils/function-extractor.ts
|
|
74
|
+
function extractFunctions(content, filePath) {
|
|
75
|
+
const functions = [];
|
|
76
|
+
const functionRegex = new RegExp(String.raw`/\*\*((?:.|\n|\r)*?)\*/\s*(export\s+)?(async\s+)?function\s+(\w+)`, "g");
|
|
77
|
+
let match;
|
|
78
|
+
while ((match = functionRegex.exec(content)) !== null) {
|
|
79
|
+
const [, jsdocRaw, , , functionName] = match;
|
|
80
|
+
const jsdoc = parseJSDoc(jsdocRaw);
|
|
81
|
+
functions.push({
|
|
82
|
+
name: functionName,
|
|
83
|
+
jsdoc,
|
|
84
|
+
filePath
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return functions;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/generators/markdown-generator.ts
|
|
92
|
+
function generateTitleSection(func) {
|
|
93
|
+
const { name, jsdoc } = func;
|
|
94
|
+
return `# ${jsdoc.title || name}\n\n`;
|
|
95
|
+
}
|
|
96
|
+
function generateDescriptionSection(func) {
|
|
97
|
+
const { jsdoc } = func;
|
|
98
|
+
if (jsdoc.desc) return `## 说明\n\n${jsdoc.desc}\n\n`;
|
|
99
|
+
return "";
|
|
100
|
+
}
|
|
101
|
+
function generateSignatureSection(func) {
|
|
102
|
+
const { jsdoc } = func;
|
|
103
|
+
if (jsdoc.signature) return `## 签名\n\n\`\`\`ts\n${jsdoc.signature}\n\`\`\`\n\n`;
|
|
104
|
+
return "";
|
|
105
|
+
}
|
|
106
|
+
function generateParametersSection(func) {
|
|
107
|
+
const { jsdoc } = func;
|
|
108
|
+
if (jsdoc.params && jsdoc.params.length > 0) {
|
|
109
|
+
let section = `## 参数\n\n`;
|
|
110
|
+
section += `| 参数名 | 类型 | 说明 |\n|--------|------|------|\n`;
|
|
111
|
+
jsdoc.params.forEach((param) => {
|
|
112
|
+
section += `| ${param.name} | \`${param.type}\` | ${param.description} |\n`;
|
|
113
|
+
});
|
|
114
|
+
section += `\n`;
|
|
115
|
+
return section;
|
|
116
|
+
}
|
|
117
|
+
return "";
|
|
118
|
+
}
|
|
119
|
+
function generateReturnsSection(func) {
|
|
120
|
+
const { jsdoc } = func;
|
|
121
|
+
if (jsdoc.returns) return `## 返回值\n\n- 类型: \`${jsdoc.returns.type}\`\n- 说明: ${jsdoc.returns.description}\n\n`;
|
|
122
|
+
return "";
|
|
123
|
+
}
|
|
124
|
+
function generateExampleSection(func) {
|
|
125
|
+
const { jsdoc } = func;
|
|
126
|
+
if (jsdoc.example) return `## 示例\n\n\`\`\`ts\n${jsdoc.example}\n\`\`\`\n\n`;
|
|
127
|
+
return "";
|
|
128
|
+
}
|
|
129
|
+
function generateMarkdown(func) {
|
|
130
|
+
let md = generateTitleSection(func);
|
|
131
|
+
md += generateDescriptionSection(func);
|
|
132
|
+
md += generateSignatureSection(func);
|
|
133
|
+
md += generateParametersSection(func);
|
|
134
|
+
md += generateReturnsSection(func);
|
|
135
|
+
md += generateExampleSection(func);
|
|
136
|
+
return md;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/core/scanner.ts
|
|
141
|
+
async function scanAndGenerateDocs(srcDir, outDir) {
|
|
142
|
+
console.log(`Scanning ${srcDir} and generating docs to ${outDir}`);
|
|
143
|
+
const files = findAllFiles(srcDir, [".ts", ".js"]);
|
|
144
|
+
for (const file of files) {
|
|
145
|
+
const functions = extractFunctions(readFileSync(file, "utf-8"), file);
|
|
146
|
+
for (const func of functions) {
|
|
147
|
+
const mdContent = generateMarkdown(func);
|
|
148
|
+
const outputPath = join(outDir, `${func.name}.md`);
|
|
149
|
+
writeFileSync(outputPath, mdContent);
|
|
150
|
+
console.log(`Generated documentation: ${outputPath}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/index.ts
|
|
157
|
+
function func2md(options = {}) {
|
|
158
|
+
const { srcDir = "src", outDir = "docs", watch = false } = options;
|
|
159
|
+
const resolvedSrcDir = resolve(srcDir);
|
|
160
|
+
const resolvedOutDir = resolve(outDir);
|
|
161
|
+
return {
|
|
162
|
+
name: "func2md",
|
|
163
|
+
async buildStart() {
|
|
164
|
+
if (!existsSync(resolvedOutDir)) mkdirSync(resolvedOutDir, { recursive: true });
|
|
165
|
+
await scanAndGenerateDocs(resolvedSrcDir, resolvedOutDir);
|
|
166
|
+
},
|
|
167
|
+
async handleHotUpdate(ctx) {
|
|
168
|
+
if (watch && ctx.file.includes(resolvedSrcDir)) await scanAndGenerateDocs(resolvedSrcDir, resolvedOutDir);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
//#endregion
|
|
174
|
+
export { func2md as default };
|
|
175
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +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 } from 'fs'\nimport { join, extname } from 'path'\n\nexport function findAllFiles(dir: string, extensions: string[]): string[] {\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 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 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 const paramMatch = line.match(/@param\\s+{([^}]+)}\\s+(\\w+)\\s+(.+)/)\n if (paramMatch) {\n info.params!.push({\n name: paramMatch[2],\n type: paramMatch[1],\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}","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 } from 'fs'\nimport { join } from 'path'\nimport { findAllFiles } from '../utils/file-utils'\nimport { extractFunctions } from '../utils/function-extractor'\nimport { generateMarkdown } from '../generators/markdown-generator'\n\nexport async function scanAndGenerateDocs(srcDir: string, outDir: string) {\n console.log(`Scanning ${srcDir} and generating docs to ${outDir}`)\n \n // Find all .ts and .js files in srcDir\n const files = findAllFiles(srcDir, ['.ts', '.js'])\n \n for (const file of files) {\n const content = readFileSync(file, 'utf-8')\n const functions = extractFunctions(content, file)\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}","import type { Plugin } from 'vite'\nimport { existsSync, mkdirSync } from 'fs'\nimport { resolve } from 'path'\nimport { scanAndGenerateDocs } from './core/scanner'\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}"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "func2md",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"description": "A Vite plugin to generate VitePress documentation from JSDoc and TypeScript types",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.mjs",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.mjs",
|
|
16
|
+
"require": "./dist/index.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsdown 2>&1 | grep -v '\\[38;5;2' | grep -v 'MISSING_EXPORT' | grep -v 'Missing export' | grep -v 'IMPORT_IS_UNDEFINED' | grep -v 'will always be undefined' | grep -v '^\\s*$'",
|
|
21
|
+
"dev": "tsdown --watch",
|
|
22
|
+
"test": "vitest",
|
|
23
|
+
"test:run": "vitest run",
|
|
24
|
+
"test:coverage": "vitest run --coverage",
|
|
25
|
+
"example:dev": "cd example && npm run dev",
|
|
26
|
+
"example:build": "cd example && npm run build"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"vite",
|
|
30
|
+
"vitepress",
|
|
31
|
+
"documentation",
|
|
32
|
+
"jsdoc",
|
|
33
|
+
"typescript"
|
|
34
|
+
],
|
|
35
|
+
"author": "Azir-11",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/Vectutil/func2md.git"
|
|
40
|
+
},
|
|
41
|
+
"bugs": {
|
|
42
|
+
"url": "https://github.com/Vectutil/func2md/issues"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://github.com/Vectutil/func2md#readme",
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
47
|
+
},
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"vite": ">=5"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public",
|
|
53
|
+
"registry": "https://registry.npmjs.org/"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/node": "^22.10.10",
|
|
57
|
+
"postcss": "^8.5.6",
|
|
58
|
+
"tsdown": "0.20.0-beta.3",
|
|
59
|
+
"typescript": "^5.9.2",
|
|
60
|
+
"vite": "^6.0.5",
|
|
61
|
+
"vitepress": "^1.5.0",
|
|
62
|
+
"vitest": "^3.0.4"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"jsdoc": "^4.0.4"
|
|
66
|
+
}
|
|
67
|
+
}
|