diff --git a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts index abe8936a6e..42c9d6f172 100644 --- a/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts +++ b/packages/docusaurus-plugin-content-blog/src/__tests__/index.test.ts @@ -10,9 +10,8 @@ import * as path from 'path'; import {normalizePluginOptions} from '@docusaurus/utils-validation'; import { posixPath, - getFileCommitDate, - LAST_UPDATE_FALLBACK, getLocaleConfig, + DEFAULT_TEST_VCS_CONFIG, } from '@docusaurus/utils'; import {DEFAULT_FUTURE_CONFIG} from '@docusaurus/core/src/server/configValidation'; import pluginContentBlog from '../index'; @@ -32,6 +31,12 @@ import type { EditUrlFunction, } from '@docusaurus/plugin-content-blog'; +async function getFileCreationDate(filePath: string): Promise { + return new Date( + (await DEFAULT_TEST_VCS_CONFIG.getFileCreationInfo(filePath)).timestamp, + ); +} + const markdown: MarkdownConfig = { format: 'mdx', mermaid: true, @@ -561,10 +566,7 @@ describe('blog plugin', () => { const blogPosts = await getBlogPosts(siteDir); const noDateSource = path.posix.join('@site', PluginPath, 'no date.md'); const noDateSourceFile = path.posix.join(siteDir, PluginPath, 'no date.md'); - // We know the file exists and we know we have git - const noDateSourceTime = ( - await getFileCommitDate(noDateSourceFile, {age: 'oldest'}) - ).date; + const noDateSourceTime = await getFileCreationDate(noDateSourceFile); expect({ ...getByTitle(blogPosts, 'no date').metadata, @@ -675,29 +677,25 @@ describe('last update', () => { ); const {blogPosts} = (await plugin.loadContent!())!; - expect(blogPosts[0]?.metadata.lastUpdatedBy).toBe('seb'); - expect(blogPosts[0]?.metadata.lastUpdatedAt).toBe( - LAST_UPDATE_FALLBACK.lastUpdatedAt, + const TestLastUpdate = await DEFAULT_TEST_VCS_CONFIG.getFileLastUpdateInfo( + 'any path', ); - expect(blogPosts[1]?.metadata.lastUpdatedBy).toBe( - LAST_UPDATE_FALLBACK.lastUpdatedBy, + expect(blogPosts[0]?.metadata.lastUpdatedBy).toBe('seb'); + expect(blogPosts[0]?.metadata.lastUpdatedAt).toBe( + lastUpdateFor('2021-01-01'), ); + + expect(blogPosts[1]?.metadata.lastUpdatedBy).toBe(TestLastUpdate.author); expect(blogPosts[1]?.metadata.lastUpdatedAt).toBe( - LAST_UPDATE_FALLBACK.lastUpdatedAt, + lastUpdateFor('2021-01-01'), ); expect(blogPosts[2]?.metadata.lastUpdatedBy).toBe('seb'); - expect(blogPosts[2]?.metadata.lastUpdatedAt).toBe( - lastUpdateFor('2021-01-01'), - ); + expect(blogPosts[2]?.metadata.lastUpdatedAt).toBe(TestLastUpdate.timestamp); - expect(blogPosts[3]?.metadata.lastUpdatedBy).toBe( - LAST_UPDATE_FALLBACK.lastUpdatedBy, - ); - expect(blogPosts[3]?.metadata.lastUpdatedAt).toBe( - lastUpdateFor('2021-01-01'), - ); + expect(blogPosts[3]?.metadata.lastUpdatedBy).toBe(TestLastUpdate.author); + expect(blogPosts[3]?.metadata.lastUpdatedAt).toBe(TestLastUpdate.timestamp); }); it('time only', async () => { @@ -711,29 +709,29 @@ describe('last update', () => { ); const {blogPosts} = (await plugin.loadContent!())!; - expect(blogPosts[0]?.metadata.title).toBe('Author'); + const TestLastUpdate = await DEFAULT_TEST_VCS_CONFIG.getFileLastUpdateInfo( + 'any path', + ); + + expect(blogPosts[0]?.metadata.title).toBe('Both'); expect(blogPosts[0]?.metadata.lastUpdatedBy).toBeUndefined(); expect(blogPosts[0]?.metadata.lastUpdatedAt).toBe( - LAST_UPDATE_FALLBACK.lastUpdatedAt, + lastUpdateFor('2021-01-01'), ); - expect(blogPosts[1]?.metadata.title).toBe('Nothing'); + expect(blogPosts[1]?.metadata.title).toBe('Last update date'); expect(blogPosts[1]?.metadata.lastUpdatedBy).toBeUndefined(); expect(blogPosts[1]?.metadata.lastUpdatedAt).toBe( - LAST_UPDATE_FALLBACK.lastUpdatedAt, + lastUpdateFor('2021-01-01'), ); - expect(blogPosts[2]?.metadata.title).toBe('Both'); + expect(blogPosts[2]?.metadata.title).toBe('Author'); expect(blogPosts[2]?.metadata.lastUpdatedBy).toBeUndefined(); - expect(blogPosts[2]?.metadata.lastUpdatedAt).toBe( - lastUpdateFor('2021-01-01'), - ); + expect(blogPosts[2]?.metadata.lastUpdatedAt).toBe(TestLastUpdate.timestamp); - expect(blogPosts[3]?.metadata.title).toBe('Last update date'); + expect(blogPosts[3]?.metadata.title).toBe('Nothing'); expect(blogPosts[3]?.metadata.lastUpdatedBy).toBeUndefined(); - expect(blogPosts[3]?.metadata.lastUpdatedAt).toBe( - lastUpdateFor('2021-01-01'), - ); + expect(blogPosts[3]?.metadata.lastUpdatedAt).toBe(TestLastUpdate.timestamp); }); it('author only', async () => { @@ -747,20 +745,20 @@ describe('last update', () => { ); const {blogPosts} = (await plugin.loadContent!())!; + const TestLastUpdate = await DEFAULT_TEST_VCS_CONFIG.getFileLastUpdateInfo( + 'any path', + ); + expect(blogPosts[0]?.metadata.lastUpdatedBy).toBe('seb'); expect(blogPosts[0]?.metadata.lastUpdatedAt).toBeUndefined(); - expect(blogPosts[1]?.metadata.lastUpdatedBy).toBe( - LAST_UPDATE_FALLBACK.lastUpdatedBy, - ); + expect(blogPosts[1]?.metadata.lastUpdatedBy).toBe(TestLastUpdate.author); expect(blogPosts[1]?.metadata.lastUpdatedAt).toBeUndefined(); expect(blogPosts[2]?.metadata.lastUpdatedBy).toBe('seb'); expect(blogPosts[2]?.metadata.lastUpdatedAt).toBeUndefined(); - expect(blogPosts[3]?.metadata.lastUpdatedBy).toBe( - LAST_UPDATE_FALLBACK.lastUpdatedBy, - ); + expect(blogPosts[3]?.metadata.lastUpdatedBy).toBe(TestLastUpdate.author); expect(blogPosts[3]?.metadata.lastUpdatedAt).toBeUndefined(); }); diff --git a/packages/docusaurus-types/src/config.d.ts b/packages/docusaurus-types/src/config.d.ts index f22380430e..3f4a0c6fdc 100644 --- a/packages/docusaurus-types/src/config.d.ts +++ b/packages/docusaurus-types/src/config.d.ts @@ -44,9 +44,9 @@ export type FutureV4Config = { // The agnostic term "VCS" is used instead of "git" to acknowledge the existence // of other version control systems, and external systems like CMSs and i18n // translation SaaS (e.g., Crowdin) -type VcsChangeInfo = {timestamp: number; author: string}; +export type VcsChangeInfo = {timestamp: number; author: string}; -type VscInitializeParams = { +export type VscInitializeParams = { siteDir: string; // TODO could it be useful to provide all plugins getPathsToWatch() here? // this could give the opportunity to find out all VCS roots ahead of times diff --git a/packages/docusaurus-types/src/index.d.ts b/packages/docusaurus-types/src/index.d.ts index 3032f0810e..8b8cb11e40 100644 --- a/packages/docusaurus-types/src/index.d.ts +++ b/packages/docusaurus-types/src/index.d.ts @@ -14,6 +14,8 @@ export { FasterConfig, StorageConfig, VcsConfig, + VcsChangeInfo, + VscInitializeParams, Config, } from './config'; diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts index 5842b3ed21..87b9311446 100644 --- a/packages/docusaurus-utils/src/index.ts +++ b/packages/docusaurus-utils/src/index.ts @@ -131,6 +131,6 @@ export { type FrontMatterLastUpdate, } from './lastUpdateUtils'; -export {DEFAULT_VCS_CONFIG} from './vcs/vcs'; +export {DEFAULT_VCS_CONFIG, DEFAULT_TEST_VCS_CONFIG} from './vcs/vcs'; export {normalizeTags, reportInlineTags} from './tags'; diff --git a/packages/docusaurus-utils/src/lastUpdateUtils.ts b/packages/docusaurus-utils/src/lastUpdateUtils.ts index 7aa4f7560a..d22cf8abc7 100644 --- a/packages/docusaurus-utils/src/lastUpdateUtils.ts +++ b/packages/docusaurus-utils/src/lastUpdateUtils.ts @@ -72,6 +72,8 @@ export async function getGitLastUpdate( } } +// TODO Docusaurus v4: remove this legacy fallback data +// we now have a Vcs API that does the same for dev/test envs export const LAST_UPDATE_FALLBACK: LastUpdateData = { lastUpdatedAt: 1539502055000, lastUpdatedBy: 'Author', diff --git a/packages/docusaurus-utils/src/vcs/gitUtils.ts b/packages/docusaurus-utils/src/vcs/gitUtils.ts index 1bc19734a2..09738110f8 100644 --- a/packages/docusaurus-utils/src/vcs/gitUtils.ts +++ b/packages/docusaurus-utils/src/vcs/gitUtils.ts @@ -48,9 +48,13 @@ const realHasGitFn = () => { const hasGit = process.env.NODE_ENV === 'test' ? realHasGitFn : _.memoize(realHasGitFn); +// TODO Docusaurus v4: remove this +// Exceptions are not made for control flow logic /** Custom error thrown when git is not found in `PATH`. */ export class GitNotFoundError extends Error {} +// TODO Docusaurus v4: remove this, only kept for retro-compatibility +// Exceptions are not made for control flow logic /** Custom error thrown when the current file is not tracked by git. */ export class FileNotTrackedError extends Error {} diff --git a/packages/docusaurus-utils/src/vcs/vcs.ts b/packages/docusaurus-utils/src/vcs/vcs.ts index 194a3da57d..e817bef362 100644 --- a/packages/docusaurus-utils/src/vcs/vcs.ts +++ b/packages/docusaurus-utils/src/vcs/vcs.ts @@ -5,24 +5,51 @@ * LICENSE file in the root directory of this source tree. */ +import {VcsHardcoded} from './vcsHardcoded'; import {VcsGitAdHoc} from './vcsGitAdHoc'; import type {VcsConfig} from '@docusaurus/types'; +const VcsPresets = { + 'git-ad-hoc': VcsGitAdHoc, + hardcoded: VcsHardcoded, +} as const satisfies Record; + +type VscPresetName = keyof typeof VcsPresets; + +function getVcsPreset(presetName: VscPresetName): VcsConfig { + return VcsPresets[presetName]; +} + function getDefaultVcsConfig(): VcsConfig { - // TODO configure this properly for dev/test envs - // + opt-in for new Git Eager config - // + escape hatch env for forcing a given known config + // Escape hatch to override the default VCS preset we use + if (process.env.DOCUSAURUS_VCS) { + const vcs = getVcsPreset(process.env.DOCUSAURUS_VCS as VscPresetName); + if (vcs) { + return vcs; + } else { + throw new Error( + `Unknown DOCUSAURUS_VCS preset name: ${process.env.DOCUSAURUS_VCS}`, + ); + } + } if (process.env.NODE_ENV === 'production') { - return VcsGitAdHoc; + return getVcsPreset('git-ad-hoc'); } + + // Return hardcoded values in dev to improve DX if (process.env.NODE_ENV === 'development') { - return VcsGitAdHoc; + return getVcsPreset('hardcoded'); } + + // Return hardcoded values in test to make tests simpler and faster if (process.env.NODE_ENV === 'test') { - return VcsGitAdHoc; + return getVcsPreset('hardcoded'); } + return VcsGitAdHoc; } +export const DEFAULT_TEST_VCS_CONFIG: VcsConfig = VcsHardcoded; + export const DEFAULT_VCS_CONFIG: VcsConfig = getDefaultVcsConfig(); diff --git a/packages/docusaurus-utils/src/vcs/vcsHardcoded.ts b/packages/docusaurus-utils/src/vcs/vcsHardcoded.ts new file mode 100644 index 0000000000..9b1cc9d6bd --- /dev/null +++ b/packages/docusaurus-utils/src/vcs/vcsHardcoded.ts @@ -0,0 +1,37 @@ +/** + * 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 type {VcsConfig, VcsChangeInfo} from '@docusaurus/types'; + +export const VCS_CHANGE_HARDCODED_CREATION_INFO: VcsChangeInfo = { + timestamp: 1490997600000, // 1st Apr 2017 + author: 'Creator', +}; + +export const VCS_CHANGE_HARDCODED_LAST_UPDATE_INFO: VcsChangeInfo = { + timestamp: 1539502055000, // 14th Oct 2018 + author: 'Author', +}; + +/** + * This VCS implementation always returns hardcoded values for testing purposes. + * It is also useful in dev environments where VCS info is not important. + * Reading information from the VCS can be slow and is not always necessary. + */ +export const VcsHardcoded: VcsConfig = { + initialize: () => { + // Noop + }, + + getFileCreationInfo: async (_filePath: string) => { + return VCS_CHANGE_HARDCODED_CREATION_INFO; + }, + + getFileLastUpdateInfo: async (_filePath: string) => { + return VCS_CHANGE_HARDCODED_LAST_UPDATE_INFO; + }, +};