From d2741b50fff38711361fa21fc9b08dfb4bfa1795 Mon Sep 17 00:00:00 2001 From: leihaohao Date: Mon, 16 Aug 2021 00:09:29 +0800 Subject: [PATCH 1/2] =?UTF-8?q?build:=20devui-cli=20=E5=A2=9E=E5=8A=A0=20s?= =?UTF-8?q?idebar.ts=20=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- devui-cli/commands/create.js | 81 ++++++++++++++++++------ devui-cli/index.js | 5 +- devui-cli/inquirers/component.js | 4 +- devui-cli/inquirers/create.js | 4 +- devui-cli/shared/constant.js | 17 +++++ devui-cli/shared/utils.js | 32 ++++++++-- devui-cli/templates/component.js | 6 +- devui-cli/templates/vitepress-sidebar.js | 35 ++++++++++ 8 files changed, 153 insertions(+), 31 deletions(-) create mode 100644 devui-cli/templates/vitepress-sidebar.js diff --git a/devui-cli/commands/create.js b/devui-cli/commands/create.js index b9755438..753f4f78 100644 --- a/devui-cli/commands/create.js +++ b/devui-cli/commands/create.js @@ -1,5 +1,5 @@ const logger = require('../shared/logger') -const { bigCamelCase, resolveDirFilesInfo, parseExportByFileInfo } = require('../shared/utils') +const { bigCamelCase, resolveDirFilesInfo, parseExportByFileInfo, parseComponentInfo } = require('../shared/utils') const fs = require('fs-extra') const { resolve } = require('path') const { @@ -10,7 +10,13 @@ const { INDEX_FILE_NAME, VUE_DEVUI_FILE, VUE_DEVUI_IGNORE_DIRS, - VUE_DEVUI_FILE_NAME + VUE_DEVUI_FILE_NAME, + CREATE_SUPPORT_TYPES, + CREATE_UNFINISHED_TYPES, + CREATE_SUPPORT_TYPE_MAP, + SITES_COMPONENTS_DIR, + VITEPRESS_SIDEBAR_FILE, + VITEPRESS_SIDEBAR_FILE_NAME } = require('../shared/constant') const { isEmpty, kebabCase } = require('lodash') const inquirer = require('inquirer') @@ -27,42 +33,48 @@ const { } = require('../templates/component') const { createVueDevuiTemplate } = require('../templates/vue-devui') const ora = require('ora') +const { createVitepressSidebarTemplate } = require('../templates/vitepress-sidebar') exports.validateCreateType = (type) => { - const flag = /^(component|(vue-devui)|(vitepress\/sidebar))$/.test(type) + const re = new RegExp('^(' + CREATE_SUPPORT_TYPES.map((t) => `(${t})`).join('|') + ')$') + const flag = re.test(type) - !flag && logger.error('类型错误,可选类型为:component, vue-devui, vitepress/sidebar') + !flag && logger.error(`类型错误,可选类型为:${CREATE_SUPPORT_TYPES.join(', ')}`) return flag ? type : null } +// TODO: 待优化代码结构 exports.create = async (cwd) => { - let { type, ignoreParseError } = cwd + let { type } = cwd if (isEmpty(type)) { const result = await inquirer.prompt([selectCreateType()]) type = result.type } - if (['vitepress/sidebar'].includes(type)) { + if (CREATE_UNFINISHED_TYPES.includes(type)) { logger.info('抱歉,该功能暂未完成!') - return process.exit(0) + process.exit(0) } + let params = {} + try { switch (type) { - case 'component': - const result = await inquirer.prompt([typeName(), typeTitle(), selectCategory(), selectParts()]) - result.hasComponent = result.parts.includes(COMPONENT_PARTS_MAP.get('component')) - result.hasDirective = result.parts.includes(COMPONENT_PARTS_MAP.get('directive')) - result.hasService = result.parts.includes(COMPONENT_PARTS_MAP.get('service')) + case CREATE_SUPPORT_TYPE_MAP.component: + params = await inquirer.prompt([typeName(), typeTitle(), selectCategory(), selectParts()]) + params.hasComponent = params.parts.includes(COMPONENT_PARTS_MAP.get('component')) + params.hasDirective = params.parts.includes(COMPONENT_PARTS_MAP.get('directive')) + params.hasService = params.parts.includes(COMPONENT_PARTS_MAP.get('service')) - await createComponent(result) + await createComponent(params, cwd) break - case 'vue-devui': - await createVueDevui(ignoreParseError) + case CREATE_SUPPORT_TYPE_MAP['vue-devui']: + await createVueDevui(params, cwd) break - case 'vitepress/sidebar': + case CREATE_SUPPORT_TYPE_MAP['vitepress/sidebar']: + await createVitepressSidebar(params, cwd) break default: break @@ -99,6 +111,7 @@ async function createComponent(params = {}) { const directiveTemplate = createDirectiveTemplate(_params) const serviceTemplate = createServiceTemplate(_params) const indexTemplate = createIndexTemplate(_params) + // TODO: 增加测试模板 const testsTemplate = createTestsTemplate(_params) const componentDir = resolve(DEVUI_DIR, componentName) @@ -107,7 +120,7 @@ async function createComponent(params = {}) { if (fs.pathExistsSync(componentDir)) { logger.error(`${bigCamelCase(componentName)} 组件目录已存在!`) - return process.exit(1) + process.exit(1) } let spinner = ora(`创建组件 ${bigCamelCase(componentName)} 开始...`).start() @@ -146,7 +159,7 @@ async function createComponent(params = {}) { } } -async function createVueDevui(ignoreParseError) { +async function createVueDevui(params, { ignoreParseError }) { const fileInfo = resolveDirFilesInfo(DEVUI_DIR, VUE_DEVUI_IGNORE_DIRS) const exportModules = [] @@ -172,3 +185,35 @@ async function createVueDevui(ignoreParseError) { process.exit(1) } } + +async function createVitepressSidebar(params, { cover }) { + if (fs.pathExistsSync(VITEPRESS_SIDEBAR_FILE) && !cover) { + logger.warning(`${VITEPRESS_SIDEBAR_FILE_NAME} 文件已存在!`) + process.exit(0) + } + + const fileInfo = resolveDirFilesInfo(DEVUI_DIR, VUE_DEVUI_IGNORE_DIRS) + const componentsInfo = [] + + fileInfo.forEach((f) => { + const info = parseComponentInfo(f.dirname) + + if (isEmpty(info)) return + + componentsInfo.push(info) + }) + + const template = createVitepressSidebarTemplate(componentsInfo) + + let spinner = ora(`创建 ${VITEPRESS_SIDEBAR_FILE_NAME} 文件开始...`).start() + + try { + await fs.writeFile(VITEPRESS_SIDEBAR_FILE, template, { encoding: 'utf-8' }) + + spinner.succeed(`创建 ${VITEPRESS_SIDEBAR_FILE_NAME} 文件成功!`) + logger.info(`文件地址:${VITEPRESS_SIDEBAR_FILE}`) + } catch (e) { + spinner.fail(e.toString()) + process.exit(1) + } +} diff --git a/devui-cli/index.js b/devui-cli/index.js index 0ff397d5..db57d98c 100755 --- a/devui-cli/index.js +++ b/devui-cli/index.js @@ -1,15 +1,16 @@ #!/usr/bin/env node const { Command } = require('commander') const { create, validateCreateType } = require('./commands/create') -const { VERSION } = require('./shared/constant') +const { VERSION, CREATE_SUPPORT_TYPES } = require('./shared/constant') const program = new Command() program .command('create') .description('创建一个组件模板或配置文件') - .option('-t --type ', '创建类型,可选值:component, vue-devui, vitepress/sidebar', validateCreateType) + .option('-t --type ', `创建类型,可选值:${CREATE_SUPPORT_TYPES.join(', ')}`, validateCreateType) .option('--ignore-parse-error', '忽略解析错误', false) + .option('--cover', '覆盖原文件', false) .action(create) program.parse().version(VERSION) diff --git a/devui-cli/inquirers/component.js b/devui-cli/inquirers/component.js index d4d2362b..226bb7ed 100644 --- a/devui-cli/inquirers/component.js +++ b/devui-cli/inquirers/component.js @@ -1,4 +1,4 @@ -const { COMPONENT_PARTS_MAP } = require('../shared/constant') +const { COMPONENT_PARTS_MAP, VITEPRESS_SIDEBAR_CATEGORY } = require('../shared/constant') exports.typeName = () => ({ name: 'name', @@ -28,7 +28,7 @@ exports.selectCategory = () => ({ name: 'category', type: 'list', message: '(必填)请选择组件分类,将用作文档列表分类:', - choices: ['通用', '导航', '反馈', '扩展服务', '数据录入', '数据展示', '布局'], + choices: VITEPRESS_SIDEBAR_CATEGORY, default: 0 }) diff --git a/devui-cli/inquirers/create.js b/devui-cli/inquirers/create.js index f582f31a..5bc07766 100644 --- a/devui-cli/inquirers/create.js +++ b/devui-cli/inquirers/create.js @@ -1,7 +1,9 @@ +const { CREATE_SUPPORT_TYPES } = require('../shared/constant') + exports.selectCreateType = () => ({ name: 'type', type: 'list', message: '(必填)请选择创建类型:', - choices: ['component', 'vue-devui', 'vitepress/sidebar'], + choices: CREATE_SUPPORT_TYPES, default: 0 }) diff --git a/devui-cli/shared/constant.js b/devui-cli/shared/constant.js index d4852ff0..da844a75 100644 --- a/devui-cli/shared/constant.js +++ b/devui-cli/shared/constant.js @@ -10,9 +10,26 @@ exports.INDEX_FILE_NAME = 'index.ts' exports.VUE_DEVUI_IGNORE_DIRS = ['shared', 'style'] exports.VUE_DEVUI_FILE_NAME = 'vue-devui.ts' exports.VUE_DEVUI_FILE = resolve(this.DEVUI_DIR, this.VUE_DEVUI_FILE_NAME) +exports.SITES_DIR = resolve(this.CWD, 'sites') +exports.SITES_COMPONENTS_DIR_NAME = 'components' +exports.SITES_COMPONENTS_DIR = resolve(this.SITES_DIR, this.SITES_COMPONENTS_DIR_NAME) +exports.VITEPRESS_DIR = resolve(this.SITES_DIR, '.vitepress') +exports.VITEPRESS_SIDEBAR_FILE_NAME = 'sidebar.ts' +exports.VITEPRESS_SIDEBAR_FILE = resolve(this.VITEPRESS_DIR, `config/${this.VITEPRESS_SIDEBAR_FILE_NAME}`) + +// 这里的分类顺序将会影响最终生成的页面侧边栏顺序 +exports.VITEPRESS_SIDEBAR_CATEGORY = ['通用', '导航', '反馈', '数据录入', '数据展示', '布局'] exports.COMPONENT_PARTS_MAP = new Map([ ['component', 'component(组件)'], ['directive', 'directive(指令)'], ['service', 'service(服务)'] ]) + +exports.CREATE_SUPPORT_TYPE_MAP = Object.freeze({ + component: 'component', + 'vue-devui': 'vue-devui', + 'vitepress/sidebar': 'vitepress/sidebar' +}) +exports.CREATE_SUPPORT_TYPES = Object.keys(this.CREATE_SUPPORT_TYPE_MAP) +exports.CREATE_UNFINISHED_TYPES = [] diff --git a/devui-cli/shared/utils.js b/devui-cli/shared/utils.js index 5c3f840f..6dbb6ed3 100644 --- a/devui-cli/shared/utils.js +++ b/devui-cli/shared/utils.js @@ -1,5 +1,5 @@ const { camelCase, upperFirst } = require('lodash') -const { INDEX_FILE_NAME } = require('./constant') +const { INDEX_FILE_NAME, DEVUI_DIR } = require('./constant') const { resolve } = require('path') const logger = require('./logger') const fs = require('fs-extra') @@ -57,10 +57,7 @@ exports.parseExportByFileInfo = (fileInfo, ignoreParseError) => { while (searchContent.search(partRe) !== -1) { const reStartIndex = indexContent.search(partRe) - const partStartIndex = indexContent.indexOf('{', reStartIndex) + 1 - const partEndIndex = indexContent.indexOf('}', partStartIndex) - 1 - - const partContent = indexContent.slice(partStartIndex, partEndIndex) + const partContent = this.extractStr(indexContent, '{', '}', reStartIndex) partContent .replace(/(\s|\r|\n|\t)/g, '') @@ -76,3 +73,28 @@ exports.parseExportByFileInfo = (fileInfo, ignoreParseError) => { return exportModule } + +exports.parseComponentInfo = (name) => { + const componentInfo = {} + const indexContent = fs.readFileSync(resolve(DEVUI_DIR, name, INDEX_FILE_NAME), { encoding: 'utf-8' }) + + const defaultRe = /export default/ + + if (!defaultRe.test(indexContent)) { + logger.warning(`${fileInfo.path} must have "export default" and component info.`) + } else { + const reStartIndex = indexContent.indexOf('export default {') + componentInfo.title = this.extractStr(indexContent, 'title:', ',', reStartIndex).replace(/['"]/g, '') + componentInfo.category = this.extractStr(indexContent, 'category:', ',', reStartIndex).replace(/['"]/g, '') + } + + componentInfo.name = this.bigCamelCase(name) + + return componentInfo +} + +exports.extractStr = (content = '', startKeywords = '', endKeywords = '', startIndex = 0) => { + const keywordsStartIndex = content.indexOf(startKeywords, startIndex) + startKeywords.length + const keywordsEndIndex = content.indexOf(endKeywords, keywordsStartIndex) + return content.slice(keywordsStartIndex, keywordsEndIndex).trim() +} diff --git a/devui-cli/templates/component.js b/devui-cli/templates/component.js index eaa672a1..871b09fb 100644 --- a/devui-cli/templates/component.js +++ b/devui-cli/templates/component.js @@ -101,7 +101,7 @@ import type { App } from 'vue'\ ${importStr} ${ hasComponent - ? `\n${bigCamelCase(componentName)}.install = function(app: App) { + ? `\n${bigCamelCase(componentName)}.install = function(app: App): void { app.component(${bigCamelCase(componentName)}.name, ${bigCamelCase(componentName)}) }\n` : '' @@ -117,8 +117,8 @@ export { ${[ export default { title: '${bigCamelCase(componentName)} ${title}', category: '${category}', - install(app: App): void {\ -${installStr} + install(app: App): void { + ${installStr} } } ` diff --git a/devui-cli/templates/vitepress-sidebar.js b/devui-cli/templates/vitepress-sidebar.js new file mode 100644 index 00000000..4bedec17 --- /dev/null +++ b/devui-cli/templates/vitepress-sidebar.js @@ -0,0 +1,35 @@ +const { kebabCase } = require('lodash') +const { SITES_COMPONENTS_DIR_NAME, VITEPRESS_SIDEBAR_CATEGORY } = require('../shared/constant') +const logger = require('../shared/logger') + +function buildComponentOptions(text, name) { + return { text, link: `/${SITES_COMPONENTS_DIR_NAME}/${kebabCase(name)}/` } +} + +function buildCategoryOptions(text, children = []) { + return { text, children } +} + +exports.createVitepressSidebarTemplate = (componentsInfo) => { + const rootNav = { text: '快速开始', link: '/' } + const categoryMap = VITEPRESS_SIDEBAR_CATEGORY.reduce((map, cate) => map.set(cate, []), new Map()) + + ;(componentsInfo || []).forEach((info) => { + if (categoryMap.has(info.category)) { + categoryMap.get(info.category).push(buildComponentOptions(info.title, info.name)) + } else { + logger.warning(`组件 ${info.name} 的分类 ${info.category} 不存在!`) + } + }) + + const sidebar = [].concat( + rootNav, + Array.from(categoryMap).map(([k, v]) => buildCategoryOptions(k, v)) + ) + + return `\ +export default { + '/': ${JSON.stringify(sidebar, null, 2).replace(/\n/g, '\n\t')} +} +` +} -- Gitee From faa273843dea8ca5c9578b0d9b4fcb4b81eb1135 Mon Sep 17 00:00:00 2001 From: leihaohao Date: Mon, 16 Aug 2021 00:25:28 +0800 Subject: [PATCH 2/2] =?UTF-8?q?build:=20=E7=BB=84=E4=BB=B6=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E5=88=9B=E5=BB=BA=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建模板增加 status 属性 - 创建模板时不再自动移除"d"前缀 --- devui-cli/commands/create.js | 2 -- devui-cli/shared/utils.js | 4 ++++ devui-cli/templates/component.js | 1 + devui-cli/templates/vitepress-sidebar.js | 6 +++--- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/devui-cli/commands/create.js b/devui-cli/commands/create.js index 753f4f78..5d44628f 100644 --- a/devui-cli/commands/create.js +++ b/devui-cli/commands/create.js @@ -88,8 +88,6 @@ exports.create = async (cwd) => { async function createComponent(params = {}) { let { name, hasComponent, hasDirective, hasService } = params - name = name.replace(new RegExp(`^${DEVUI_NAMESPACE}`, 'i'), '') - const componentName = kebabCase(name) const styleName = kebabCase(name) const typesName = kebabCase(name) + '-types' diff --git a/devui-cli/shared/utils.js b/devui-cli/shared/utils.js index 6dbb6ed3..cf8dc240 100644 --- a/devui-cli/shared/utils.js +++ b/devui-cli/shared/utils.js @@ -86,6 +86,7 @@ exports.parseComponentInfo = (name) => { const reStartIndex = indexContent.indexOf('export default {') componentInfo.title = this.extractStr(indexContent, 'title:', ',', reStartIndex).replace(/['"]/g, '') componentInfo.category = this.extractStr(indexContent, 'category:', ',', reStartIndex).replace(/['"]/g, '') + componentInfo.status = this.extractStr(indexContent, 'status:', ',', reStartIndex).replace(/['"]/g, '') } componentInfo.name = this.bigCamelCase(name) @@ -96,5 +97,8 @@ exports.parseComponentInfo = (name) => { exports.extractStr = (content = '', startKeywords = '', endKeywords = '', startIndex = 0) => { const keywordsStartIndex = content.indexOf(startKeywords, startIndex) + startKeywords.length const keywordsEndIndex = content.indexOf(endKeywords, keywordsStartIndex) + + if ([keywordsStartIndex - startIndex, keywordsEndIndex].some((index) => index < 0)) return '' + return content.slice(keywordsStartIndex, keywordsEndIndex).trim() } diff --git a/devui-cli/templates/component.js b/devui-cli/templates/component.js index 871b09fb..77c07c66 100644 --- a/devui-cli/templates/component.js +++ b/devui-cli/templates/component.js @@ -117,6 +117,7 @@ export { ${[ export default { title: '${bigCamelCase(componentName)} ${title}', category: '${category}', + status: undefined, \/\/ TODO: 组件若开发完成则填入"已完成",并删除该注释 install(app: App): void { ${installStr} } diff --git a/devui-cli/templates/vitepress-sidebar.js b/devui-cli/templates/vitepress-sidebar.js index 4bedec17..81e1dbe6 100644 --- a/devui-cli/templates/vitepress-sidebar.js +++ b/devui-cli/templates/vitepress-sidebar.js @@ -2,8 +2,8 @@ const { kebabCase } = require('lodash') const { SITES_COMPONENTS_DIR_NAME, VITEPRESS_SIDEBAR_CATEGORY } = require('../shared/constant') const logger = require('../shared/logger') -function buildComponentOptions(text, name) { - return { text, link: `/${SITES_COMPONENTS_DIR_NAME}/${kebabCase(name)}/` } +function buildComponentOptions(text, name, status) { + return { text, link: `/${SITES_COMPONENTS_DIR_NAME}/${kebabCase(name)}/`, status } } function buildCategoryOptions(text, children = []) { @@ -16,7 +16,7 @@ exports.createVitepressSidebarTemplate = (componentsInfo) => { ;(componentsInfo || []).forEach((info) => { if (categoryMap.has(info.category)) { - categoryMap.get(info.category).push(buildComponentOptions(info.title, info.name)) + categoryMap.get(info.category).push(buildComponentOptions(info.title, info.name, info.status)) } else { logger.warning(`组件 ${info.name} 的分类 ${info.category} 不存在!`) } -- Gitee