feat(shared): Add git info to prebuild step
This commit is contained in:
parent
c047b905df
commit
5b12ea49ec
5 changed files with 156 additions and 182 deletions
|
@ -1,6 +1,48 @@
|
||||||
|
/*
|
||||||
|
* A list of (documentation) authors.
|
||||||
|
* The ID is the (future v3) FreeSewing user ID (zero for now)
|
||||||
|
* The name is what we'll use as display name
|
||||||
|
*/
|
||||||
export const authors = {
|
export const authors = {
|
||||||
joostdecock: {
|
joostdecock: { id: 0, name: 'Joost De Cock' },
|
||||||
id: 1,
|
benjamesben: { id: 0, name: 'Benjamin' },
|
||||||
name: 'Joost De Cock',
|
nikhil: { id: 0, name: 'nikhil' },
|
||||||
},
|
jackseye: { id: 0, name: 'jackseye' },
|
||||||
|
'Annie Kao': { id: 0, name: 'Annie Kao' },
|
||||||
|
Bart: { id: 0, name: 'Bart' },
|
||||||
|
'Enoch Riese': { id: 0, name: 'Enoch Riese' },
|
||||||
|
Zee: { id: 0, name: 'Zee' },
|
||||||
|
'James Bradbury': { id: 0, name: 'James Bradbury' },
|
||||||
|
jgfichte: { id: 0, name: 'jgfichte' },
|
||||||
|
Tríona: { id: 0, name: 'Tríona' },
|
||||||
|
starfetch: { id: 0, name: 'starfetch' },
|
||||||
|
bobgeorgethe3rd: { id: 0, name: 'starfetch' },
|
||||||
|
'Glenn Matthews': { id: 0, name: 'Glenn Matthews' },
|
||||||
|
'Raphael Sizemore': { id: 0, name: 'Raphael Sizemore' },
|
||||||
|
'Joe Schofield': { id: 0, name: 'Joe Schofield' },
|
||||||
|
mergerg: { id: 0, name: 'starfetch' },
|
||||||
|
woutervdub: { id: 0, name: 'Wouter van Wageningen' },
|
||||||
|
'anna-puk': { id: 0, name: 'Anna Puk' },
|
||||||
|
'Nick Dower': { id: 0, name: 'Nick Dower' },
|
||||||
|
'Sanne Kalkman': { id: 0, name: 'Sanne Kalkman' },
|
||||||
|
'Darigov Research': { id: 0, name: 'Darigov Research' },
|
||||||
|
'Jeroen Hoek': { id: 0, name: 'Jeroen Hoek' },
|
||||||
|
Natalia: { id: 0, name: 'Natalia Sayang' },
|
||||||
|
chri5b: { id: 0, name: 'chri5b' },
|
||||||
|
tangerineshark: { id: 0, name: 'tangerineshark' },
|
||||||
|
'MA-TATAS': { id: 0, name: 'MA-TATAS' },
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Maps git commiter name to author for those cases where the
|
||||||
|
* name in the authors table is different
|
||||||
|
*/
|
||||||
|
export const gitToAuthor = {
|
||||||
|
'Joost De Cock': 'joostdecock',
|
||||||
|
'Benjamin F': 'benjamesben',
|
||||||
|
SeaZeeZee: 'Zee',
|
||||||
|
'Wouter van Wageningen': 'woutervdub',
|
||||||
|
'bobgeorgethe3rd@googlemail.com': 'bobgeorgethe3rd',
|
||||||
|
'70777269+tangerineshark@users.noreply.github.com': 'tangerineshark',
|
||||||
|
'thijs.assies@gmail.com': 'MA-TATAS',
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import { components as baseComponents } from 'shared/components/mdx/index.mjs'
|
import { components as baseComponents } from 'shared/components/mdx/index.mjs'
|
||||||
// List of authors
|
// List of authors
|
||||||
import { authors as allAuthors } from 'config/authors.mjs'
|
import { authors as allAuthors } from 'config/authors.mjs'
|
||||||
|
import { docUpdates } from 'site/prebuild/doc-updates.mjs'
|
||||||
// FreeSewing config
|
// FreeSewing config
|
||||||
import { freeSewingConfig } from 'shared/config/freesewing.config.mjs'
|
import { freeSewingConfig } from 'shared/config/freesewing.config.mjs'
|
||||||
// Components
|
// Components
|
||||||
|
@ -55,7 +56,7 @@ const Ul = ({ children }) => (
|
||||||
)
|
)
|
||||||
|
|
||||||
const MetaData = ({ authors = [], maintainers = [], updated = '20220825', locale, slug, t }) => (
|
const MetaData = ({ authors = [], maintainers = [], updated = '20220825', locale, slug, t }) => (
|
||||||
<div className="py-4 px-4 rounded-lg bg-secondary bg-opacity-5 shadow mb-4">
|
<div className="py-4 px-4 rounded-lg bg-secondary bg-opacity-20 shadow mb-4">
|
||||||
{locale === 'en' ? (
|
{locale === 'en' ? (
|
||||||
<div className="flex flex-row items-center gap-4 justify-between text-base-content mb-2">
|
<div className="flex flex-row items-center gap-4 justify-between text-base-content mb-2">
|
||||||
<span className="font-medium text-lg">{t('helpImproveDocs')}</span>
|
<span className="font-medium text-lg">{t('helpImproveDocs')}</span>
|
||||||
|
@ -84,7 +85,7 @@ const MetaData = ({ authors = [], maintainers = [], updated = '20220825', locale
|
||||||
{authors.length > 0 ? (
|
{authors.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<li className="list-none font-medium opacity-70 italic">{t('authors')}:</li>
|
<li className="list-none font-medium opacity-70 italic">{t('authors')}:</li>
|
||||||
<PersonList list={authors} />
|
<PersonList list={authors} slug={slug} />
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
@ -109,11 +110,16 @@ export const MdxWrapper = ({ MDX, frontmatter = {}, components = {} }) => {
|
||||||
const allComponents = { ...baseComponents, ...components }
|
const allComponents = { ...baseComponents, ...components }
|
||||||
const { locale, slug } = useContext(NavigationContext)
|
const { locale, slug } = useContext(NavigationContext)
|
||||||
|
|
||||||
const { authors = [], maintainers = [] } = frontmatter || {}
|
const updates = docUpdates[slug] || {}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="text-primary mdx max-w-prose text-base-content max-w-prose text-base">
|
<div className="text-primary mdx max-w-prose text-base-content max-w-prose text-base">
|
||||||
<MetaData {...frontmatter} {...{ locale, slug, t }} />
|
<MetaData
|
||||||
|
maintainers={frontmatter?.maintainers || []}
|
||||||
|
authors={updates?.authors || []}
|
||||||
|
lastUpdated={updates?.lastUpdates}
|
||||||
|
{...{ locale, slug, t }}
|
||||||
|
/>
|
||||||
<MDX components={allComponents} />
|
<MDX components={allComponents} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
94
sites/shared/prebuild/git.mjs
Normal file
94
sites/shared/prebuild/git.mjs
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import execa from 'execa'
|
||||||
|
import { gitToAuthor, authors as authorInfo } from '../../../config/authors.mjs'
|
||||||
|
import path from 'path'
|
||||||
|
import fs from 'fs'
|
||||||
|
import rdir from 'recursive-readdir'
|
||||||
|
import { getMdxFileList, fileToSlug } from './docs.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) throw `Git author email ${email} is unknown in the git-to-author table`
|
||||||
|
else authors.add(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
lastUpdated,
|
||||||
|
authors,
|
||||||
|
slug,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Main method that does what needs doing
|
||||||
|
*/
|
||||||
|
export const prebuildGitData = async (site) => {
|
||||||
|
// Say hi
|
||||||
|
console.log()
|
||||||
|
console.log(`Prebuilding git author data for freesewing.${site}`)
|
||||||
|
|
||||||
|
// Setup MDX root path
|
||||||
|
const root = ['..', '..', 'markdown', site]
|
||||||
|
if (site === 'org') root.push('docs')
|
||||||
|
const mdxRoot = path.resolve(...root)
|
||||||
|
|
||||||
|
const pages = {}
|
||||||
|
|
||||||
|
// 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, site)
|
||||||
|
pages[slug] = { lastUpdated, authors: [...authors] }
|
||||||
|
}
|
||||||
|
// Write page to disk
|
||||||
|
const dir = path.resolve('..', site, 'prebuild')
|
||||||
|
fs.mkdirSync(dir, { recursive: true })
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.resolve(dir, `doc-updates.mjs`),
|
||||||
|
`export const docUpdates = ${JSON.stringify(pages)}`
|
||||||
|
)
|
||||||
|
|
||||||
|
// How about some stats
|
||||||
|
const stats = {}
|
||||||
|
for (const slug in pages) {
|
||||||
|
for (const author of pages[slug].authors) {
|
||||||
|
if (typeof stats[author] === 'undefined') stats[author] = 0
|
||||||
|
stats[author]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.resolve(dir, `doc-stats.mjs`),
|
||||||
|
`export const docStats = ${JSON.stringify(stats, null, 2)}`
|
||||||
|
)
|
||||||
|
|
||||||
|
return pages
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import { prebuildDocs } from './docs.mjs'
|
import { prebuildDocs } from './docs.mjs'
|
||||||
//import { prebuildMdx } from './mdx.mjs'
|
|
||||||
import { prebuildNavigation } from './navigation.mjs'
|
import { prebuildNavigation } from './navigation.mjs'
|
||||||
|
import { prebuildGitData } from './git.mjs'
|
||||||
import { prebuildContributors } from './contributors.mjs'
|
import { prebuildContributors } from './contributors.mjs'
|
||||||
import { prebuildPatrons } from './patrons.mjs'
|
import { prebuildPatrons } from './patrons.mjs'
|
||||||
import { prebuildI18n } from './i18n.mjs'
|
import { prebuildI18n } from './i18n.mjs'
|
||||||
|
@ -10,13 +10,12 @@ import { generateOgImage } from './og/index.mjs'
|
||||||
|
|
||||||
const run = async () => {
|
const run = async () => {
|
||||||
const SITE = process.env.SITE || 'lab'
|
const SITE = process.env.SITE || 'lab'
|
||||||
if (SITE === 'org') {
|
let docPages
|
||||||
prebuildDesigns()
|
if (['org', 'dev'].includes(SITE)) {
|
||||||
const docPages = await prebuildDocs(SITE)
|
await prebuildGitData(SITE)
|
||||||
const posts = {}
|
|
||||||
prebuildNavigation(docPages, posts, SITE)
|
|
||||||
} else if (SITE === 'dev') {
|
|
||||||
const docPages = await prebuildDocs(SITE)
|
const docPages = await prebuildDocs(SITE)
|
||||||
|
prebuildNavigation(docPages, false, SITE)
|
||||||
|
if (SITE === 'org') prebuildDesigns()
|
||||||
if (process.env.GENERATE_OG_IMAGES) {
|
if (process.env.GENERATE_OG_IMAGES) {
|
||||||
// Create og image for the home page
|
// Create og image for the home page
|
||||||
await generateOgImage({
|
await generateOgImage({
|
||||||
|
@ -35,7 +34,6 @@ const run = async () => {
|
||||||
lead: '404',
|
lead: '404',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
prebuildNavigation(docPages, false, SITE)
|
|
||||||
} else await prebuildLab()
|
} else await prebuildLab()
|
||||||
|
|
||||||
await prebuildI18n(SITE)
|
await prebuildI18n(SITE)
|
||||||
|
|
|
@ -1,166 +0,0 @@
|
||||||
import path from 'path'
|
|
||||||
import fs from 'fs'
|
|
||||||
import rdir from 'recursive-readdir'
|
|
||||||
import { unified } from 'unified'
|
|
||||||
import remarkParser from 'remark-parse'
|
|
||||||
import remarkCompiler from 'remark-stringify'
|
|
||||||
import remarkFrontmatter from 'remark-frontmatter'
|
|
||||||
import remarkFrontmatterExtractor from 'remark-extract-frontmatter'
|
|
||||||
import { readSync } from 'to-vfile'
|
|
||||||
import yaml from 'js-yaml'
|
|
||||||
import { mdIntro } from './md-intro.mjs'
|
|
||||||
import { generateOgImage } from './og/index.mjs'
|
|
||||||
|
|
||||||
/*
|
|
||||||
* There's an issue in crowdin where it changes the frontmatter marker:
|
|
||||||
* ---
|
|
||||||
* into this:
|
|
||||||
* - - -
|
|
||||||
* which breaks stuff. So this method takes the input and replaces all
|
|
||||||
* - - - with ---
|
|
||||||
*/
|
|
||||||
export const fixCrowdinBugs = (md) => {
|
|
||||||
md.value = md.value.split('- - -\n').join('---\n')
|
|
||||||
return md
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Helper method to get a list of MDX files in a folder.
|
|
||||||
* Will traverse recursively to get all files from a given root folder.
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
*
|
|
||||||
* - folder: the root folder to look in
|
|
||||||
* - lang: the language files to looks for
|
|
||||||
*
|
|
||||||
* Exported because it's also used by the Algolia index script
|
|
||||||
*/
|
|
||||||
export const getMdxFileList = async (folder, lang) => {
|
|
||||||
let allFiles
|
|
||||||
try {
|
|
||||||
allFiles = await rdir(folder)
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter out all that's not a language-specific markdown file
|
|
||||||
// and avoid including the 'ui' files
|
|
||||||
const files = []
|
|
||||||
for (const file of allFiles) {
|
|
||||||
if (
|
|
||||||
file.slice(-5) === `${lang}.md` &&
|
|
||||||
file.indexOf('/ui/') === -1 &&
|
|
||||||
file.indexOf('/uimd/') === -1
|
|
||||||
)
|
|
||||||
files.push(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
return files.sort()
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Helper method to get the website slug (path) from the file path
|
|
||||||
*/
|
|
||||||
export const fileToSlug = (file, site, lang) =>
|
|
||||||
file.slice(-6) === `/${lang}.md` ? file.split(`/markdown/${site}/`).pop().slice(0, -6) : false
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Helper method to get the title and meta data from an MDX file
|
|
||||||
*
|
|
||||||
* Parameters:
|
|
||||||
*
|
|
||||||
* - file: the full path to the file
|
|
||||||
*/
|
|
||||||
const mdxMetaInfo = async (file) => {
|
|
||||||
let result
|
|
||||||
try {
|
|
||||||
result = await unified()
|
|
||||||
.use(remarkParser)
|
|
||||||
.use(remarkCompiler)
|
|
||||||
.use(remarkFrontmatter)
|
|
||||||
.use(remarkFrontmatterExtractor, { yaml: yaml.load })
|
|
||||||
.process(fixCrowdinBugs(readSync(file, { encoding: 'utf-8' })))
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Main method that does what needs doing
|
|
||||||
*/
|
|
||||||
export const prebuildMdx = async (site) => {
|
|
||||||
// Say hi
|
|
||||||
console.log()
|
|
||||||
console.log(`Prebuilding MDX for freesewing.${site}`)
|
|
||||||
|
|
||||||
// Setup MDX root path
|
|
||||||
const mdxRoot = path.resolve('..', '..', 'markdown', site)
|
|
||||||
|
|
||||||
// Inform if we're also generating OG images
|
|
||||||
if (process.env.GENERATE_OG_IMAGES) {
|
|
||||||
console.log('⚙ Also generating Open Graph images (this takes a while)')
|
|
||||||
console.log(' Unset the GENERATE_OG_IMAGES env var to skip this step)')
|
|
||||||
} else {
|
|
||||||
console.log('⏩ Not generating Open Graph images as it takes a while')
|
|
||||||
console.log(' Set GENERATE_OG_IMAGES env var to include this step')
|
|
||||||
}
|
|
||||||
// Loop over locales
|
|
||||||
const pages = {}
|
|
||||||
const locales = site === 'dev' ? ['en'] : ['en', 'fr', 'es', 'nl', 'de']
|
|
||||||
for (const lang of locales) {
|
|
||||||
console.log(` - Language: ${lang}`)
|
|
||||||
|
|
||||||
// Get list of filenames
|
|
||||||
const list = await getMdxFileList(mdxRoot, lang)
|
|
||||||
|
|
||||||
// Parse them for title and intro
|
|
||||||
pages[lang] = {}
|
|
||||||
for (const file of list) {
|
|
||||||
const slug = fileToSlug(file, site, lang)
|
|
||||||
if (slug) {
|
|
||||||
const meta = await mdxMetaInfo(file)
|
|
||||||
if (meta.data?.title) {
|
|
||||||
pages[lang][slug] = { t: meta.data.title }
|
|
||||||
if (meta.data.order) pages[lang][slug].o = `${meta.data.order}${meta.data.title}`
|
|
||||||
} else {
|
|
||||||
if (pages.en[slug]) {
|
|
||||||
console.log(`⚠️l Falling back to EN metadata for ${slug}`)
|
|
||||||
pages[lang][slug] = pages.en[slug]
|
|
||||||
} else {
|
|
||||||
console.log(`❌ [${lang}] Failed to extract meta info from: ${slug}`)
|
|
||||||
if (meta.messages.length > 0) console.log(meta.messages)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (process.env.GENERATE_OG_IMAGES) {
|
|
||||||
// Create og image
|
|
||||||
const intro = await mdIntro(lang, site, slug)
|
|
||||||
await generateOgImage({ lang, site, slug, title: meta.data.title, intro })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.resolve('..', site, 'prebuild', `mdx.${lang}.js`),
|
|
||||||
`export default ${JSON.stringify(pages[lang])}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create wrapper
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.resolve('..', site, 'prebuild', `mdx.js`),
|
|
||||||
locales.map((l) => `import ${l} from './mdx.${l}.js'`).join('\n') +
|
|
||||||
'\n\n' +
|
|
||||||
`export default { ${locales.join()} }`
|
|
||||||
)
|
|
||||||
|
|
||||||
// Write list of all MDX paths (in one language)
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.resolve('..', site, 'prebuild', `mdx.paths.mjs`),
|
|
||||||
`export const mdxPaths = ${JSON.stringify(Object.keys(pages.en))}`
|
|
||||||
)
|
|
||||||
|
|
||||||
return pages
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue