feat(content-blog): infer blog post date from git history (#6593)

This commit is contained in:
Felipe Santos 2022-02-09 13:18:32 -03:00 committed by GitHub
parent 665d164351
commit 6996ed2f2f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 127 additions and 57 deletions

View file

@ -5,14 +5,13 @@
* LICENSE file in the root directory of this source tree.
*/
import fs from 'fs-extra';
import path from 'path';
import pluginContentBlog from '../index';
import type {DocusaurusConfig, LoadContext, I18n} from '@docusaurus/types';
import {PluginOptionSchema} from '../pluginOptionSchema';
import type {BlogPost} from '../types';
import type {Joi} from '@docusaurus/utils-validation';
import {posixPath} from '@docusaurus/utils';
import {posixPath, getFileCommitDate} from '@docusaurus/utils';
import type {
PluginOptions,
EditUrlFunction,
@ -425,14 +424,15 @@ describe('loadBlog', () => {
);
const blogPosts = await getBlogPosts(siteDir);
const noDateSource = path.posix.join('@site', PluginPath, 'no date.md');
const noDateSourceBirthTime = (
await fs.stat(noDateSource.replace('@site', siteDir))
).birthtime;
const noDateSourceFile = path.posix.join(siteDir, PluginPath, 'no date.md');
// we know the file exist and we know we have git
const result = getFileCommitDate(noDateSourceFile, {age: 'oldest'});
const noDateSourceTime = result.date;
const formattedDate = Intl.DateTimeFormat('en', {
day: 'numeric',
month: 'long',
year: 'numeric',
}).format(noDateSourceBirthTime);
}).format(noDateSourceTime);
expect({
...getByTitle(blogPosts, 'no date').metadata,
@ -445,7 +445,7 @@ describe('loadBlog', () => {
title: 'no date',
description: `no date`,
authors: [],
date: noDateSourceBirthTime,
date: noDateSourceTime,
formattedDate,
frontMatter: {},
tags: [],

View file

@ -27,6 +27,7 @@ import {
Globby,
normalizeFrontMatterTags,
groupTaggedItems,
getFileCommitDate,
getContentPathList,
} from '@docusaurus/utils';
import type {LoadContext} from '@docusaurus/types';
@ -242,8 +243,17 @@ async function processBlogSourceFile(
} else if (parsedBlogFileName.date) {
return parsedBlogFileName.date;
}
// Fallback to file create time
return (await fs.stat(blogSourceAbsolute)).birthtime;
try {
const result = getFileCommitDate(blogSourceAbsolute, {
age: 'oldest',
includeAuthor: false,
});
return result.date;
} catch (e) {
logger.error(e);
return (await fs.stat(blogSourceAbsolute)).birthtime;
}
}
const date = await getDate();

View file

@ -34,7 +34,6 @@
"js-yaml": "^4.0.0",
"lodash": "^4.17.20",
"remark-admonitions": "^1.2.1",
"shelljs": "^0.8.4",
"tslib": "^2.3.1",
"utility-types": "^3.10.0",
"webpack": "^5.68.0"
@ -47,6 +46,7 @@
"commander": "^5.1.0",
"escape-string-regexp": "^4.0.0",
"picomatch": "^2.1.1",
"shelljs": "^0.8.4",
"utility-types": "^3.10.0"
},
"peerDependencies": {

View file

@ -5,14 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import shell from 'shelljs';
import logger from '@docusaurus/logger';
import path from 'path';
import {getFileCommitDate, GitNotFoundError} from '@docusaurus/utils';
type FileLastUpdateData = {timestamp?: number; author?: string};
const GIT_COMMIT_TIMESTAMP_AUTHOR_REGEX = /^(?<timestamp>\d+),(?<author>.+)$/;
let showedGitRequirementError = false;
export async function getFileLastUpdate(
@ -21,53 +18,22 @@ export async function getFileLastUpdate(
if (!filePath) {
return null;
}
function getTimestampAndAuthor(str: string): FileLastUpdateData | null {
if (!str) {
return null;
}
const temp = str.match(GIT_COMMIT_TIMESTAMP_AUTHOR_REGEX)?.groups;
return temp
? {timestamp: Number(temp.timestamp), author: temp.author}
: null;
}
// Wrap in try/catch in case the shell commands fail
// (e.g. project doesn't use Git, etc).
try {
if (!shell.which('git')) {
if (!showedGitRequirementError) {
showedGitRequirementError = true;
logger.warn('Sorry, the docs plugin last update options require Git.');
}
return null;
}
if (!shell.test('-f', filePath)) {
throw new Error(
`Retrieval of git history failed at "${filePath}" because the file does not exist.`,
);
}
const fileBasename = path.basename(filePath);
const fileDirname = path.dirname(filePath);
const result = shell.exec(
`git log --max-count=1 --format=%ct,%an -- "${fileBasename}"`,
{
cwd: fileDirname, // this is needed: https://github.com/facebook/docusaurus/pull/5048
silent: true,
},
);
if (result.code !== 0) {
throw new Error(
`Retrieval of git history failed at "${filePath}" with exit code ${result.code}: ${result.stderr}`,
);
}
return getTimestampAndAuthor(result.stdout.trim());
const result = getFileCommitDate(filePath, {
age: 'newest',
includeAuthor: true,
});
return {timestamp: result.timestamp, author: result.author};
} catch (e) {
logger.error(e);
if (e instanceof GitNotFoundError && !showedGitRequirementError) {
logger.warn('Sorry, the docs plugin last update options require Git.');
showedGitRequirementError = true;
} else {
logger.error(e);
}
return null;
}
return null;
}

View file

@ -29,6 +29,7 @@
"lodash": "^4.17.20",
"micromatch": "^4.0.4",
"resolve-pathname": "^3.0.0",
"shelljs": "^0.8.4",
"tslib": "^2.3.1",
"url-loader": "^4.1.1",
"webpack": "^5.68.0"

View file

@ -0,0 +1,92 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import path from 'path';
import shell from 'shelljs';
export class GitNotFoundError extends Error {}
export const getFileCommitDate = (
file: string,
{
age = 'oldest',
includeAuthor = false,
}: {
age?: 'oldest' | 'newest';
includeAuthor?: boolean;
},
): {
date: Date;
timestamp: number;
author?: string;
} => {
if (!shell.which('git')) {
throw new GitNotFoundError(
`Failed to retrieve git history for "${file}" because git is not installed.`,
);
}
if (!shell.test('-f', file)) {
throw new Error(
`Failed to retrieve git history for "${file}" because the file does not exist.`,
);
}
const fileBasename = path.basename(file);
const fileDirname = path.dirname(file);
let formatArg = '--format=%ct';
if (includeAuthor) {
formatArg += ',%an';
}
let extraArgs = '--max-count=1';
if (age === 'oldest') {
// --follow is necessary to follow file renames
// --diff-filter=A ensures we only get the commit which (A)dded the file
extraArgs += ' --follow --diff-filter=A';
}
const result = shell.exec(
`git log ${extraArgs} ${formatArg} -- "${fileBasename}"`,
{
// cwd is important, see: https://github.com/facebook/docusaurus/pull/5048
cwd: fileDirname,
silent: true,
},
);
if (result.code !== 0) {
throw new Error(
`Failed to retrieve the git history for file "${file}" with exit code ${result.code}: ${result.stderr}`,
);
}
let regex = /^(?<timestamp>\d+)$/;
if (includeAuthor) {
regex = /^(?<timestamp>\d+),(?<author>.+)$/;
}
const output = result.stdout.trim();
const match = output.match(regex);
if (
!match ||
!match.groups ||
!match.groups.timestamp ||
(includeAuthor && !match.groups.author)
) {
throw new Error(
`Failed to retrieve the git history for file "${file}" with unexpected output: ${output}`,
);
}
const timestamp = Number(match.groups.timestamp);
const date = new Date(timestamp * 1000);
if (includeAuthor) {
return {date, timestamp, author: match.groups.author};
}
return {date, timestamp};
};

View file

@ -37,6 +37,7 @@ export {
DEFAULT_PLUGIN_ID,
WEBPACK_URL_LOADER_LIMIT,
} from './constants';
export {getFileCommitDate, GitNotFoundError} from './gitUtils';
export {normalizeUrl, getEditUrl} from './urlUtils';
export {
type Tag,