build-docs.js 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. const fs = require('fs/promises')
  2. const path = require('path')
  3. const { marked } = require('marked')
  4. const fm = require('front-matter')
  5. const { highlight } = require('highlight.js')
  6. marked.use({
  7. highlight: (code, lang) => {
  8. if (lang) {
  9. return highlight(code, { language: lang }).value
  10. }
  11. return code
  12. }
  13. })
  14. function tocHTML (toc) {
  15. let html = '<ul>\n'
  16. for (const li of toc) {
  17. html += '<li>\n'
  18. html += `<div>\n<a href="#${li.slug}">${li.text}</a>\n</div>\n`
  19. if (li.children && li.children.length > 0) {
  20. html += tocHTML(li.children)
  21. }
  22. html += '</li>\n'
  23. }
  24. html += '</ul>\n'
  25. return html
  26. }
  27. function markdownTOC (markdown) {
  28. const tokens = marked.lexer(markdown)
  29. const slugger = new marked.Slugger()
  30. const toc = []
  31. let currentHeading
  32. let ignoreFirst = true
  33. for (const token of tokens) {
  34. if (token.type === 'heading') {
  35. if (token.depth === 1) {
  36. if (ignoreFirst) {
  37. ignoreFirst = false
  38. continue
  39. }
  40. currentHeading = {
  41. text: token.text,
  42. slug: slugger.slug(token.text),
  43. children: []
  44. }
  45. toc.push(currentHeading)
  46. } else if (token.depth === 2) {
  47. if (!currentHeading) {
  48. continue
  49. }
  50. currentHeading.children.push({
  51. text: token.text,
  52. slug: slugger.slug(token.text)
  53. })
  54. }
  55. }
  56. }
  57. return {
  58. toc: tocHTML(toc),
  59. html: marked.parser(tokens)
  60. }
  61. }
  62. function createHTML (template, text) {
  63. const { attributes, body } = fm(text)
  64. const { toc, html } = markdownTOC(body)
  65. attributes.toc_html = toc
  66. attributes.content = html
  67. for (const prop in attributes) {
  68. template = template.replace(new RegExp(`%\\(${prop}\\)s`, 'ig'), attributes[prop])
  69. }
  70. return template
  71. }
  72. async function copyRecursive (src, dest) {
  73. const stats = await fs.stat(src)
  74. const isDirectory = stats.isDirectory()
  75. if (isDirectory) {
  76. await fs.mkdir(dest)
  77. const files = await fs.readdir(src)
  78. for (const file of files) {
  79. await copyRecursive(path.join(src, file), path.join(dest, file))
  80. }
  81. } else {
  82. await fs.copyFile(src, dest)
  83. }
  84. }
  85. async function createDocs () {
  86. const docs = path.resolve(__dirname, '..', 'docs')
  87. const dist = path.resolve(__dirname, '..', 'public')
  88. const branding = path.join(docs, 'branding')
  89. const src = path.join(branding, 'public')
  90. try {
  91. await fs.rm(dist, { recursive: true })
  92. } catch (ex) {
  93. if (ex.code !== 'ENOENT') {
  94. throw ex
  95. }
  96. }
  97. await copyRecursive(src, dist)
  98. const highlightjsStyles = path.resolve(__dirname, '..', 'node_modules', 'highlight.js', 'styles')
  99. await fs.copyFile(path.join(highlightjsStyles, 'default.css'), path.join(dist, 'media', 'css', 'highlight.css'))
  100. const template = await fs.readFile(path.join(branding, 'template.html'), { encoding: 'utf8' })
  101. const files = await fs.readdir(docs)
  102. for (const file of files) {
  103. if (!file.endsWith('.md')) {
  104. continue
  105. }
  106. const text = await fs.readFile(path.join(docs, file), { encoding: 'utf8' })
  107. const html = createHTML(template, text)
  108. await fs.writeFile(path.join(dist, file.replace(/md$/, 'html')), html)
  109. }
  110. }
  111. createDocs().catch(ex => {
  112. console.error(ex)
  113. process.exitCode = 1
  114. })