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.
@@ -1,74 +1,68 @@
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.parseVuepressFileHeader = exports.parseComment = void 0;
7
1
  /*
8
2
  * @vuepress
9
3
  * ---
10
4
  * headline: lib/comment-parser.ts
11
5
  * ---
12
6
  */
13
- const front_matter_1 = __importDefault(require("front-matter"));
7
+ import { createRequire } from 'node:module';
8
+ // front-matter is a commonjs module with wrong types for es modules
9
+ const fm = createRequire(import.meta.url)('front-matter');
14
10
  /**
15
- * Search in file for @vuepress comment
11
+ * Search in file for a `@vuepress` comment block and parse its frontmatter
16
12
  * @param {string} fileContent content of given file
17
13
  * @returns {object} object of found frontmatter data
18
14
  */
19
- const parseComment = (fileContent) => {
15
+ export const parseComment = (fileContent) => {
16
+ const empty = { frontmatter: null, attributes: null };
17
+ if (typeof fileContent !== 'string')
18
+ return empty;
20
19
  try {
21
- const allCommentBlocks = fileContent.match(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/g);
22
- const vuepressBlock = allCommentBlocks === null || allCommentBlocks === void 0 ? void 0 : allCommentBlocks.filter((block) => {
23
- return block.split('\n').filter(line => line.indexOf('@vuepress') >= 0).length;
24
- })[0];
25
- if (!vuepressBlock) {
26
- return {
27
- frontmatter: null,
28
- attributes: null
29
- };
30
- }
31
- return (0, front_matter_1.default)(vuepressBlock
32
- .replace(/\n /g, '\n')
33
- .replace('/*', '')
34
- .replace('*/', '')
35
- .replace(/@vuepress/g, '')
36
- .replace(/\*\s?/g, '')
37
- .trim());
20
+ const vuepressBlock = fileContent.match(/\/\*[\s\S]*?\*\//g)?.find((block) => /^[\s/*]*@vuepress\b/m.test(block));
21
+ if (!vuepressBlock)
22
+ return empty;
23
+ const body = vuepressBlock
24
+ .replace(/^\/\*+/, '')
25
+ .replace(/\*+\/$/, '')
26
+ .split('\n')
27
+ // remove the leading "*" of every comment line
28
+ .map((line) => line.replace(/^\s*\*\s?/, ''))
29
+ .filter((line) => !/^\s*@vuepress\s*$/.test(line))
30
+ .join('\n')
31
+ .trim();
32
+ const { frontmatter, attributes } = fm(body);
33
+ return { frontmatter: frontmatter || null, attributes: frontmatter ? attributes : null };
38
34
  }
39
- catch (e) {
40
- return {
41
- frontmatter: null,
42
- attributes: null
43
- };
35
+ catch {
36
+ return empty;
44
37
  }
45
38
  };
46
- exports.parseComment = parseComment;
47
39
  /**
48
- * Helper function to get header as structured markdown
40
+ * Quote a string for the use inside yaml frontmatter
41
+ * @param {string} value
42
+ * @returns {string} quoted string
43
+ */
44
+ const yamlString = (value) => JSON.stringify(value);
45
+ /**
46
+ * Helper function to get the header of a page as structured markdown.
47
+ * It contains the frontmatter and a headline (`# headline`).
48
+ * The headline is omitted for vue files, because vue-docgen-cli renders its own.
49
49
  * @param {string} content file content
50
- * @param {object} file file object
50
+ * @param {DirectoryFile} file file object
51
51
  * @returns {string} markdown header
52
52
  */
53
- const parseVuepressFileHeader = (content, file) => {
54
- const { frontmatter, attributes } = (0, exports.parseComment)(content);
55
- let fileContent = '---\n';
56
- fileContent += !(attributes === null || attributes === void 0 ? void 0 : attributes.title) ? `title: ${file.name}` : '';
53
+ export const parseVuepressFileHeader = (content, file) => {
54
+ const { frontmatter, attributes } = parseComment(content);
55
+ const lines = [];
56
+ if (!attributes?.title) {
57
+ lines.push(`title: ${yamlString(file.name)}`);
58
+ }
57
59
  if (frontmatter) {
58
- fileContent += !(attributes === null || attributes === void 0 ? void 0 : attributes.title) ? '\n' : '';
59
- fileContent += `${frontmatter}`;
60
+ lines.push(frontmatter);
60
61
  }
61
- fileContent += '\n---\n';
62
- if ((attributes === null || attributes === void 0 ? void 0 : attributes.title) || file.ext !== '.vue') {
63
- let headline = file.name;
64
- if (attributes === null || attributes === void 0 ? void 0 : attributes.headline) {
65
- headline = attributes.headline;
66
- }
67
- else if (attributes === null || attributes === void 0 ? void 0 : attributes.title) {
68
- headline = attributes.title;
69
- }
70
- fileContent += `\n# ${headline}\n\n`;
62
+ let header = `---\n${lines.join('\n')}\n---\n`;
63
+ if (attributes?.title || file.ext !== '.vue') {
64
+ const headline = attributes?.headline || attributes?.title || file.name;
65
+ header += `\n# ${headline}\n\n`;
71
66
  }
72
- return fileContent;
67
+ return header;
73
68
  };
74
- exports.parseVuepressFileHeader = parseVuepressFileHeader;
@@ -0,0 +1,21 @@
1
+ import { UserConfig } from '../interfaces.js';
2
+ /**
3
+ * Helper for typed config files
4
+ * @example
5
+ * // vuepress-jsdoc.config.js
6
+ * import { defineConfig } from 'vuepress-jsdoc';
7
+ *
8
+ * export default defineConfig({ source: './lib', exclude: ['**\/*.test.js'] });
9
+ * @param {UserConfig} config
10
+ * @returns {UserConfig} the same config
11
+ */
12
+ export declare const defineConfig: (config: UserConfig) => UserConfig;
13
+ /**
14
+ * Load a config file. Without a path, the working directory is searched for a `vuepress-jsdoc.config.*` file.
15
+ * @param {string} [configPath] path to a config file
16
+ * @returns {Promise<{ path: string, config: UserConfig } | null>} config or null if there is no config file
17
+ */
18
+ export declare const loadConfig: (configPath?: string) => Promise<{
19
+ path: string;
20
+ config: UserConfig;
21
+ } | null>;
@@ -0,0 +1,57 @@
1
+ /*
2
+ * @vuepress
3
+ * ---
4
+ * headline: lib/config.ts
5
+ * ---
6
+ */
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { pathToFileURL } from 'node:url';
10
+ /**
11
+ * Names of config files, which are searched in the working directory
12
+ */
13
+ const CONFIG_FILES = [
14
+ 'vuepress-jsdoc.config.js',
15
+ 'vuepress-jsdoc.config.mjs',
16
+ 'vuepress-jsdoc.config.cjs',
17
+ 'vuepress-jsdoc.config.json'
18
+ ];
19
+ /**
20
+ * Helper for typed config files
21
+ * @example
22
+ * // vuepress-jsdoc.config.js
23
+ * import { defineConfig } from 'vuepress-jsdoc';
24
+ *
25
+ * export default defineConfig({ source: './lib', exclude: ['**\/*.test.js'] });
26
+ * @param {UserConfig} config
27
+ * @returns {UserConfig} the same config
28
+ */
29
+ export const defineConfig = (config) => config;
30
+ /**
31
+ * Load a config file. Without a path, the working directory is searched for a `vuepress-jsdoc.config.*` file.
32
+ * @param {string} [configPath] path to a config file
33
+ * @returns {Promise<{ path: string, config: UserConfig } | null>} config or null if there is no config file
34
+ */
35
+ export const loadConfig = async (configPath) => {
36
+ const file = configPath
37
+ ? path.resolve(configPath)
38
+ : CONFIG_FILES.map((name) => path.resolve(name)).find((p) => fs.existsSync(p));
39
+ if (!file)
40
+ return null;
41
+ if (!fs.existsSync(file)) {
42
+ throw new Error(`Config file not found: ${configPath}`);
43
+ }
44
+ let config;
45
+ if (file.endsWith('.json')) {
46
+ config = JSON.parse(await fs.promises.readFile(file, 'utf-8'));
47
+ }
48
+ else {
49
+ // the timestamp prevents a cached module in watch mode or tests
50
+ const module = await import(`${pathToFileURL(file).href}?t=${Date.now()}`);
51
+ config = module.default ?? module;
52
+ }
53
+ if (!config || typeof config !== 'object') {
54
+ throw new Error(`Config file has to export an object: ${file}`);
55
+ }
56
+ return { path: file, config };
57
+ };
@@ -1,15 +1,33 @@
1
- import { DirectoryFile, FileTree } from '../interfaces';
2
- /**
3
- * Recursively traverse folders and return exluded files, a file list and a file tree.
4
- * @param {string} srcPath path to source dir
5
- * @param {array} exclude exluded file patter list
6
- * @param {array} include included file patter list
7
- * @param {string} mainPath path to hold source dir
8
- * @param {object} tree tree array
9
- * @returns {object} paths array, tree, excluded array
10
- */
11
- export declare const listFolder: (srcPath: string, exclude: string[], include: string[], mainPath?: string, tree?: FileTree[]) => Promise<{
1
+ import { DirectoryFile, FileTree } from '../interfaces.js';
2
+ export interface ListFolderResult {
3
+ /** All files which should be parsed */
12
4
  paths: DirectoryFile[];
5
+ /** Folder structure of all parsable files */
13
6
  tree: FileTree[];
7
+ /** Files which were excluded by the include/exclude patterns */
14
8
  excluded: DirectoryFile[];
15
- }>;
9
+ }
10
+ /**
11
+ * Normalize a path to a posix path without leading `./` and trailing slash
12
+ * @param {string} p path
13
+ * @returns {string} normalized path
14
+ */
15
+ export declare const normalizePath: (p: string) => string;
16
+ /**
17
+ * Get the page name of a file. Index files are prefixed with their folder,
18
+ * because an `index.md` would collide with the `README.md` of the folder.
19
+ * @param {string} dir directory of the file
20
+ * @param {string} name file name without extension
21
+ * @returns {string} page name
22
+ */
23
+ export declare const getPageName: (dir: string, name: string) => string;
24
+ /**
25
+ * Recursively traverse folders and return excluded files, a file list and a file tree.
26
+ * Only `.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, `.tsx`, `.mts`, `.cts` and `.vue` files are listed.
27
+ * Hidden folders and `node_modules` are skipped.
28
+ * @param {string} srcPath path to source dir
29
+ * @param {string[]} exclude excluded file pattern list (relative to srcPath)
30
+ * @param {string[]} include included file pattern list (relative to srcPath)
31
+ * @returns {Promise<ListFolderResult>} paths array, tree, excluded array
32
+ */
33
+ export declare const listFolder: (srcPath: string, exclude?: string[], include?: string[]) => Promise<ListFolderResult>;
@@ -1,86 +1,116 @@
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.listFolder = void 0;
16
1
  /*
17
2
  * @vuepress
18
3
  * ---
19
4
  * headline: lib/list-folder.ts
20
5
  * ---
21
6
  */
22
- const micromatch_1 = __importDefault(require("micromatch"));
23
- const promises_1 = __importDefault(require("node:fs/promises"));
24
- const node_path_1 = __importDefault(require("node:path"));
7
+ import mm from 'micromatch';
8
+ import fs from 'node:fs/promises';
9
+ import path from 'node:path';
10
+ import { SCRIPT_EXTENSIONS, VUE_EXTENSIONS } from '../constants.js';
11
+ const SUPPORTED_EXTENSIONS = [...SCRIPT_EXTENSIONS, ...VUE_EXTENSIONS];
12
+ /**
13
+ * Normalize a path to a posix path without leading `./` and trailing slash
14
+ * @param {string} p path
15
+ * @returns {string} normalized path
16
+ */
17
+ export const normalizePath = (p) => path.posix.normalize(p.split(path.sep).join('/')).replace(/\/$/, '') || '.';
18
+ /**
19
+ * Normalize pattern lists, which may be passed as comma separated string or array
20
+ * @param {string|string[]} patterns patterns
21
+ * @returns {string[]} list of non empty patterns
22
+ */
23
+ const toPatternList = (patterns) => {
24
+ if (typeof patterns === 'string')
25
+ return patterns.split(',').filter(Boolean);
26
+ if (Array.isArray(patterns))
27
+ return patterns.filter((p) => typeof p === 'string' && p.length > 0);
28
+ return [];
29
+ };
30
+ /**
31
+ * Check if a file should be documented
32
+ * @param {string} fileName name of the file
33
+ * @returns {boolean}
34
+ */
35
+ const isSupportedFile = (fileName) => SUPPORTED_EXTENSIONS.includes(path.extname(fileName)) && !/\.d\.[cm]?ts$/.test(fileName);
36
+ /**
37
+ * Get the page name of a file. Index files are prefixed with their folder,
38
+ * because an `index.md` would collide with the `README.md` of the folder.
39
+ * @param {string} dir directory of the file
40
+ * @param {string} name file name without extension
41
+ * @returns {string} page name
42
+ */
43
+ export const getPageName = (dir, name) => {
44
+ if (name !== 'index')
45
+ return name;
46
+ const prefix = dir
47
+ .split('/')
48
+ .filter((segment) => segment && segment !== '.' && segment !== '..')
49
+ .join('-');
50
+ return `${prefix || path.basename(path.resolve(dir))}-${name}`;
51
+ };
25
52
  /**
26
- * Recursively traverse folders and return exluded files, a file list and a file tree.
53
+ * Recursively traverse folders and return excluded files, a file list and a file tree.
54
+ * Only `.js`, `.jsx`, `.mjs`, `.cjs`, `.ts`, `.tsx`, `.mts`, `.cts` and `.vue` files are listed.
55
+ * Hidden folders and `node_modules` are skipped.
27
56
  * @param {string} srcPath path to source dir
28
- * @param {array} exclude exluded file patter list
29
- * @param {array} include included file patter list
30
- * @param {string} mainPath path to hold source dir
31
- * @param {object} tree tree array
32
- * @returns {object} paths array, tree, excluded array
57
+ * @param {string[]} exclude excluded file pattern list (relative to srcPath)
58
+ * @param {string[]} include included file pattern list (relative to srcPath)
59
+ * @returns {Promise<ListFolderResult>} paths array, tree, excluded array
33
60
  */
34
- const listFolder = (srcPath, exclude, include, mainPath, tree = []) => __awaiter(void 0, void 0, void 0, function* () {
35
- if (!Boolean(exclude) || !Array.isArray(exclude)) {
36
- exclude = [];
37
- }
38
- if (!Boolean(include) || !Array.isArray(include)) {
39
- include = [];
40
- }
41
- exclude = exclude.filter(Boolean);
42
- include = include.filter(Boolean);
61
+ export const listFolder = async (srcPath, exclude = [], include = []) => {
62
+ const excludePatterns = toPatternList(exclude);
63
+ const includePatterns = toPatternList(include);
64
+ const patterns = [...includePatterns, ...excludePatterns.map((e) => '!' + e)];
43
65
  const paths = [];
44
66
  const excluded = [];
45
- const dirs = srcPath
46
- ? yield promises_1.default.readdir(srcPath, {
47
- withFileTypes: true
48
- })
49
- : [];
50
- for (const dirent of dirs) {
51
- const filePath = node_path_1.default.join(srcPath, dirent.name);
52
- const isDir = dirent.isDirectory();
53
- const ext = node_path_1.default.extname(filePath);
54
- let name = node_path_1.default.basename(filePath).replace(ext, '');
55
- const folder = filePath.replace(name, '').replace(ext, '');
56
- if (name.endsWith('index')) {
57
- name = srcPath.replace(/\//g, '-') + '-' + name;
58
- }
59
- const file = Object.assign({ isDir,
60
- name, path: filePath }, (!isDir ? { ext, folder } : {}));
61
- // skip readmes as they are automatically resolved
62
- if (name.toLowerCase() === 'readme')
63
- continue;
64
- const baseSrc = mainPath || srcPath;
65
- if (micromatch_1.default.every(node_path_1.default.join(srcPath.replace(baseSrc, ''), dirent.name), [...include, ...exclude.map((e) => '!' + e)]) ||
66
- isDir) {
67
- let treeEntry = {
68
- name
69
- };
70
- if (isDir) {
71
- treeEntry.children = [];
72
- paths.push(...(yield (0, exports.listFolder)(filePath, exclude, include, baseSrc, treeEntry.children)).paths);
73
- }
74
- else {
75
- treeEntry = Object.assign(Object.assign({}, treeEntry), { path: `/${name}`, fullPath: node_path_1.default.join(srcPath, name), ext });
67
+ const tree = [];
68
+ if (!srcPath || typeof srcPath !== 'string') {
69
+ return { paths, tree, excluded };
70
+ }
71
+ const root = normalizePath(srcPath);
72
+ const walk = async (dir, subTree) => {
73
+ const dirents = await fs.readdir(dir, { withFileTypes: true });
74
+ dirents.sort((a, b) => a.name.localeCompare(b.name));
75
+ const files = dirents.filter((d) => !d.isDirectory());
76
+ const dirs = dirents.filter((d) => d.isDirectory());
77
+ for (const dirent of files) {
78
+ if (!isSupportedFile(dirent.name))
79
+ continue;
80
+ const filePath = path.posix.join(dir, dirent.name);
81
+ const relativePath = path.posix.relative(root, filePath);
82
+ const ext = path.extname(dirent.name);
83
+ const name = getPageName(dir, path.basename(dirent.name, ext));
84
+ const folder = `${dir}/`;
85
+ const file = { name, ext, folder, path: filePath };
86
+ if (patterns.length && !mm.every(relativePath, patterns)) {
87
+ excluded.push(file);
88
+ continue;
76
89
  }
77
- tree.push(treeEntry);
78
90
  paths.push(file);
91
+ subTree.push({
92
+ name,
93
+ path: `/${name}`,
94
+ fullPath: path.posix.join(dir, name),
95
+ ext
96
+ });
79
97
  }
80
- else {
81
- excluded.push(file);
98
+ for (const dirent of dirs) {
99
+ if (dirent.name.startsWith('.') || dirent.name === 'node_modules')
100
+ continue;
101
+ const dirPath = path.posix.join(dir, dirent.name);
102
+ const relativeDir = path.posix.relative(root, dirPath);
103
+ // a folder can be excluded as a whole, e.g. --exclude="tests" or --exclude="**/__tests__"
104
+ if (excludePatterns.length && mm.isMatch(relativeDir, excludePatterns))
105
+ continue;
106
+ const children = [];
107
+ await walk(dirPath, children);
108
+ // do not add empty folders to the tree
109
+ if (children.length > 0) {
110
+ subTree.push({ name: dirent.name, children });
111
+ }
82
112
  }
83
- }
113
+ };
114
+ await walk(root, tree);
84
115
  return { paths, tree, excluded };
85
- });
86
- exports.listFolder = listFolder;
116
+ };
@@ -1,37 +1,53 @@
1
- import { StatisticType } from '../constants';
2
- import { DirectoryFile, ParseReturn } from '../interfaces';
1
+ import { DirectoryFile, ParseReturn } from '../interfaces.js';
2
+ export interface ParseOptions {
3
+ /** source folder relative to the working directory */
4
+ srcFolder: string;
5
+ /** destination folder relative to the working directory */
6
+ destFolder: string;
7
+ /** path to a jsdoc config */
8
+ jsDocConfigPath?: string;
9
+ /** additional jsdoc2markdown partials */
10
+ partials?: string[];
11
+ }
3
12
  /**
4
- * Parse a typescript or javascript file
5
- * @param file {DirectoryFile}
6
- * @param srcFolder {string}
7
- * @param destFolder {string}
8
- * @param configPath {string}
9
- * @param partials {string | string[]}
10
- * @returns {object} file data
11
- */
12
- export declare const parseFile: (file: DirectoryFile, srcFolder: string, destFolder: string, configPath: string, partials: string | string[]) => Promise<ParseReturn | null>;
13
- /**
14
- * Parse a vue file
13
+ * Get the folder of the generated markdown file
15
14
  * @param {DirectoryFile} file
16
15
  * @param {string} srcFolder
17
16
  * @param {string} destFolder
18
- * @returns {object} file data
17
+ * @returns {string} destination folder
18
+ */
19
+ export declare const getDestFolder: (file: Pick<DirectoryFile, 'folder'>, srcFolder: string, destFolder: string) => string;
20
+ /**
21
+ * Remove TypeScript types from source code, so jsdoc is able to parse it.
22
+ * Types are replaced with whitespace, so all comments and line numbers stay intact.
23
+ * @param {string} code TypeScript code
24
+ * @returns {string} JavaScript code
25
+ */
26
+ export declare const stripTypes: (code: string) => string;
27
+ /**
28
+ * Convert TypeScript to JavaScript. Type stripping of Node.js is used first, because it keeps all line numbers.
29
+ * If the file contains syntax which needs a transformation, the TypeScript compiler is used.
30
+ * @param {string} code TypeScript code
31
+ * @param {string} fileName
32
+ * @returns {string} JavaScript code
33
+ */
34
+ export declare const typescriptToJavaScript: (code: string, fileName: string) => string;
35
+ /**
36
+ * Escape vue interpolations (`{{ }}`) outside of code, because VuePress compiles every page as a vue template.
37
+ * @param {string} markdown
38
+ * @returns {string} escaped markdown
39
+ */
40
+ export declare const escapeVueInterpolation: (markdown: string) => string;
41
+ /**
42
+ * Parse a javascript, typescript or vue file and create the markdown content
43
+ * @param {DirectoryFile} file
44
+ * @param {ParseOptions} options
45
+ * @returns {Promise<ParseReturn>} file data
19
46
  */
20
- export declare const parseVueFile: (file: DirectoryFile, srcFolder: string, destFolder: string) => Promise<ParseReturn | null>;
47
+ export declare const parseFile: (file: DirectoryFile, options: ParseOptions) => Promise<ParseReturn>;
21
48
  /**
22
- * Write content on disk
23
- * @param {ParseReturn | null} parseData
24
- * @param {string} dest
25
- * @returns {Promise | null} null or type with some data of the saved file
49
+ * Write the parsed content to disk. Files without content are skipped.
50
+ * @param {ParseReturn} parseData
51
+ * @returns {Promise<string | null>} path of the written file or null
26
52
  */
27
- export declare const writeContentToFile: (parseData: ParseReturn | null, dest: string) => Promise<{
28
- type: StatisticType;
29
- success?: boolean | undefined;
30
- dest?: string | undefined;
31
- file?: DirectoryFile | undefined;
32
- content?: string | undefined;
33
- empty?: boolean | undefined;
34
- excluded?: boolean | undefined;
35
- relativePathSrc?: string | undefined;
36
- relativePathDest?: string | undefined;
37
- } | null>;
53
+ export declare const writeContentToFile: (parseData: ParseReturn) => Promise<string | null>;