-
-
Save slorber/0bf8c8c8001505f0f99a062ac55bf442 to your computer and use it in GitHub Desktop.
Generate Docusaurus Docs from JSDoc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Requires jsdoc and jsdoc-to-markdown | |
*/ | |
/* eslint-disable no-console */ | |
const fs = require('fs') | |
const path = require('path') | |
const glob = require('glob') | |
const { execSync } = require('child_process') | |
const jsdoc2md = require('jsdoc-to-markdown') | |
const sidebars = require('../docs/sidebars') | |
// Custom package names that don't match the base pattern `utils => Utils` | |
const packageNameOverrides = [ | |
{ id: 'api', name: 'API' }, | |
{ id: 'material-ui', name: 'Material UI' }, | |
{ id: 'eslint-config', name: 'ESLint Config' }, | |
] | |
/** | |
* Runs through packages folders looking for JSDoc and generates markdown docs | |
*/ | |
function generateDocs() { | |
console.log('Generating package docs') | |
// Use glob to get all js/ts files | |
const pathPattern = path.join(__dirname, '../packages/**/src/**/*.[jt]s?(x)') | |
const filePaths = glob.sync(pathPattern, { | |
ignore: [ | |
'**/node_modules/**', | |
'**/cypress/**', | |
'**/__tests__/**', | |
'**/*.test.js', | |
'**/*_spec.js', | |
], | |
}) | |
// Get the sidebar object | |
const sidebarPackages = [...sidebars.primarySidebar.Packages] | |
let processingPackageName | |
// grab all js files | |
filePaths.forEach((filePath) => { | |
// Generate markdown from JSDoc comments | |
const markdown = jsdoc2md.renderSync({ | |
files: filePath, | |
configure: path.join(__dirname, '../jsdoc.conf.json'), | |
plugin: path.join(__dirname, '../docs/.template'), | |
'heading-depth': 1, | |
}) | |
// if there's markdown, do stuff | |
if (markdown && markdown.length > 0) { | |
// get the package ID from the file path | |
const packageId = filePath.match(/\/packages\/([\s\S]*?)\/src\//i)[1] | |
// check against the title overrides array | |
let packageName | |
// if override, use the title, else title = capitalize packageId | |
if (packageNameOverrides.find((item) => item.id === packageId)) { | |
packageName = packageNameOverrides.find((item) => item.id === packageId) | |
.name | |
} else { | |
packageName = packageId.charAt(0).toUpperCase() + packageId.slice(1) | |
} | |
if (packageName !== processingPackageName) { | |
processingPackageName = packageName | |
console.log(` Processing the ${packageName} package`) | |
} | |
// get the sub-folder structure relative to /src/ | |
let subPath = path.dirname(filePath).match(/\/src\/([\s\S]*?)$/i) | |
? `${path.dirname(filePath).match(/\/src\/([\s\S]*?)$/i)[1]}` | |
: '' | |
// Get each part of the path, filtering out empty path items | |
const subPathArray = subPath.split('/') | |
// get file name sans extension | |
let fileName = path.basename(filePath, '.js') | |
// if the file name is index, but is not the package index | |
if (subPathArray.length > 0 && fileName === 'index') { | |
// change file name to the parent folder name | |
fileName = subPathArray[subPathArray.length - 1] | |
// remove the parent folder from the path | |
subPathArray.pop() | |
subPath = subPathArray.join('/') | |
} | |
const writeDir = path.join( | |
__dirname, | |
`../docs/docs/packages/${packageId}/api-reference${ | |
subPath ? `/${subPath}` : '' | |
}`, | |
) | |
// check if the directory exists | |
if (!fs.existsSync(writeDir)) { | |
// create the directory | |
fs.mkdirSync(writeDir, { recursive: true }) | |
} | |
// write the markdown file | |
fs.writeFileSync(`${writeDir}/${fileName}.md`, markdown) | |
// update sidebar object | |
// define the relative path for the sidebar | |
const sidebarPath = `packages/${packageId}/api-reference${ | |
subPath ? `/${subPath}` : '' | |
}/${fileName}` | |
/** | |
* Searches the parent array for the key and adds it if it's not found | |
* @param {Array} parent The parent array to search | |
* @param {String} key The key to search for and add | |
*/ | |
const addOnePathLevel = (parent, key) => { | |
if ( | |
!parent.find((item) => { | |
return Object.keys(item)[0] === key | |
}) | |
) { | |
// add the first element to the parent | |
parent.push({ [`${key}`]: [] }) | |
} | |
} | |
// add package path | |
addOnePathLevel(sidebarPackages, packageName) | |
// add API Reference path | |
addOnePathLevel( | |
sidebarPackages.find((item) => { | |
return Object.keys(item)[0] === packageName | |
})[packageName], | |
'API Reference', | |
) | |
// Get the API Reference item | |
let packageApiReferenceItem = sidebarPackages | |
.find((item) => { | |
return Object.keys(item)[0] === packageName | |
}) | |
[packageName].find((item) => { | |
return Object.keys(item)[0] === 'API Reference' | |
})['API Reference'] | |
// If there's a subpath, recursively set the nested structure | |
if (subPathArray.length > 0) { | |
// make array of names | |
const pathNameArray = subPathArray.map((item) => { | |
return item | |
.split('-') | |
.map((word) => { | |
return word.charAt(0).toUpperCase() + word.slice(1) | |
}) | |
.join(' ') | |
}) | |
// TODO: Make nested items (index > 0) recursive | |
const addPathLevels = (parent, index) => { | |
// If the first element in the path array does not exist | |
if ( | |
!parent.find((item) => { | |
return typeof item === 'object' && item[pathNameArray[index]] | |
}) | |
) { | |
// add the first element to the parent | |
parent.push({ | |
[pathNameArray[index]]: [], | |
}) | |
} | |
// Find the index of the first path element | |
const subItemIndex = parent.findIndex((item) => { | |
return typeof item === 'object' && item[pathNameArray[index]] | |
}) | |
// add the sidebarPath to the last path element | |
if (pathNameArray.length - 1 === index) { | |
// eslint-disable-next-line no-param-reassign | |
parent[subItemIndex][pathNameArray[index]] = [ | |
// filter duplicates | |
...new Set([ | |
...parent[subItemIndex][pathNameArray[index]], | |
sidebarPath, | |
]), | |
] | |
} | |
if (pathNameArray.length - 2 >= index) { | |
addPathLevels(parent[subItemIndex][pathNameArray[index]], index + 1) | |
} | |
} | |
// recursively add path levels | |
addPathLevels(packageApiReferenceItem, 0) | |
} else { | |
// Push the sidebarPath on to the 'API Reference' array | |
packageApiReferenceItem = [ | |
// Preserve existing items, but filter duplicates | |
...new Set([...packageApiReferenceItem, sidebarPath]), | |
] | |
} | |
} | |
}) | |
// Let the user know what step we're on | |
console.log('\u001B[32m', '✔️ Package docs generated', '\u001B[0m') | |
// set packages to the updated sidebarPackages | |
sidebars.primarySidebar.Packages = sidebarPackages | |
// Let the user know what step we're on | |
console.log('Updating sidebars.js') | |
// write sidebar file | |
fs.writeFileSync( | |
path.join(__dirname, '../docs/sidebars.js'), | |
`module.exports = ${JSON.stringify(sidebars, null, ' ')}`, | |
'utf8', | |
) | |
// run prettier on the output json | |
execSync(`eslint --fix ${path.join(__dirname, '../docs/sidebars.js')}`) | |
// Let the user know what step we're on | |
console.log('\u001B[32m', '✔️ sidebars.js updated', '\u001B[0m') | |
} | |
generateDocs() | |
process.exit(0) |
🐵 I just renamed the file, thank @arinthros :D
❤️
@slorber Hi Sebastian, where's the .template
come from?
Hey there, I'm still a noob with docusaurus, how do I use this? Is there any sample project/code? 😅 :)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I needed something a little smaller than this for a single package.
This worked for me. Thanks for the initial work @slorber !