UNPKG

@agds/cli-plugin-doc

Version:

agds系统doc文档生成器

360 lines (303 loc) 12.4 kB
"use strict"; function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } const ejs = require('ejs'); const jsdocRender = require('./utils/jsdocRender'); const getFilesPath = require('./utils/getFilesPath'); const defaultConfig = require('./utils/config'); const path = require('path'); const fs = require('fs-extra'); const { FastFs, FastPath, PresetUtils } = require('@agds/node-utils'); const { exec } = require('child_process'); const renderCode = require('./utils/renderCode'); /** * GenDoc 基于注释和可运行的示例代码自动生成文档的强大工具类 * * #### 引入 * * ```js * const GenDoc = require('@agds/cli-plugin-doc'); * ``` */ class GenDoc { /** * 基于ejs,用模板渲染文档 * * @param {RenderOptions} options 获取用来渲染模板的数据 * @returns {Promise<string>} 异步返回基于ejs模板渲染的文档文本 */ static async render(options) { const config = await _mergeToDefaultConfig(options); const renderData = await this.getRenderData(config, false); const res = await ejs.renderFile(config.template, renderData); if (config.output) { await FastFs.writeFile(FastPath.getCwdPath(config.output), res); } else { return res; } } /** * 获取用来渲染模板的数据(jsdoc生成的文档和示例代码的内容) * * @param {RenderOptions} options 配置参数 * @param {boolean} [needMergeConfig=true] 是否需要调用`_mergeToDefaultConfig`, * options已经是merge处理过的就不需要调用,否则不推荐传入`false` * 会导致别名不支持 * @returns {Promise<GetRenderDataResult>} */ static async getRenderData(options, needMergeConfig = true) { // node API调用时需要merge以附上默认配置 if (needMergeConfig) { options = await _mergeToDefaultConfig(options); } /* istanbul ignore next */ const { jsdoc2mdOptions, codesOptions, helpers = {} } = options; const promises = []; let docs; if (jsdoc2mdOptions && jsdoc2mdOptions.files && jsdoc2mdOptions.files.length) { promises.push(jsdocRender(jsdoc2mdOptions).then(res => { docs = res; })); } let codes; if (codesOptions && JSON.stringify(codesOptions) !== '{}') { promises.push(this.getFilesCode(codesOptions).then(res => { codes = res; })); } let pkg = {}; const pkgPath = FastPath.getCwdPath('package.json'); /* istanbul ignore next */ if (FastFs.getPathStatSync(pkgPath)) { promises.push(FastFs.readJson(pkgPath).then(res => { pkg = res; })); } await Promise.all(promises); return { docs, codes, helpers, pkg }; } /** * 基于glob的文件遍历函数,返回文件对应内容的数组, * 以文件夹为单位返回文件内容对象,key是文件的extname * * @param {import('./utils/getFilesPath').GetFilesCodeOptions} options 获取源代码的文件路径配置参数 * @returns {Promise<Array<import('./utils/renderCode').GetFilesCodeResult>>} */ static async getFilesCode(options) { const filesList = getFilesPath(options); return filesList.map(files => { const codes = {}; files.forEach(file => { const ext = path.extname(file).replace(/^\./, ''); const code = fs.readFileSync(file, { encoding: 'utf8' }); codes[ext] = codes[ext] || []; codes[ext].push(code); }); return codes; }); } /** * 获取命令行使用帮助文档 * 建议提前确保全局使用了最新的脚本 * 函数为异步函数,注意不能作为ejs帮助函数传入,可以获取返回值后,将返回值作为helpers的变量传入 * * @returns {Promise<string[]>} */ static async getCliUsages() { const pkgPath = FastPath.getCwdPath('package.json'); /* istanbul ignore next */ if (FastFs.getPathStatSync(pkgPath)) { const pkg = require(pkgPath); const bin = pkg.bin; try { return await Promise.all(Object.keys(bin).map(key => execPromise(`${key} -h`))).then(res => res.filter(Boolean)); } catch (error) { /* istanbul ignore next */ await Promise.all([execPromise('chmod -R 750 lib'), execPromise('npm link')]); /* istanbul ignore next */ return this.getCliUsages(); } } else { /* istanbul ignore next */ throw new Error('未检测到根目录下存在【package.json】文件'); } } /** * 读取文件内容 * * @param {string} filename 相对于运行目录的文件路径 * @returns {string} */ static getFileContent(filename) { return fs.readFileSync(FastPath.getCwdPath(filename), { encoding: 'utf-8' }); } } _defineProperty(GenDoc, "renderCode", renderCode); ; module.exports = GenDoc; /* istanbul ignore next */ /** * 将当前配置和默认配置合并 * * @param {RenderOptions} options 获取用来渲染模板的数据 * @returns {RenderOptions} 异步返回基于ejs模板渲染的文档文本 * @ignore */ async function _mergeToDefaultConfig(options = {}) { // 获取用户本地配置文件 const cwdConfPath = FastPath.getCwdPath(options.config || 'agds.doc.config.js'); options.presets = options.presets || []; if (FastFs.getPathStatSync(cwdConfPath)) { const userConfig = require(cwdConfPath); options.presets.unshift(userConfig); } options.presets.push({ jsdoc2mdOptions: {}, codesOptions: {}, jsdocEngineOptions: {} }); let config = await PresetUtils.getDeepPresetMerge(options); // 支持配置是否使用默认配置 if (config.default !== false) { config.presets = config.presets || []; config.presets.push(defaultConfig); } config = await PresetUtils.getDeepPresetMergeAndModify(config); // files别名支持 if (config.files) { config.jsdoc2mdOptions.files = config.files; } // codesDir&codesFiles别名支持 if (config.codesDir && config.codesFiles) { config.codesOptions.dir = config.codesDir; config.codesOptions.files = config.codesFiles; } // jsdocEngineOptions配置支持 if (config.jsdocEngineOptions && JSON.stringify(config.jsdocEngineOptions) !== '{}') { const jsdocEngineConfigPath = path.join(__dirname, '../.temp/jsdoc.conf.json'); await FastFs.writeJsonFormat(jsdocEngineConfigPath, config.jsdocEngineOptions); config.jsdoc2mdOptions.configure = jsdocEngineConfigPath; } return config; } /* istanbul ignore next */ /** * execPromise * * @param {string} command shell命令 * @returns {Promise<string>} * @ignore */ function execPromise(command) { return new Promise((resolve, reject) => { exec(command, (error, stdout, stderr) => { if (error) { return reject(error); } return resolve(stdout); }); }); } /** * 函数[GenDoc.getRenderData](#GenDoc.getRenderData)的返回值 * * @typedef {object} GetRenderDataResult * @property {string} docs 源码使用jsdoc渲染后的markdown文本 * @property {Array<import('./utils/renderCode').GetFilesCodeResult>} codes 获取到的代码内容 * @memberof GenDoc.getRenderData * @alias GetRenderDataResult */ /** * 渲染函数的配置参数 * * @memberof GenDoc.render * @alias RenderOptions * @typedef {object} RenderOptions * @property {string[]} files `jsdoc2mdOptions.files`的别名 * @property {fs.PathLike} output doc文档渲染导出的文件名称路径,相对于cwd目录 * @property {string} template ejs渲染的模板相对于cwd的路径或者绝对路径 * @property {string} [codesDir] `codesOptions.dir`的别名 * @property {string[]} [codesFiles] `codesOptions.codesFiles`的别名 * @property {fs.PathLike} [conifg=agds.doc.config.js] 配置文件路径,默认为运行目录下的`agds.doc.config.js`,仅支持`js`文件类型 * @property {boolean} [default] 是否合并默认配置,一般我们认为您是需要默认配置的,当默认配置和你的需求冲突时可以设置为`false` * @property {import('./utils/jsdocRender').Jsdoc2mdOptions} [jsdoc2mdOptions] jsdocToMarkdown配置参数 * @property {import('./utils/getFilesPath').GetFilesCodeOptions} [codesOptions] 获取源代码的文件路径配置参数 * @property {JsdocEngineOptions} [jsdocEngineOptions] jsdoc解析引擎的配置,实际上是`jsdoc.conf.js`的整合, * 也可以使用 `RenderOptions.jsdoc2mdOptions.configure`字段来指定本地的jsdoc配置 * 配置选项[👉参考文档](https://jsdoc.app/about-configuring-jsdoc.html) * @property {DefaultHelpers} [helpers] 注入ejs模板的`helpers`对象,提供模板使用的帮助函数和变量,配合模板使用 * @property {RenderOptions[]} [presets] 基于preset机制实现配置支持预设的功能, * 具体[👉参考文档](https://gitee.com/agile-development-system/node-utils#presetutilsgetdeeppresetmergeconfig--config)`PresetUtils.getDeepPresetMerge` * @property {import('@agds/node-utils').ConfigModify} [modify] 将默认配置和preset合并后生成的config再次处理的钩子 * 具体[👉参考文档](https://gitee.com/agile-development-system/node-utils#presetutilsgetdeeppresetmergeconfig--config) */ /** * 默认模板所支持的`helpers`属性 * * @typedef {Object} DefaultHelpers * @property {string} [installCode] 安装脚本,bash脚本,默认为`npm i ${pkg.name}`,如不符合要求,可以通过此字段自行修改 * @property {boolean} [devInstall] 是否是作为开发依赖下载,`true`时,默认下载代码自动拼接npm `-D` 参数 * @property {string} [importCode] 引入代码示例,js字符串 * @property {string} [exportCode] 导出代码,js字符串 * @property {string[]} [cliUsages] cli命令行使用帮助文档,格式类似`agds-doc -h`的输出内容 * @property {string} [remark] 文档备注信息,md字符串 * @property {GenDoc.renderCode} [renderCode] 将`GenDoc.getFileCodes`的返回值渲染成对应的代码段 * @property {Postfix[]} [postfixes] 后缀内容数组 * @property {string} [logo] logo * @property {Badge[]} [bradges] 徽标数组 * @memberof GenDoc.render * @alias DefaultHelpers */ /** * 徽标对象 * * @typedef {Object} Badge * @property {string} url 图片链接 * @property {string} [name] 图片名称 * @property {string} [link] 跳转链接 * @memberof GenDoc.render * @alias Badge */ /** * 后缀内容类型 * * @typedef {Object} Postfix * @property {string} [id] 锚点的名称,填写之后可以支持 `href="#${id}"`锚点定位 * @property {string} [title] 内容的标题 * @property {string} [desc] 内容的描述 * @property {string} [content] 内容的正文 * @memberof GenDoc.render * @alias Postfix */ /** * jsdoc引擎配置[👉参考文档](https://jsdoc.app/about-configuring-jsdoc.html) * * @typedef {Object} JsdocEngineOptions * @property {string[]} plugins 插件路径数组(建议使用绝对路径) * @property {number} recurseDepth 指定递归深度 * @property {object} source 指定输入文件 * @property {string[]} source.include 路径数组,包含 JSDoc 应为其生成文档的文件 * @property {string[]} source.exclude JSDoc 应该忽略的可选路径数组 * @property {string} source.includePattern 可选字符串,解释为正则表达式,如果存在,则所有文件名都必须与此正则表达式匹配才能由 JSDoc 处理 * @property {string} source.excludePattern 可选字符串,解释为正则表达式。如果存在,任何匹配此正则表达式的文件都将被忽略。 * @property {'module'|'script'} [sourceType='module'] 指定源类型,会影响 JSDoc 解析 JavaScript 文件的方式 * @property {object} tags 配置标签和标签字典,tags控制允许使用哪些 JSDoc 标签以及如何解释每个标签的选项。 * @property {boolean|string[]} [tags.allowUnknownTags=true] 影响 JSDoc 处理无法识别的标签的方式 * @property {string[]} [tags.dictionaries=["jsdoc","closure"]] 控制 JSDoc 识别哪些标签,以及 JSDoc 如何解释它识别的标签。 * @memberof GenDoc.render * @alias JsdocEngineOptions */