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 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
@@ -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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"}
@@ -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
@@ -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":";;;;AAGA,SAAgB,aAAa,KAAa,YAAgC;CACxE,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;;;;;ACNT,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,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;EACpB,MAAM,aAAa,KAAK,MAAM,oCAAoC;AAClE,MAAI,WACF,MAAK,OAAQ,KAAK;GAChB,MAAM,WAAW;GACjB,MAAM,WAAW;GACjB,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;;;;;ACjGT,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;;;;;AC9DT,eAAsB,oBAAoB,QAAgB,QAAgB;AACxE,SAAQ,IAAI,YAAY,OAAO,0BAA0B,SAAS;CAGlE,MAAM,QAAQ,aAAa,QAAQ,CAAC,OAAO,MAAM,CAAC;AAElD,MAAK,MAAM,QAAQ,OAAO;EAExB,MAAM,YAAY,iBADF,aAAa,MAAM,QAAQ,EACC,KAAK;AAEjD,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;;;;;;;ACK3D,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 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
+ }