import execa from 'execa' import { gitToAuthor, authors as authorInfo } from '../../../config/authors.mjs' import path from 'path' import fs from 'fs' import { getMdxFileList, fileToSlug } from './docs.mjs' import { yyyymmdd } from '../utils.mjs' const divider = '____' const parseLog = (line) => line.split(divider).map((item) => item.trim()) /* * 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') 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 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({ email, author, slug }) 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)}` ) } /* * 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) { 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.en) pages[slug] = { u, a: ['mocked'] } return { pages, stats: { mocked: store.navigation.sluglut.en.length }, } }