Skip to content

Instantly share code, notes, and snippets.

@arinthros
Created September 29, 2020 20:38
Show Gist options
  • Save arinthros/526df3ef12383d26903c1c8588211cca to your computer and use it in GitHub Desktop.
Save arinthros/526df3ef12383d26903c1c8588211cca to your computer and use it in GitHub Desktop.
Generate Docusaurus Docs from JSDoc
/**
* 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment