vuepress-jsdoc 5.0.0 → 6.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 +168 -69
- package/bin/vuepress-jsdoc.js +7 -4
- package/dist/cmds.d.ts +4 -2
- package/dist/cmds.js +44 -17
- package/dist/constants.d.ts +16 -0
- package/dist/constants.js +18 -5
- package/dist/index.d.ts +46 -6
- package/dist/index.js +211 -163
- package/dist/interfaces.d.ts +45 -19
- package/dist/interfaces.js +1 -2
- package/dist/lib/comment-parser.d.ts +15 -9
- package/dist/lib/comment-parser.js +46 -52
- package/dist/lib/config.d.ts +21 -0
- package/dist/lib/config.js +57 -0
- package/dist/lib/list-folder.d.ts +30 -12
- package/dist/lib/list-folder.js +100 -70
- package/dist/lib/parser.d.ts +46 -30
- package/dist/lib/parser.js +199 -141
- package/dist/lib/vue-sidebar.d.ts +61 -21
- package/dist/lib/vue-sidebar.js +109 -43
- package/dist/lib/vuepress-plugin.d.ts +48 -0
- package/dist/lib/vuepress-plugin.js +63 -0
- package/package.json +66 -115
- package/CHANGELOG.md +0 -507
- package/dist/lib/index.d.ts +0 -6
- package/dist/lib/index.js +0 -16
- package/dist/lib/utils.d.ts +0 -19
- package/dist/lib/utils.js +0 -41
package/dist/lib/parser.js
CHANGED
|
@@ -1,165 +1,223 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
-
});
|
|
10
|
-
};
|
|
11
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
-
};
|
|
14
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
-
exports.writeContentToFile = exports.parseVueFile = exports.parseFile = void 0;
|
|
16
1
|
/*
|
|
17
2
|
* @vuepress
|
|
18
3
|
* ---
|
|
19
4
|
* headline: lib/parser.ts
|
|
20
5
|
* ---
|
|
21
6
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
7
|
+
import jsdoc2md from 'jsdoc-to-markdown';
|
|
8
|
+
import fs from 'node:fs/promises';
|
|
9
|
+
import nodeModule, { createRequire } from 'node:module';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { StatisticType, TYPESCRIPT_EXTENSIONS, VUE_EXTENSIONS } from '../constants.js';
|
|
13
|
+
import { parseVuepressFileHeader } from './comment-parser.js';
|
|
14
|
+
const require = createRequire(import.meta.url);
|
|
15
|
+
const TEMPLATE_FOLDER = fileURLToPath(new URL('../../template/', import.meta.url));
|
|
30
16
|
/**
|
|
31
|
-
*
|
|
32
|
-
* @param
|
|
33
|
-
* @param
|
|
34
|
-
* @param
|
|
35
|
-
* @
|
|
36
|
-
* @param partials {string | string[]}
|
|
37
|
-
* @returns {object} file data
|
|
17
|
+
* Get the folder of the generated markdown file
|
|
18
|
+
* @param {DirectoryFile} file
|
|
19
|
+
* @param {string} srcFolder
|
|
20
|
+
* @param {string} destFolder
|
|
21
|
+
* @returns {string} destination folder
|
|
38
22
|
*/
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
23
|
+
export const getDestFolder = (file, srcFolder, destFolder) => path.join(destFolder, path.relative(path.resolve(srcFolder), path.resolve(file.folder)));
|
|
24
|
+
/**
|
|
25
|
+
* Get a readable error message
|
|
26
|
+
* @param {unknown} error
|
|
27
|
+
* @returns {string}
|
|
28
|
+
*/
|
|
29
|
+
const getErrorMessage = (error) => (error instanceof Error ? error.message : String(error)).split('\n').filter(Boolean).slice(0, 3).join('\n');
|
|
30
|
+
/**
|
|
31
|
+
* Remove TypeScript types from source code, so jsdoc is able to parse it.
|
|
32
|
+
* Types are replaced with whitespace, so all comments and line numbers stay intact.
|
|
33
|
+
* @param {string} code TypeScript code
|
|
34
|
+
* @returns {string} JavaScript code
|
|
35
|
+
*/
|
|
36
|
+
export const stripTypes = (code) => {
|
|
37
|
+
const { stripTypeScriptTypes } = nodeModule;
|
|
38
|
+
if (typeof stripTypeScriptTypes !== 'function') {
|
|
39
|
+
throw new Error('TypeScript files need Node.js >= 22.13 or a jsdoc config (--jsDocConfigPath)');
|
|
40
|
+
}
|
|
41
|
+
// stripTypeScriptTypes is still experimental and would print a warning
|
|
42
|
+
const emitWarning = process.emitWarning;
|
|
43
|
+
process.emitWarning = () => { };
|
|
51
44
|
try {
|
|
52
|
-
|
|
53
|
-
let fileName = file.name;
|
|
54
|
-
if (fileName.endsWith('index')) {
|
|
55
|
-
fileName = 'index';
|
|
56
|
-
}
|
|
57
|
-
content = yield jsdoc_to_markdown_1.default.render({
|
|
58
|
-
'no-cache': configPath ? true : false,
|
|
59
|
-
files: [(0, node_path_1.join)(process.cwd(), file.folder, fileName + file.ext)],
|
|
60
|
-
configure: configPath,
|
|
61
|
-
partial: [
|
|
62
|
-
(0, node_path_1.resolve)(__filename, '../../../template/header.hbs'),
|
|
63
|
-
(0, node_path_1.resolve)(__filename, '../../../template/main.hbs'),
|
|
64
|
-
...partials
|
|
65
|
-
]
|
|
66
|
-
});
|
|
67
|
-
fileContent = (0, comment_parser_1.parseVuepressFileHeader)(yield promises_1.default.readFile(`${(0, node_path_1.join)(folderInSrc, fileName + file.ext)}`, 'utf-8'), file);
|
|
68
|
-
if (content) {
|
|
69
|
-
fileContent += content;
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
empty = true;
|
|
73
|
-
}
|
|
45
|
+
return stripTypeScriptTypes(code, { mode: 'strip' });
|
|
74
46
|
}
|
|
75
47
|
catch (e) {
|
|
76
|
-
|
|
77
|
-
excluded = true;
|
|
48
|
+
throw new Error(getErrorMessage(e), { cause: e });
|
|
78
49
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
excluded,
|
|
84
|
-
relativePathDest,
|
|
85
|
-
relativePathSrc: file.folder,
|
|
86
|
-
dest: folderInDest,
|
|
87
|
-
content: fileContent
|
|
88
|
-
};
|
|
89
|
-
});
|
|
90
|
-
exports.parseFile = parseFile;
|
|
50
|
+
finally {
|
|
51
|
+
process.emitWarning = emitWarning;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
91
54
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* @
|
|
96
|
-
|
|
55
|
+
* Load the TypeScript compiler API. The project of the user is preferred over the bundled one.
|
|
56
|
+
* `typescript` >= 7 has no JavaScript API anymore, so `@typescript/typescript6` is tried as well.
|
|
57
|
+
* The result is cached, because it is used for every TypeScript file.
|
|
58
|
+
* @returns {TypeScriptApi | null} TypeScript module or null if it is not installed
|
|
59
|
+
*/
|
|
60
|
+
let typescript;
|
|
61
|
+
const loadTypeScript = () => {
|
|
62
|
+
if (typescript !== undefined)
|
|
63
|
+
return typescript;
|
|
64
|
+
const requires = [createRequire(path.join(process.cwd(), 'package.json')), require];
|
|
65
|
+
for (const req of requires) {
|
|
66
|
+
for (const name of ['typescript', '@typescript/typescript6']) {
|
|
67
|
+
try {
|
|
68
|
+
const ts = req(name);
|
|
69
|
+
if (typeof ts?.transpileModule === 'function')
|
|
70
|
+
return (typescript = ts);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// try the next one
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return (typescript = null);
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Transpile TypeScript with the TypeScript compiler. Comments and JSX are preserved.
|
|
81
|
+
* This supports syntax that type stripping can not handle: `enum`, `namespace`, parameter properties and JSX.
|
|
82
|
+
* @param {string} code TypeScript code
|
|
83
|
+
* @param {string} fileName used to detect `.tsx`
|
|
84
|
+
* @returns {string | null} JavaScript code or null if TypeScript is not installed
|
|
97
85
|
*/
|
|
98
|
-
const
|
|
99
|
-
|
|
86
|
+
const transpileTypes = (code, fileName) => {
|
|
87
|
+
const ts = loadTypeScript();
|
|
88
|
+
if (!ts)
|
|
100
89
|
return null;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
let fileName = file.name;
|
|
112
|
-
if (fileName.endsWith('index')) {
|
|
113
|
-
fileName = 'index';
|
|
90
|
+
return ts.transpileModule(code, {
|
|
91
|
+
fileName,
|
|
92
|
+
reportDiagnostics: false,
|
|
93
|
+
compilerOptions: {
|
|
94
|
+
target: ts.ScriptTarget.ESNext,
|
|
95
|
+
module: ts.ModuleKind.ESNext,
|
|
96
|
+
jsx: ts.JsxEmit.Preserve,
|
|
97
|
+
removeComments: false,
|
|
98
|
+
verbatimModuleSyntax: false,
|
|
99
|
+
isolatedModules: true
|
|
114
100
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
101
|
+
}).outputText;
|
|
102
|
+
};
|
|
103
|
+
/**
|
|
104
|
+
* Convert TypeScript to JavaScript. Type stripping of Node.js is used first, because it keeps all line numbers.
|
|
105
|
+
* If the file contains syntax which needs a transformation, the TypeScript compiler is used.
|
|
106
|
+
* @param {string} code TypeScript code
|
|
107
|
+
* @param {string} fileName
|
|
108
|
+
* @returns {string} JavaScript code
|
|
109
|
+
*/
|
|
110
|
+
export const typescriptToJavaScript = (code, fileName) => {
|
|
111
|
+
let reason = 'JSX can not be stripped';
|
|
112
|
+
if (!fileName.endsWith('.tsx')) {
|
|
113
|
+
try {
|
|
114
|
+
return stripTypes(code);
|
|
120
115
|
}
|
|
121
|
-
|
|
122
|
-
|
|
116
|
+
catch (e) {
|
|
117
|
+
reason = getErrorMessage(e);
|
|
123
118
|
}
|
|
124
119
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
120
|
+
const transpiled = transpileTypes(code, fileName);
|
|
121
|
+
if (transpiled !== null)
|
|
122
|
+
return transpiled;
|
|
123
|
+
throw new Error(`${reason}\nInstall typescript or use a jsdoc config with babel (--jsDocConfigPath) for this file.`);
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Escape vue interpolations (`{{ }}`) outside of code, because VuePress compiles every page as a vue template.
|
|
127
|
+
* @param {string} markdown
|
|
128
|
+
* @returns {string} escaped markdown
|
|
129
|
+
*/
|
|
130
|
+
export const escapeVueInterpolation = (markdown) => markdown
|
|
131
|
+
// split by fenced code blocks, <code> and <pre> tags and inline code
|
|
132
|
+
.split(/(```[\s\S]*?```|<pre[\s\S]*?<\/pre>|<code>[\s\S]*?<\/code>|`[^`\n]*`)/)
|
|
133
|
+
// odd parts are code, which is kept. Inside <code> tags vue still interpolates.
|
|
134
|
+
.map((part, i) => i % 2 === 1 && !part.startsWith('<code>')
|
|
135
|
+
? part
|
|
136
|
+
: part.replace(/\{\{/g, '{{').replace(/\}\}/g, '}}'))
|
|
137
|
+
.join('');
|
|
140
138
|
/**
|
|
141
|
-
*
|
|
142
|
-
* @param {
|
|
143
|
-
* @param {
|
|
144
|
-
* @
|
|
139
|
+
* Render a javascript or typescript file with jsdoc-to-markdown
|
|
140
|
+
* @param {DirectoryFile} file
|
|
141
|
+
* @param {ParseOptions} options
|
|
142
|
+
* @param {string} source content of the file
|
|
143
|
+
* @returns {Promise<string>} markdown
|
|
145
144
|
*/
|
|
146
|
-
const
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
145
|
+
const renderScript = async (file, options, source) => {
|
|
146
|
+
const absolutePath = path.resolve(file.path);
|
|
147
|
+
const jsdocOptions = {
|
|
148
|
+
partial: [
|
|
149
|
+
path.join(TEMPLATE_FOLDER, 'header.hbs'),
|
|
150
|
+
path.join(TEMPLATE_FOLDER, 'main.hbs'),
|
|
151
|
+
...(options.partials || [])
|
|
152
|
+
]
|
|
153
|
+
};
|
|
154
|
+
if (options.jsDocConfigPath) {
|
|
155
|
+
// the user config takes care of the file (e.g. typescript via jsdoc-babel)
|
|
156
|
+
jsdocOptions.files = [absolutePath];
|
|
157
|
+
jsdocOptions.configure = path.resolve(options.jsDocConfigPath);
|
|
158
|
+
jsdocOptions['no-cache'] = true;
|
|
159
|
+
}
|
|
160
|
+
else if (TYPESCRIPT_EXTENSIONS.includes(file.ext)) {
|
|
161
|
+
jsdocOptions.source = typescriptToJavaScript(source, file.path);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
jsdocOptions.files = [absolutePath];
|
|
152
165
|
}
|
|
166
|
+
return jsdoc2md.render(jsdocOptions);
|
|
167
|
+
};
|
|
168
|
+
/**
|
|
169
|
+
* Render a vue file with vue-docgen-cli
|
|
170
|
+
* @param {DirectoryFile} file
|
|
171
|
+
* @returns {Promise<string>} markdown
|
|
172
|
+
*/
|
|
173
|
+
const renderVue = async (file) => {
|
|
174
|
+
const { default: compileTemplates } = require('vue-docgen-cli/lib/compileTemplates');
|
|
175
|
+
const { extractConfig } = require('vue-docgen-cli/lib/docgen');
|
|
176
|
+
const fileName = path.basename(file.path);
|
|
177
|
+
const config = await extractConfig(path.resolve(file.folder));
|
|
178
|
+
const { content } = await compileTemplates('add', path.join(config.componentsRoot, fileName), config, fileName);
|
|
179
|
+
return content;
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Parse a javascript, typescript or vue file and create the markdown content
|
|
183
|
+
* @param {DirectoryFile} file
|
|
184
|
+
* @param {ParseOptions} options
|
|
185
|
+
* @returns {Promise<ParseReturn>} file data
|
|
186
|
+
*/
|
|
187
|
+
export const parseFile = async (file, options) => {
|
|
188
|
+
const dest = getDestFolder(file, options.srcFolder, options.destFolder);
|
|
189
|
+
const empty = { type: StatisticType.EMPTY, file, dest, content: '' };
|
|
153
190
|
try {
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
191
|
+
const source = await fs.readFile(file.path, 'utf-8');
|
|
192
|
+
const isVue = VUE_EXTENSIONS.includes(file.ext);
|
|
193
|
+
const content = isVue ? await renderVue(file) : await renderScript(file, options, source);
|
|
194
|
+
if (!content?.trim())
|
|
195
|
+
return empty;
|
|
196
|
+
return {
|
|
197
|
+
type: StatisticType.INCLUDE,
|
|
198
|
+
file,
|
|
199
|
+
dest,
|
|
200
|
+
content: parseVuepressFileHeader(source, file) + escapeVueInterpolation(content.trim()) + '\n'
|
|
201
|
+
};
|
|
161
202
|
}
|
|
162
|
-
catch (
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
203
|
+
catch (e) {
|
|
204
|
+
const error = getErrorMessage(e);
|
|
205
|
+
// the file is excluded by the jsdoc config (source.excludePattern)
|
|
206
|
+
if (/no input files to process/i.test(error))
|
|
207
|
+
return empty;
|
|
208
|
+
return { ...empty, type: StatisticType.ERROR, error };
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Write the parsed content to disk. Files without content are skipped.
|
|
213
|
+
* @param {ParseReturn} parseData
|
|
214
|
+
* @returns {Promise<string | null>} path of the written file or null
|
|
215
|
+
*/
|
|
216
|
+
export const writeContentToFile = async (parseData) => {
|
|
217
|
+
if (parseData.type !== StatisticType.INCLUDE || !parseData.content)
|
|
218
|
+
return null;
|
|
219
|
+
const filePath = path.join(parseData.dest, `${parseData.file.name}.md`);
|
|
220
|
+
await fs.mkdir(parseData.dest, { recursive: true });
|
|
221
|
+
await fs.writeFile(filePath, parseData.content, 'utf-8');
|
|
222
|
+
return filePath;
|
|
223
|
+
};
|
|
@@ -1,26 +1,66 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
export
|
|
12
|
-
|
|
1
|
+
import { FileTree } from '../interfaces.js';
|
|
2
|
+
export interface SidebarGroup {
|
|
3
|
+
text: string;
|
|
4
|
+
collapsible: boolean;
|
|
5
|
+
children: SidebarItem[];
|
|
6
|
+
}
|
|
7
|
+
export type SidebarItem = string | {
|
|
8
|
+
text: string;
|
|
9
|
+
link: string;
|
|
10
|
+
} | SidebarGroup;
|
|
11
|
+
export interface SidebarOptions {
|
|
12
|
+
/** tree structure, created by `listFolder` */
|
|
13
|
+
fileTree: FileTree[];
|
|
14
|
+
/** name of the folder inside the vuepress folder, e.g. `code` */
|
|
13
15
|
codeFolder: string;
|
|
16
|
+
/** source folder, e.g. `src` */
|
|
14
17
|
srcFolder: string;
|
|
18
|
+
/** folder of the generated markdown files, e.g. `documentation/code` */
|
|
15
19
|
docsFolder: string;
|
|
20
|
+
/** title of the first sidebar group */
|
|
16
21
|
title: string;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
/** sidebar format of the generated `config.js` */
|
|
23
|
+
format?: SiteFormat;
|
|
24
|
+
/** text of the first entry, which links to the readme */
|
|
25
|
+
readmeTitle?: string;
|
|
26
|
+
/** make the top level groups collapsible. Nested folders are always collapsible. */
|
|
27
|
+
collapsible?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export type SiteFormat = 'vuepress' | 'vitepress';
|
|
30
|
+
/**
|
|
31
|
+
* Get the route of the generated folder, e.g. `/code/`
|
|
32
|
+
* @param {string} codeFolder
|
|
33
|
+
* @returns {string}
|
|
34
|
+
*/
|
|
35
|
+
export declare const getSidebarBase: (codeFolder: string) => string;
|
|
36
|
+
export interface VitepressSidebarItem {
|
|
37
|
+
text: string;
|
|
38
|
+
link?: string;
|
|
39
|
+
collapsed?: boolean;
|
|
40
|
+
items?: VitepressSidebarItem[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Runs through the given tree structure and creates a vuepress 2 sidebar config.
|
|
44
|
+
* Only pages that were generated are linked. Folders become nested sidebar groups.
|
|
45
|
+
* @param {SidebarOptions} data Information to build config
|
|
46
|
+
* @returns {object} returns the vuepress sidebar structure
|
|
47
|
+
*/
|
|
48
|
+
export declare const generateVueSidebar: ({ fileTree, codeFolder, srcFolder, docsFolder, title, readmeTitle, collapsible }: SidebarOptions) => {
|
|
49
|
+
[x: string]: SidebarGroup[];
|
|
26
50
|
};
|
|
51
|
+
/**
|
|
52
|
+
* Runs through the given tree structure and creates a vitepress sidebar config.
|
|
53
|
+
* Vitepress needs the text of every link, so it is read from the generated page.
|
|
54
|
+
* @param {SidebarOptions} data Information to build config
|
|
55
|
+
* @returns {object} returns the vitepress sidebar structure
|
|
56
|
+
*/
|
|
57
|
+
export declare const generateVitepressSidebar: (data: SidebarOptions) => {
|
|
58
|
+
[x: string]: VitepressSidebarItem[];
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Create the content of the `config.js`, which is placed into the generated folder.
|
|
62
|
+
* It exports the file tree and a `sidebarTree(title)` function, which returns the vuepress or vitepress sidebar.
|
|
63
|
+
* @param {SidebarOptions} options
|
|
64
|
+
* @returns {string} javascript code (ES module)
|
|
65
|
+
*/
|
|
66
|
+
export declare const createSidebarConfig: (options: SidebarOptions) => string;
|
package/dist/lib/vue-sidebar.js
CHANGED
|
@@ -1,59 +1,125 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.generateVueSidebar = void 0;
|
|
7
1
|
/*
|
|
8
2
|
* @vuepress
|
|
9
3
|
* ---
|
|
10
4
|
* headline: lib/vue-sidebar.ts
|
|
11
5
|
* ---
|
|
12
6
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { SIDEBAR_TITLE_PLACEHOLDER } from '../constants.js';
|
|
10
|
+
import { getPageName, normalizePath } from './list-folder.js';
|
|
11
|
+
/**
|
|
12
|
+
* Get the route of the generated folder, e.g. `/code/`
|
|
13
|
+
* @param {string} codeFolder
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
export const getSidebarBase = (codeFolder) => `/${normalizePath(codeFolder)}/`;
|
|
15
17
|
/**
|
|
16
|
-
* Runs through the given tree structure and creates a vuepress config
|
|
17
|
-
*
|
|
18
|
-
* @param {
|
|
19
|
-
* @
|
|
20
|
-
* @param {string} data.srcFolder ./src/ folder
|
|
21
|
-
* @param {string} data.docsFolder ./documentation/ folder
|
|
22
|
-
* @param {string} data.title title string
|
|
23
|
-
* @returns {object} returns the vuepress menu structure
|
|
18
|
+
* Runs through the given tree structure and creates a vuepress 2 sidebar config.
|
|
19
|
+
* Only pages that were generated are linked. Folders become nested sidebar groups.
|
|
20
|
+
* @param {SidebarOptions} data Information to build config
|
|
21
|
+
* @returns {object} returns the vuepress sidebar structure
|
|
24
22
|
*/
|
|
25
|
-
const generateVueSidebar = ({ fileTree, codeFolder, srcFolder, docsFolder, title }) => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
23
|
+
export const generateVueSidebar = ({ fileTree, codeFolder, srcFolder, docsFolder, title, readmeTitle = SIDEBAR_TITLE_PLACEHOLDER, collapsible = false }) => {
|
|
24
|
+
const base = getSidebarBase(codeFolder);
|
|
25
|
+
const root = normalizePath(srcFolder);
|
|
26
|
+
const toLink = (file) => {
|
|
27
|
+
if (!file.fullPath)
|
|
28
|
+
return null;
|
|
29
|
+
const relativePath = path.posix.relative(root, normalizePath(file.fullPath));
|
|
30
|
+
if (!fs.existsSync(`${path.join(docsFolder, relativePath)}.md`))
|
|
31
|
+
return null;
|
|
32
|
+
return `${base}${relativePath}`;
|
|
33
|
+
};
|
|
34
|
+
const buildChildren = (children) => {
|
|
35
|
+
const items = [];
|
|
31
36
|
for (const child of children) {
|
|
32
|
-
if (child.children
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
const path = child.fullPath.replace(srcFolder, '');
|
|
37
|
-
if (node_fs_1.default.existsSync(`${(0, node_path_1.join)(docsFolder, path)}.md`)) {
|
|
38
|
-
newChildren.push(`/${(0, node_path_1.join)(codeFolder, path)}`);
|
|
37
|
+
if (child.children) {
|
|
38
|
+
const nested = buildChildren(child.children);
|
|
39
|
+
if (nested.length > 0) {
|
|
40
|
+
items.push({ text: child.name, collapsible: true, children: nested });
|
|
39
41
|
}
|
|
40
42
|
}
|
|
43
|
+
else {
|
|
44
|
+
const link = toLink(child);
|
|
45
|
+
if (link)
|
|
46
|
+
items.push(link);
|
|
47
|
+
}
|
|
41
48
|
}
|
|
42
|
-
return
|
|
49
|
+
return items;
|
|
43
50
|
};
|
|
44
|
-
const
|
|
45
|
-
text:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
const rootFiles = [
|
|
52
|
+
{ text: readmeTitle, link: base },
|
|
53
|
+
...buildChildren(fileTree.filter((file) => !file.children))
|
|
54
|
+
];
|
|
55
|
+
const folders = fileTree
|
|
56
|
+
.filter((file) => file.children)
|
|
57
|
+
.map((folder) => ({ text: folder.name, collapsible, children: buildChildren(folder.children) }))
|
|
58
|
+
.filter((group) => group.children.length > 0);
|
|
49
59
|
return {
|
|
50
|
-
[
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
60
|
+
[base]: [{ text: title, collapsible, children: rootFiles }, ...folders]
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Get the `title` of the frontmatter of a generated page (set with the `@vuepress` block)
|
|
65
|
+
* @param {string} mdPath path to the generated markdown file
|
|
66
|
+
* @returns {string|undefined}
|
|
67
|
+
*/
|
|
68
|
+
const readPageTitle = (mdPath) => {
|
|
69
|
+
const title = /^---\r?\n[\s\S]*?^title:\s*(.+?)\s*$[\s\S]*?^---/m.exec(fs.readFileSync(mdPath, 'utf-8'))?.[1];
|
|
70
|
+
return title?.replace(/^(['"])(.*)\1$/, '$2');
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Runs through the given tree structure and creates a vitepress sidebar config.
|
|
74
|
+
* Vitepress needs the text of every link, so it is read from the generated page.
|
|
75
|
+
* @param {SidebarOptions} data Information to build config
|
|
76
|
+
* @returns {object} returns the vitepress sidebar structure
|
|
77
|
+
*/
|
|
78
|
+
export const generateVitepressSidebar = (data) => {
|
|
79
|
+
const base = getSidebarBase(data.codeFolder);
|
|
80
|
+
const root = normalizePath(data.srcFolder);
|
|
81
|
+
// the frontmatter title or the file name. Index files are shown as `index` instead of their prefixed page name.
|
|
82
|
+
const getText = (link) => {
|
|
83
|
+
const relativePath = link.slice(base.length);
|
|
84
|
+
const name = path.posix.basename(relativePath);
|
|
85
|
+
const isIndex = name === getPageName(path.posix.join(root, path.posix.dirname(relativePath)), 'index');
|
|
86
|
+
return readPageTitle(`${path.join(data.docsFolder, relativePath)}.md`) ?? (isIndex ? 'index' : name);
|
|
87
|
+
};
|
|
88
|
+
const toVitepressItem = (item, nested = true) => {
|
|
89
|
+
if (typeof item === 'string') {
|
|
90
|
+
return { text: getText(item), link: item };
|
|
91
|
+
}
|
|
92
|
+
if ('link' in item) {
|
|
93
|
+
return { text: item.text, link: item.link };
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
text: item.text,
|
|
97
|
+
// nested folders are collapsed, vitepress opens groups with the active page.
|
|
98
|
+
// collapsible top level groups stay open.
|
|
99
|
+
...(item.collapsible ? { collapsed: nested } : {}),
|
|
100
|
+
items: item.children.map((child) => toVitepressItem(child))
|
|
101
|
+
};
|
|
57
102
|
};
|
|
103
|
+
return { [base]: generateVueSidebar(data)[base].map((group) => toVitepressItem(group, false)) };
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Create the content of the `config.js`, which is placed into the generated folder.
|
|
107
|
+
* It exports the file tree and a `sidebarTree(title)` function, which returns the vuepress or vitepress sidebar.
|
|
108
|
+
* @param {SidebarOptions} options
|
|
109
|
+
* @returns {string} javascript code (ES module)
|
|
110
|
+
*/
|
|
111
|
+
export const createSidebarConfig = (options) => {
|
|
112
|
+
// the placeholder is replaced with the `title` parameter of `sidebarTree`
|
|
113
|
+
const data = { ...options, readmeTitle: SIDEBAR_TITLE_PLACEHOLDER };
|
|
114
|
+
const tree = options.format === 'vitepress' ? generateVitepressSidebar(data) : generateVueSidebar(data);
|
|
115
|
+
const sidebar = JSON.stringify(tree, null, 2).replace(JSON.stringify(SIDEBAR_TITLE_PLACEHOLDER), 'title');
|
|
116
|
+
return [
|
|
117
|
+
'// This file is generated by vuepress-jsdoc. Do not edit it manually.',
|
|
118
|
+
`export const fileTree = ${JSON.stringify(options.fileTree, null, 2)};`,
|
|
119
|
+
'',
|
|
120
|
+
`export const sidebarTree = (title = 'Mainpage') => (${sidebar});`,
|
|
121
|
+
'',
|
|
122
|
+
'export default sidebarTree;',
|
|
123
|
+
''
|
|
124
|
+
].join('\n');
|
|
58
125
|
};
|
|
59
|
-
exports.generateVueSidebar = generateVueSidebar;
|