Skip to content

Instantly share code, notes, and snippets.

@slorber
Forked from arinthros/generate-docs-from-js
Last active June 14, 2024 20:40

Revisions

  1. slorber renamed this gist Apr 6, 2021. 1 changed file with 0 additions and 0 deletions.
    File renamed without changes.
  2. @arinthros arinthros created this gist Sep 29, 2020.
    231 changes: 231 additions & 0 deletions generate-docs-from-js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,231 @@
    /**
    * 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)