diff --git a/.gitignore b/.gitignore index caef309..9b4fb41 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,6 @@ build pnpm-lock.yaml package-lock.json example + +prompts +graphify-out diff --git a/index.js b/index.js index db884ae..7093993 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,7 @@ const CracoRemoteComponentsPlugin = require('./lib/craco-remote-components-plugin'); const CracoLibsExamplePlugin = require('./lib/craco-libs-example-plugin'); const env = require('./lib/env'); +const buildComponentDocs = require('./lib/build-component-docs'); + +module.exports = { CracoRemoteComponentsPlugin, CracoLibsExamplePlugin, env, buildComponentDocs }; -module.exports = {CracoRemoteComponentsPlugin, CracoLibsExamplePlugin, env}; diff --git a/lib/build-component-docs.js b/lib/build-component-docs.js new file mode 100644 index 0000000..01721e6 --- /dev/null +++ b/lib/build-component-docs.js @@ -0,0 +1,74 @@ +#!/usr/bin/env node + +const path = require('path'); +const fs = require('fs-extra'); +const { stringify } = require('@kne/md-doc'); +const env = require('./env'); +const { getModuleList } = require('./utils'); + +const DOC_MD_START = ''; +const DOC_MD_END = ''; + +const upsertDocMdSection = async (rootReadmePath, body) => { + const section = `${DOC_MD_START}\n\n${body}\n\n${DOC_MD_END}`; + const exists = await fs.exists(rootReadmePath); + + if (!exists) { + await fs.writeFile(rootReadmePath, `${section}\n`); + return; + } + + const content = await fs.readFile(rootReadmePath, 'utf8'); + const start = content.indexOf(DOC_MD_START); + const end = content.indexOf(DOC_MD_END); + + if (start !== -1 && end !== -1 && end > start) { + const next = `${content.slice(0, start)}${section}${content.slice(end + DOC_MD_END.length)}`; + await fs.writeFile(rootReadmePath, next); + return; + } + + const trimmed = content.replace(/\s*$/, ''); + await fs.writeFile(rootReadmePath, `${trimmed}\n\n${section}\n`); +}; + +/** + * 生成各组件 README.md,并聚合写入根 README 的 DOC_MD 段。 + * @param {object} [options] + * @param {string} [options.moduleBaseDir] + * @param {string} [options.rootReadme] + * @param {Function} [options.getModuleList] + */ +const buildComponentDocs = async (options = {}) => { + const moduleBaseDir = options.moduleBaseDir || env.moduleBaseDir; + const rootReadme = options.rootReadme || path.resolve(env.appDir, 'README.md'); + const listLoader = options.getModuleList || getModuleList; + const list = await listLoader(moduleBaseDir); + + await Promise.all(list.map(props => stringify(props))); + + const sorted = [...list].sort((a, b) => String(a.name).localeCompare(String(b.name))); + const parts = []; + for (const { baseDir } of sorted) { + const readmePath = path.join(baseDir, 'README.md'); + if (await fs.exists(readmePath)) { + parts.push((await fs.readFile(readmePath, 'utf8')).trim()); + } + } + + await upsertDocMdSection(rootReadme, parts.join('\n\n')); + + return { list: sorted, rootReadme }; +}; + +module.exports = buildComponentDocs; +module.exports.upsertDocMdSection = upsertDocMdSection; +module.exports.DOC_MD_START = DOC_MD_START; +module.exports.DOC_MD_END = DOC_MD_END; + +if (require.main === module) { + buildComponentDocs().catch(err => { + console.error(err); + process.exit(1); + }); +} diff --git a/lib/readme-webpack-plugin.js b/lib/readme-webpack-plugin.js index 4a894e1..283b7ba 100644 --- a/lib/readme-webpack-plugin.js +++ b/lib/readme-webpack-plugin.js @@ -1,11 +1,10 @@ const path = require('path'); -const fs = require('fs-extra'); -const { stringify } = require('@kne/md-doc'); const VirtualModulesPlugin = require('webpack-virtual-modules'); const virtualModules = new VirtualModulesPlugin(); const env = require('./env'); const { getModuleList } = require('./utils'); const camelCase = require('@kne/camel-case'); +const buildComponentDocs = require('./build-component-docs'); const ReadmeWebpackPlugin_MODULE_NAMES = 'ReadmeWebpackPlugin_MODULE_NAMES'; @@ -18,8 +17,11 @@ class ReadmeWebpackPlugin { virtualModules.apply(compiler); compiler.hooks.beforeRun.tapPromise('ReadmeWebpackPlugin', async (compilationParams) => { if (compilationParams.options.mode === 'production') { - const list = await (this.options.getModuleList || getModuleList)(env.moduleBaseDir); - await Promise.all(list.map((props) => stringify(props))); + await buildComponentDocs({ + moduleBaseDir: env.moduleBaseDir, + rootReadme: path.resolve(env.appDir, 'README.md'), + getModuleList: this.options.getModuleList || getModuleList + }); } }); compiler.hooks.beforeCompile.tapPromise('ReadmeWebpackPlugin', async (params) => { diff --git a/package.json b/package.json index b992130..20ba5f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@kne/modules-dev", - "version": "2.4.3", + "version": "2.4.4", "description": "用于辅助在项目内启动一个规范化组件开发的环境", "publishConfig": { "access": "public", @@ -13,22 +13,24 @@ "main": "index.js", "bin": { "modules-dev-create": "lib/init-module.js", - "modules-dev-libs-init": "lib/init-libs-example.js" + "modules-dev-libs-init": "lib/init-libs-example.js", + "modules-dev-build-docs": "lib/build-component-docs.js" }, - "files": [ - "dist", - "lib", - "template", - "template-libs-example", - "index.js" - ], "scripts": { "build:md": "create-md", "start:md": "create-md --watch", "build:lib": "microbundle --no-compress --format modern,cjs --jsxImportSource react --jsx React.createElement --jsxFragment React.Fragment", + "build:docs": "node lib/build-component-docs.js", "example": "node lib/init-libs-example.js", "test": "mocha test/**/*.test.js" }, + "files": [ + "dist", + "lib", + "template", + "template-libs-example", + "index.js" + ], "repository": { "type": "git", "url": "https://github.com/kne-union/modules-dev.git" diff --git a/test/build-component-docs.test.js b/test/build-component-docs.test.js new file mode 100644 index 0000000..10f305b --- /dev/null +++ b/test/build-component-docs.test.js @@ -0,0 +1,66 @@ +const path = require('path'); +const os = require('os'); +const fs = require('fs-extra'); +const { expect } = require('chai'); +const buildComponentDocs = require('../lib/build-component-docs'); +const { DOC_MD_START, DOC_MD_END } = buildComponentDocs; + +describe('buildComponentDocs', () => { + let tempDir; + + beforeEach(async () => { + tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'modules-dev-build-docs-')); + const componentsDir = path.join(tempDir, 'src/components'); + + for (const name of ['Beta', 'Alpha']) { + const base = path.join(componentsDir, name); + await fs.ensureDir(path.join(base, 'doc')); + await fs.writeFile(path.join(base, 'index.js'), 'module.exports = {};\n'); + await fs.writeJson(path.join(base, 'package.json'), { + name: `@test/${name.toLowerCase()}`, + description: `${name} desc`, + version: '1.0.0' + }); + await fs.writeFile(path.join(base, 'doc/summary.md'), `${name} summary`); + await fs.writeFile(path.join(base, 'doc/api.md'), `${name} api`); + await fs.writeJson(path.join(base, 'doc/example.json'), { list: [] }); + } + + await fs.writeFile( + path.join(tempDir, 'README.md'), + `# Root\n\nintro\n\n${DOC_MD_START}\n\nold\n\n${DOC_MD_END}\n` + ); + }); + + afterEach(async () => { + await fs.remove(tempDir); + }); + + it('应生成各组件 README 并替换根 README 的 DOC_MD', async () => { + const moduleBaseDir = path.join(tempDir, 'src/components'); + const rootReadme = path.join(tempDir, 'README.md'); + + const result = await buildComponentDocs({ moduleBaseDir, rootReadme }); + + expect(result.list.map(item => item.name)).to.deep.equal(['Alpha', 'Beta']); + expect(await fs.exists(path.join(moduleBaseDir, 'Alpha/README.md'))).to.equal(true); + expect(await fs.exists(path.join(moduleBaseDir, 'Beta/README.md'))).to.equal(true); + + const alphaReadme = await fs.readFile(path.join(moduleBaseDir, 'Alpha/README.md'), 'utf8'); + const betaReadme = await fs.readFile(path.join(moduleBaseDir, 'Beta/README.md'), 'utf8'); + const root = await fs.readFile(rootReadme, 'utf8'); + + expect(root).to.include('# Root'); + expect(root).to.include('intro'); + expect(root).to.include(DOC_MD_START); + expect(root).to.include(DOC_MD_END); + expect(root).to.not.include('\nold\n'); + expect(root).to.include(alphaReadme.trim()); + expect(root).to.include(betaReadme.trim()); + + const start = root.indexOf(DOC_MD_START); + const end = root.indexOf(DOC_MD_END); + const section = root.slice(start + DOC_MD_START.length, end); + expect(section.indexOf('Alpha')).to.be.lessThan(section.indexOf('Beta')); + }); +});