160 lines
4.4 KiB
JavaScript
160 lines
4.4 KiB
JavaScript
import execa from 'execa'
|
|
import { exec } from 'node:child_process'
|
|
import { gitToAuthor, authors as authorInfo } from '../../../config/authors.mjs'
|
|
import path from 'path'
|
|
import fs from 'fs'
|
|
import { yyyymmdd } from '../utils.mjs'
|
|
|
|
const divider = '____'
|
|
|
|
const parseLog = (line) => line.split(divider).map((item) => item.trim())
|
|
|
|
/*
|
|
* Helper method to get the website slug (path) from the file path
|
|
*/
|
|
const fileToSlug = (file, site, lang) =>
|
|
file.slice(-6) === `/${lang}.md` ? file.split(`/markdown/${site}/`).pop().slice(0, -6) : false
|
|
|
|
/*
|
|
* Extracts git authors and last modification date from git log.
|
|
* Strictly speaking, it's the last commit date, but you get the idea.
|
|
*/
|
|
export const getGitMetadata = async (file, site) => {
|
|
const slug = fileToSlug(file, site, 'en')
|
|
if (!slug) console.log({ file, slug })
|
|
const log = await execa.command(
|
|
`git log --pretty="format:%cs${divider}%aN${divider}%aE" ${file}`,
|
|
{ shell: true }
|
|
)
|
|
|
|
const authors = new Set()
|
|
let lastUpdated = false
|
|
for (const line of log.stdout.split('\n')) {
|
|
const [date, author, email] = parseLog(line)
|
|
if (!lastUpdated) lastUpdated = date.split('-').join('')
|
|
let key = false
|
|
if (typeof authorInfo[author] !== 'undefined') key = author
|
|
else if (typeof authorInfo[email] !== 'undefined') key = author
|
|
else {
|
|
if (typeof gitToAuthor[author] !== 'undefined') key = gitToAuthor[author]
|
|
else if (typeof gitToAuthor[email] !== 'undefined') key = gitToAuthor[email]
|
|
}
|
|
if (!key) {
|
|
if (typeof email === 'undefined') {
|
|
// This means files lack git history (they are new and haven't been committed yet)
|
|
authors.add('unknown')
|
|
} else {
|
|
// There is a git history, but the author is not known
|
|
console.log('Missing git author info for:', { email, author, slug })
|
|
// Don't throw, it's annotying
|
|
//throw `Git author email ${email} is unknown in the git-to-author table`
|
|
}
|
|
} else authors.add(key)
|
|
}
|
|
|
|
return {
|
|
lastUpdated,
|
|
authors,
|
|
slug,
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Writes data to the prebuild files
|
|
*/
|
|
const writeData = async (store) => {
|
|
// Write page to disk
|
|
const dir = path.resolve('..', store.site, 'prebuild')
|
|
fs.mkdirSync(dir, { recursive: true })
|
|
fs.writeFileSync(
|
|
path.resolve(dir, `doc-updates.mjs`),
|
|
`export const docUpdates = ${JSON.stringify(store.git.pages)}`
|
|
)
|
|
|
|
fs.writeFileSync(
|
|
path.resolve(dir, `doc-stats.mjs`),
|
|
`export const docStats = ${JSON.stringify(store.git.stats)}`
|
|
)
|
|
}
|
|
|
|
/*
|
|
* Helper method to load all MDX files from a folder
|
|
*/
|
|
const getMdxFileList = async (cwd) => {
|
|
const cmd = `find ${cwd} -type f -name "en.md"`
|
|
const find = exec(cmd, { cwd }, (error, stdout, stderr) => {
|
|
if (error) {
|
|
console.error(`exec error: ${error} - ${stderr}`)
|
|
return
|
|
}
|
|
|
|
return stdout
|
|
})
|
|
|
|
/*
|
|
* Stdout is buffered, so we need to gather all of it
|
|
*/
|
|
let stdout = ''
|
|
for await (const data of find.stdout) stdout += data
|
|
|
|
/*
|
|
* Rerturn all matches as a sorted array
|
|
*/
|
|
return stdout.split('\n').sort()
|
|
}
|
|
|
|
/*
|
|
* Main method that does what needs doing
|
|
*/
|
|
export const prebuildGitData = async (store, mock) => {
|
|
if (mock) {
|
|
store.git = mockedData(store)
|
|
return writeData(store)
|
|
}
|
|
|
|
// Setup MDX root path
|
|
const root = ['..', '..', 'markdown', store.site]
|
|
if (store.site === 'org') root.push('docs')
|
|
const mdxRoot = path.resolve(...root)
|
|
|
|
store.git = {
|
|
pages: {},
|
|
stats: {},
|
|
}
|
|
|
|
// Get list of filenames
|
|
const list = await getMdxFileList(mdxRoot)
|
|
// Loop over files
|
|
for (const file of list) {
|
|
// This list will include '' which we don't want to get the git log for as that
|
|
// will return the entire history
|
|
if (file) {
|
|
const { lastUpdated, authors, slug } = await getGitMetadata(file, store.site)
|
|
store.git.pages[slug] = { u: lastUpdated, a: [...authors] }
|
|
}
|
|
}
|
|
|
|
// How about some stats
|
|
for (const slug in store.git.pages) {
|
|
for (const author of store.git.pages[slug].a) {
|
|
if (typeof store.git.stats[author] === 'undefined') store.git.stats[author] = 0
|
|
store.git.stats[author]++
|
|
}
|
|
}
|
|
|
|
return writeData(store)
|
|
}
|
|
|
|
/*
|
|
* In development, we return this mocked data to speed things up
|
|
*/
|
|
const mockedData = (store) => {
|
|
const pages = {}
|
|
const u = yyyymmdd()
|
|
for (const slug of store.navigation.sluglut) pages[slug] = { u, a: ['mocked'] }
|
|
|
|
return {
|
|
pages,
|
|
stats: { mocked: store.navigation.sluglut.length },
|
|
}
|
|
}
|