Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 6 commits into from
Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/

import fs from 'fs-extra';
import path from 'path';
import shell from 'shelljs';
import pluginContentBlog from '../index';
import type {DocusaurusConfig, LoadContext, I18n} from '@docusaurus/types';
import {PluginOptionSchema} from '../pluginOptionSchema';
Expand Down Expand Up @@ -425,14 +425,17 @@ 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 = shell.exec(
felipecrs marked this conversation as resolved.
Show resolved Hide resolved
`git log --follow --max-count=1 --diff-filter=A --format=%ct -- "${noDateSourceFile}"`,
);
const noDateSourceTime = new Date(Number(result.stdout.trim()) * 1000);
slorber marked this conversation as resolved.
Show resolved Hide resolved
const formattedDate = Intl.DateTimeFormat('en', {
day: 'numeric',
month: 'long',
year: 'numeric',
}).format(noDateSourceBirthTime);
}).format(noDateSourceTime);

expect({
...getByTitle(blogPosts, 'no date').metadata,
Expand All @@ -445,7 +448,7 @@ describe('loadBlog', () => {
title: 'no date',
description: `no date`,
authors: [],
date: noDateSourceBirthTime,
date: noDateSourceTime,
formattedDate,
frontMatter: {},
tags: [],
Expand Down
16 changes: 14 additions & 2 deletions packages/docusaurus-plugin-content-blog/src/blogUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
Globby,
normalizeFrontMatterTags,
groupTaggedItems,
getCommitterDateForFile,
getContentPathList,
} from '@docusaurus/utils';
import type {LoadContext} from '@docusaurus/types';
Expand Down Expand Up @@ -242,8 +243,19 @@ async function processBlogSourceFile(
} else if (parsedBlogFileName.date) {
return parsedBlogFileName.date;
}
// Fallback to file create time
return (await fs.stat(blogSourceAbsolute)).birthtime;

let result;
try {
result = getCommitterDateForFile(blogSourceAbsolute, {
age: 'oldest',
includeAuthor: false,
});
} catch (e) {
logger.error(e);
felipecrs marked this conversation as resolved.
Show resolved Hide resolved
return (await fs.stat(blogSourceAbsolute)).birthtime;
}

return result.date;
felipecrs marked this conversation as resolved.
Show resolved Hide resolved
}

const date = await getDate();
Expand Down
1 change: 0 additions & 1 deletion packages/docusaurus-plugin-content-docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
56 changes: 8 additions & 48 deletions packages/docusaurus-plugin-content-docs/src/lastUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,69 +5,29 @@
* LICENSE file in the root directory of this source tree.
*/

import shell from 'shelljs';
import logger from '@docusaurus/logger';
import path from 'path';
import {getCommitterDateForFile} 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(
filePath?: string,
): Promise<FileLastUpdateData | null> {
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).
let result;
felipecrs marked this conversation as resolved.
Show resolved Hide resolved
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());
result = getCommitterDateForFile(filePath, {
age: 'newest',
includeAuthor: true,
});
} catch (e) {
logger.error(e);
return null;
}

return null;
return {timestamp: result.timestamp, author: result.author};
}
1 change: 1 addition & 0 deletions packages/docusaurus-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"remark-mdx-remove-exports": "^1.6.22",
"remark-mdx-remove-imports": "^1.6.22",
"resolve-pathname": "^3.0.0",
"shelljs": "^0.8.4",
"tslib": "^2.3.1",
"url-loader": "^4.1.1"
},
Expand Down
83 changes: 83 additions & 0 deletions packages/docusaurus-utils/src/gitUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/**
* 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 const getCommitterDateForFile = (
felipecrs marked this conversation as resolved.
Show resolved Hide resolved
file: string,
options: {
age: 'oldest' | 'newest';
includeAuthor: boolean;
} = {age: 'oldest', includeAuthor: false},
): {date: Date; timestamp: number; author?: string} => {
if (!shell.which('git')) {
throw new Error(
`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 (options.includeAuthor) {
formatArg += ',%an';
}

let extraArgs = '--max-count=1';
if (options.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 (options.includeAuthor) {
regex = /^(?<timestamp>\d+),(?<author>.+)$/;
}

const output = result.stdout.trim();
const match = output.match(regex);

if (
!match ||
!match.groups ||
!match.groups.timestamp ||
(options.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 (options.includeAuthor) {
return {date, timestamp, author: match.groups.author};
}
return {date, timestamp};
};
1 change: 1 addition & 0 deletions packages/docusaurus-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export * from './markdownLinks';
export * from './slugger';
export * from './pathUtils';
export * from './hashUtils';
export * from './gitUtils';
export * from './globUtils';
export * from './webpackUtils';
export * from './dataFileUtils';
Expand Down