#!/usr/bin/env node // [start-readme] // // An automated test checks for discrepancies between category directory names and // slugified category titles as IDs. // // If the test fails, a human needs to run this script to update the directory // names and add appropriate redirects. // // **This script is not currently supported on Windows.** // // [end-readme] import fs from 'fs' import path from 'path' import frontmatter from '../lib/read-frontmatter.js' import walk from 'walk-sync' import slash from 'slash' import GithubSlugger from 'github-slugger' import { decode } from 'html-entities' import renderContent from '../lib/render-content/index.js' const slugger = new GithubSlugger() const contentDir = path.join(process.cwd(), 'content') // TODO fix path separators in the redirect if (process.platform.startsWith('win')) { console.log('This script cannot be run on Windows at this time! Exiting...') process.exit() } // Execute! main() async function main() { const englishCategoryIndices = getEnglishCategoryIndices() for (const categoryIndex of englishCategoryIndices) { const contents = fs.readFileSync(categoryIndex, 'utf8') const { data, content } = frontmatter(contents) // Get the parent directory name const categoryDirPath = path.dirname(categoryIndex) const categoryDirName = path.basename(categoryDirPath) const title = await renderContent(data.title, {}, { textOnly: true }) slugger.reset() const expectedSlug = slugger.slug(decode(title)) // If the directory name already matches the expected slug, bail out now if (categoryDirName === expectedSlug) continue // Figure out the new path for the category const categoryDirParentDir = path.dirname(categoryDirPath) const newPath = path.join(categoryDirParentDir, expectedSlug) // Figure out redirect path const relativeOldPath = path.relative(contentDir, categoryDirPath) const redirectPath = '/' + slash(relativeOldPath) // Log it const relativeNewPath = path.relative(contentDir, newPath) console.log(`Renaming category directory: Old: "${relativeOldPath}" New: "${relativeNewPath}" Redirect: "${redirectPath}" `) // Add a new redirect to the frontmatter if (!data.redirect_from) { data.redirect_from = [] } data.redirect_from.push(redirectPath) // Update the category index file on disk fs.writeFileSync(categoryIndex, frontmatter.stringify(content, data, { lineWidth: 10000 })) // Update all of the category's articles on disk as well to add a new redirect to their frontmatter for (const articleFileName of fs.readdirSync(categoryDirPath)) { const articlePath = path.join(categoryDirPath, articleFileName) // Figure out redirect path const articlePathMinusExtension = path.join( categoryDirPath, path.basename(articleFileName, '.md') ) const redirectArticlePath = '/' + slash(path.relative(contentDir, articlePathMinusExtension)) // Log it const relativeOldArticlePath = path.relative(contentDir, articlePath) const newArticlePath = path.join(categoryDirParentDir, expectedSlug, articleFileName) const relativeNewArticlePath = path.relative(contentDir, newArticlePath) console.log(`Adding redirect to article: Old: "${relativeOldArticlePath}" New: "${relativeNewArticlePath}" Redirect: "${redirectArticlePath}" `) const articleContents = fs.readFileSync(articlePath, 'utf8') const { data: articleData, content: articleContent } = frontmatter(articleContents) // Add a new redirect to the frontmatter if (!articleData.redirect_from) { articleData.redirect_from = [] } articleData.redirect_from.push(redirectArticlePath) // Update the article file on disk fs.writeFileSync( articlePath, frontmatter.stringify(articleContent, articleData, { lineWidth: 10000 }) ) } // Update the reference to this category in the product index file on disk // // NOTE: This approach may update the same product index multiple times per // script run but TBH I'm OK with that in a manually executed script const productIndexPath = path.join(categoryDirParentDir, 'index.md') const productIndexContents = fs.readFileSync(productIndexPath, 'utf8') const { data: productIndexData, content: productIndex } = frontmatter(productIndexContents) const revisedProductIndex = productIndex.replace( new RegExp(`(\\s+)(?:/${categoryDirName})(\\s+)`, 'g'), `$1/${expectedSlug}$2` ) fs.writeFileSync( productIndexPath, frontmatter.stringify(revisedProductIndex, productIndexData, { lineWidth: 10000 }) ) console.log(`*** Updated product index "${productIndexPath}" for ☝️\n`) // Finally, rename the directory fs.renameSync(categoryDirPath, newPath) } } function getEnglishCategoryIndices() { const walkOptions = { globs: ['*/*/**/index.md'], ignore: ['{rest,graphql,developers}/**', 'enterprise/admin/index.md', '**/articles/**'], directories: false, includeBasePath: true, } return walk(contentDir, walkOptions) }