1
0
Fork 0
freesewing/sites/shared/prebuild/git.mjs
2023-07-21 09:54:05 +02:00

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, lang) => {
const cmd = `find ${cwd} -type f -name "en.md"`
const find = exec(cmd, { cwd }, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`)
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, 'en')
// 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 },
}
}