forked from github/docs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
reconcile-category-dirs-with-ids.js
executable file
·157 lines (129 loc) · 5.31 KB
/
reconcile-category-dirs-with-ids.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#!/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 { XmlEntities } from 'html-entities'
import loadSiteData from '../lib/site-data.js'
import renderContent from '../lib/render-content/index.js'
const slugger = new GithubSlugger()
const entities = new XmlEntities()
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()
const siteData = await getEnglishSiteData()
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, { site: siteData }, { textOnly: true })
slugger.reset()
const expectedSlug = slugger.slug(entities.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)
}
async function getEnglishSiteData() {
const siteData = await loadSiteData()
return siteData.en.site
}