theme/gatsby-node.js (126 lines of code) (raw):
const path = require('path')
const fs = require('fs')
const readPkgUp = require('read-pkg-up')
const getPkgRepo = require('get-pkg-repo')
const axios = require('axios')
const uniqBy = require('lodash.uniqby')
const extractExports = require(`gatsby-plugin-mdx/utils/extract-exports`)
const mdx = require(`gatsby-plugin-mdx/utils/mdx`)
const CONTRIBUTOR_CACHE = new Map()
exports.createPages = async ({graphql, actions}, themeOptions) => {
const repo = getPkgRepo(readPkgUp.sync().package)
const {data} = await graphql(`
query {
allMdx {
nodes {
fileAbsolutePath
rawBody
tableOfContents(maxDepth: 2)
parent {
... on File {
relativeDirectory
name
}
}
}
}
}
`)
if (!process.env.GITHUB_TOKEN && !process.env.NOW_GITHUB_DEPLOYMENT && !process.env.VERCEL_GITHUB_DEPLOYMENT) {
console.error(`Non-deploy build and no GITHUB_TOKEN environment variable set; skipping GitHub API calls`)
}
// Turn every MDX file into a page.
await Promise.all(
data.allMdx.nodes.map(async node => {
const pagePath = path
.join(node.parent.relativeDirectory, node.parent.name === 'index' ? '/' : node.parent.name)
.replace(/\\/g, '/') // Convert Windows backslash paths to forward slash paths: foo\\bar → foo/bar
const rootAbsolutePath = path.resolve(process.cwd(), themeOptions.repoRootPath || '.')
const fileRelativePath = path.relative(rootAbsolutePath, node.fileAbsolutePath)
const defaultBranch = themeOptions.defaultBranch || 'master'
const editUrl = getEditUrl(repo, fileRelativePath, defaultBranch)
let contributors = []
if (process.env.GITHUB_TOKEN || process.env.NOW_GITHUB_DEPLOYMENT || process.env.VERCEL_GITHUB_DEPLOYMENT) {
contributors = await fetchContributors(repo, fileRelativePath, process.env.GITHUB_TOKEN)
}
// Copied from gatsby-plugin-mdx (https://git.io/JUs3H)
// as a workaround for https://github.com/gatsbyjs/gatsby/issues/21837
const code = await mdx(node.rawBody)
const {frontmatter} = extractExports(code)
actions.createPage({
path: pagePath,
component: node.fileAbsolutePath,
context: {
editUrl,
contributors,
tableOfContents: node.tableOfContents,
// Note: gatsby-plugin-mdx should insert frontmatter
// for us here, and does on the first build,
// but when HMR kicks in the frontmatter is lost.
// The solution is to include it here explicitly.
frontmatter
}
})
})
)
}
exports.onPostBuild = async ({graphql}) => {
try {
const {data} = await graphql(`
query {
allSitePage(filter: {context: {frontmatter: {componentId: {ne: null}, status: {ne: null}}}}) {
nodes {
path
context {
frontmatter {
componentId
status
}
}
}
}
}
`)
const components = data.allSitePage.nodes.map(node => {
return {
id: node.context.frontmatter.componentId,
path: node.path,
status: node.context.frontmatter.status.toLowerCase()
}
})
fs.writeFileSync(path.resolve(process.cwd(), 'public/components.json'), JSON.stringify(components))
} catch (error) {
// This is not necessarily an error, so we just log a warning instead of failing the build.
// Some sites won't have any markdown files with `componentId` frontmatter and that's okay.
console.warn('Unable to build components.json')
}
}
function getEditUrl(repo, filePath, defaultBranch) {
return `https://github.com/${repo.user}/${repo.project}/edit/${defaultBranch}/${filePath}`
}
async function fetchContributors(repo, filePath, accessToken = '') {
const hash = `${repo.user}/${repo.project}/${filePath}`
const cached = CONTRIBUTOR_CACHE.get(hash)
if (cached) {
return cached
}
try {
const req = {
method: 'get',
baseURL: 'https://api.github.com/',
url: `/repos/${repo.user}/${repo.project}/commits?path=${filePath}&per_page=100`
}
if (accessToken && accessToken.length) {
req.headers = {
Authorization: `token ${accessToken}`
}
}
const {data} = await axios.request(req)
const commits = data
.map(commit => ({
login: commit.author && commit.author.login,
latestCommit: {
date: commit.commit.author.date,
url: commit.html_url
}
}))
.filter(contributor => Boolean(contributor.login))
const result = uniqBy(commits, 'login')
CONTRIBUTOR_CACHE.set(hash, result)
return result
} catch (error) {
console.error(`[ERROR] Unable to fetch contributors for ${filePath}. ${error.message}`)
return []
}
}