Add API to initialize the VCS config

This commit is contained in:
sebastien 2025-10-30 16:57:29 +01:00
parent b80ee31401
commit 7dc89e0b98
5 changed files with 144 additions and 0 deletions

View File

@ -46,12 +46,32 @@ export type FutureV4Config = {
// translation SaaS (e.g., Crowdin)
type VcsChangeInfo = {timestamp: number; author: string};
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
// this is mostly useful for multi-git-repo setups, can be added later
};
// VCS (Version Control System) config hooks to get file change info.
// This lets you override and customize the default Docusaurus behavior.
// This can be useful to optimize calls or when using something else than git
// See https://github.com/facebook/docusaurus/issues/11208
// See https://github.com/e18e/ecosystem-issues/issues/216
export type VcsConfig = {
/**
* Initialize the VCS system.
* This is notably useful to pre-read eagerly a full Git repository so that
* all the files first/last update info can be retrieved efficiently later
*
* Note: for now, this function is synchronous on purpose, it can be used to
* start warming up the VCS by reading eagerly, but we don't want to delay
* the rest of the Docusaurus start/build process. Instead of awaiting the
* init promise, you can create/store it and await it later during reads.
*
* @param params Initialization params that can be useful to warm up the VCS
*/
initialize: (params: VscInitializeParams) => void;
getFileCreationInfo: (filePath: string) => Promise<VcsChangeInfo | null>;
getFileLastUpdateInfo: (filePath: string) => Promise<VcsChangeInfo | null>;
};

View File

@ -14,6 +14,10 @@ import {getLastUpdate} from './lastUpdateUtils';
import type {VcsConfig} from '@docusaurus/types';
export const DEFAULT_VCS_CONFIG: VcsConfig = {
initialize: () => {
// Nothing to do here for the default/historical Git implementation
},
getFileCreationInfo: async (filePath: string) => {
try {
return await getFileCommitDate(filePath, {
@ -36,6 +40,7 @@ export const DEFAULT_VCS_CONFIG: VcsConfig = {
}
}
},
getFileLastUpdateInfo: async (filePath: string) => {
// TODO non-ideal integration but good enough for now
// This keeps this new VscConfig system retro-compatible with the existing

View File

@ -76,6 +76,7 @@ describe('normalizeConfig', () => {
namespace: true,
},
experimental_vcs: {
initialize: (_params) => {},
getFileCreationInfo: (_filePath) => null,
getFileLastUpdateInfo: (_filePath) => null,
},
@ -1084,6 +1085,7 @@ describe('future', () => {
ssgWorkerThreads: true,
},
experimental_vcs: {
initialize: (_params) => {},
getFileCreationInfo: (_filePath) => null,
getFileLastUpdateInfo: (_filePath) => null,
},
@ -1431,6 +1433,7 @@ describe('future', () => {
it('accepts vcs - full', () => {
const vcs: VcsConfig = {
initialize: (_params) => {},
getFileCreationInfo: (_filePath) => null,
getFileLastUpdateInfo: (_filePath) => null,
};
@ -1473,6 +1476,110 @@ describe('future', () => {
`);
});
describe('initialize', () => {
it('accepts fn(params)', () => {
const vcs: Partial<VcsConfig> = {
initialize: (_params) => null,
};
expect(
normalizeConfig({
future: {
experimental_vcs: vcs,
},
}),
).toEqual(
vcsContaining({
...DEFAULT_VCS_CONFIG,
...vcs,
}),
);
});
it('accepts undefined', () => {
const vcs: Partial<VcsConfig> = {
initialize: undefined,
};
expect(
normalizeConfig({
future: {
experimental_vcs: vcs,
},
}),
).toEqual(
vcsContaining({
...DEFAULT_VCS_CONFIG,
}),
);
});
it('rejects null', () => {
const vcs: Partial<VcsConfig> = {
// @ts-expect-error: invalid
initialize: null,
};
expect(() =>
normalizeConfig({
future: {
experimental_vcs: vcs,
},
}),
).toThrowErrorMatchingInlineSnapshot(`
""future.experimental_vcs.initialize" must be of type function
"
`);
});
it('rejects number', () => {
const vcs: Partial<VcsConfig> = {
// @ts-expect-error: invalid
initialize: 42,
};
expect(() =>
normalizeConfig({
future: {
experimental_vcs: vcs,
},
}),
).toThrowErrorMatchingInlineSnapshot(`
""future.experimental_vcs.initialize" must be of type function
"
`);
});
it('rejects fn()', () => {
const vcs: Partial<VcsConfig> = {
initialize: () => null,
};
expect(() =>
normalizeConfig({
future: {
experimental_vcs: vcs,
},
}),
).toThrowErrorMatchingInlineSnapshot(`
""future.experimental_vcs.initialize" must have an arity of 1
"
`);
});
it('rejects fn(filePath, anotherArg)', () => {
const vcs: Partial<VcsConfig> = {
// @ts-expect-error: invalid
initialize: (_params, _anotherArg) => null,
};
expect(() =>
normalizeConfig({
future: {
experimental_vcs: vcs,
},
}),
).toThrowErrorMatchingInlineSnapshot(`
""future.experimental_vcs.initialize" must have an arity of 1
"
`);
});
});
describe('getFileCreationInfo', () => {
it('accepts fn(filePath)', () => {
const vcs: Partial<VcsConfig> = {

View File

@ -335,6 +335,10 @@ const STORAGE_CONFIG_SCHEMA = Joi.object({
.default(DEFAULT_STORAGE_CONFIG);
const VCS_CONFIG_SCHEMA = Joi.object<VcsConfig>({
initialize: Joi.function()
.arity(1)
.optional()
.default(() => DEFAULT_VCS_CONFIG.initialize),
getFileCreationInfo: Joi.function()
.arity(1)
.optional()

View File

@ -159,9 +159,17 @@ function createCustomVcsConfig(): VcsConfig {
*/
return {
initialize: ({siteDir}) => {
console.log('initializing custom site VCS config...');
getRepoInfoForFile(siteDir).catch((e) => {
console.error('Failed to read the Docusaurus Git repository info', e);
});
},
getFileCreationInfo: async (filePath: string) => {
return DEFAULT_VCS_CONFIG.getFileCreationInfo(filePath);
},
getFileLastUpdateInfo: async (filePath: string) => {
return getRepoInfoForFile(filePath);
},