mirror of
https://github.com/facebook/docusaurus.git
synced 2025-12-26 01:33:02 +00:00
add VcsConfig + default retro-compatible implementation + config option + config validation tests
This commit is contained in:
parent
a4742594a9
commit
bb85b80813
|
|
@ -40,6 +40,22 @@ export type FutureV4Config = {
|
|||
useCssCascadeLayers: boolean;
|
||||
};
|
||||
|
||||
// VCS (Version Control System) info about a given change, e.g., a git commit.
|
||||
// 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};
|
||||
|
||||
// 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 = {
|
||||
getFileCreationInfo: (filePath: string) => Promise<VcsChangeInfo | null>;
|
||||
getFileLastUpdateInfo: (filePath: string) => Promise<VcsChangeInfo | null>;
|
||||
};
|
||||
|
||||
export type FutureConfig = {
|
||||
/**
|
||||
* Turns v4 future flags on
|
||||
|
|
@ -50,6 +66,8 @@ export type FutureConfig = {
|
|||
|
||||
experimental_storage: StorageConfig;
|
||||
|
||||
experimental_vcs: VcsConfig;
|
||||
|
||||
/**
|
||||
* Docusaurus can work with 2 router types.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export {
|
|||
FutureV4Config,
|
||||
FasterConfig,
|
||||
StorageConfig,
|
||||
VcsConfig,
|
||||
Config,
|
||||
} from './config';
|
||||
|
||||
|
|
|
|||
|
|
@ -126,6 +126,7 @@ export async function getFileCommitDate(
|
|||
timestamp: number;
|
||||
author?: string;
|
||||
}> {
|
||||
console.log({file});
|
||||
if (!hasGit()) {
|
||||
throw new GitNotFoundError(
|
||||
`Failed to retrieve git history for "${file}" because git is not installed.`,
|
||||
|
|
|
|||
|
|
@ -129,4 +129,6 @@ export {
|
|||
type FrontMatterLastUpdate,
|
||||
} from './lastUpdateUtils';
|
||||
|
||||
export {DEFAULT_VCS_CONFIG} from './vcsUtils';
|
||||
|
||||
export {normalizeTags, reportInlineTags} from './tags';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* 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 {getFileCommitDate} from './gitUtils';
|
||||
import {getLastUpdate} from './lastUpdateUtils';
|
||||
import type {VcsConfig} from '@docusaurus/types';
|
||||
|
||||
export const DEFAULT_VCS_CONFIG: VcsConfig = {
|
||||
getFileCreationInfo: async (filePath: string) => {
|
||||
return getFileCommitDate(filePath, {
|
||||
age: 'oldest',
|
||||
includeAuthor: true,
|
||||
});
|
||||
},
|
||||
getFileLastUpdateInfo: async (filePath: string) => {
|
||||
// TODO non-ideal integration but good enough for now
|
||||
// This keeps this new VscConfig system retro-compatible with the existing
|
||||
// historical Docusaurus behavior based on Git
|
||||
const result = await getLastUpdate(filePath);
|
||||
if (result === null) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
timestamp: result.lastUpdatedAt!,
|
||||
author: result.lastUpdatedBy!,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import {jest} from '@jest/globals';
|
||||
import {DEFAULT_VCS_CONFIG} from '@docusaurus/utils';
|
||||
import {
|
||||
ConfigSchema,
|
||||
DEFAULT_CONFIG,
|
||||
|
|
@ -29,6 +30,7 @@ import type {
|
|||
PluginConfig,
|
||||
I18nConfig,
|
||||
I18nLocaleConfig,
|
||||
VcsConfig,
|
||||
} from '@docusaurus/types';
|
||||
import type {DeepPartial} from 'utility-types';
|
||||
|
||||
|
|
@ -73,6 +75,10 @@ describe('normalizeConfig', () => {
|
|||
type: 'sessionStorage',
|
||||
namespace: true,
|
||||
},
|
||||
experimental_vcs: {
|
||||
getFileCreationInfo: (_filePath) => null,
|
||||
getFileLastUpdateInfo: (_filePath) => null,
|
||||
},
|
||||
experimental_router: 'hash',
|
||||
},
|
||||
tagline: 'my awesome site',
|
||||
|
|
@ -1077,6 +1083,10 @@ describe('future', () => {
|
|||
rspackPersistentCache: true,
|
||||
ssgWorkerThreads: true,
|
||||
},
|
||||
experimental_vcs: {
|
||||
getFileCreationInfo: (_filePath) => null,
|
||||
getFileLastUpdateInfo: (_filePath) => null,
|
||||
},
|
||||
experimental_storage: {
|
||||
type: 'sessionStorage',
|
||||
namespace: 'myNamespace',
|
||||
|
|
@ -1394,6 +1404,284 @@ describe('future', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('vcs', () => {
|
||||
function vcsContaining(vcs: Partial<VcsConfig>) {
|
||||
return futureContaining({
|
||||
experimental_vcs: expect.objectContaining(vcs),
|
||||
});
|
||||
}
|
||||
|
||||
it('accepts vcs - undefined', () => {
|
||||
expect(
|
||||
normalizeConfig({
|
||||
future: {
|
||||
experimental_vcs: undefined,
|
||||
},
|
||||
}),
|
||||
).toEqual(futureContaining(DEFAULT_FUTURE_CONFIG));
|
||||
});
|
||||
|
||||
it('accepts vcs - empty', () => {
|
||||
expect(
|
||||
normalizeConfig({
|
||||
future: {experimental_vcs: {}},
|
||||
}),
|
||||
).toEqual(futureContaining(DEFAULT_FUTURE_CONFIG));
|
||||
});
|
||||
|
||||
it('accepts vcs - full', () => {
|
||||
const vcs: VcsConfig = {
|
||||
getFileCreationInfo: (_filePath) => null,
|
||||
getFileLastUpdateInfo: (_filePath) => null,
|
||||
};
|
||||
expect(
|
||||
normalizeConfig({
|
||||
future: {
|
||||
experimental_vcs: vcs,
|
||||
},
|
||||
}),
|
||||
).toEqual(vcsContaining(vcs));
|
||||
});
|
||||
|
||||
it('rejects vcs - boolean', () => {
|
||||
// @ts-expect-error: invalid
|
||||
const vcs: Partial<VcsConfig> = true;
|
||||
expect(() =>
|
||||
normalizeConfig({
|
||||
future: {
|
||||
experimental_vcs: vcs,
|
||||
},
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(`
|
||||
""future.experimental_vcs" must be of type object
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('rejects vcs - number', () => {
|
||||
// @ts-expect-error: invalid
|
||||
const vcs: Partial<VcsConfig> = 42;
|
||||
expect(() =>
|
||||
normalizeConfig({
|
||||
future: {
|
||||
experimental_vcs: vcs,
|
||||
},
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(`
|
||||
""future.experimental_vcs" must be of type object
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
describe('getFileCreationInfo', () => {
|
||||
it('accepts fn(filePath)', () => {
|
||||
const vcs: Partial<VcsConfig> = {
|
||||
getFileCreationInfo: (_filePath) => null,
|
||||
};
|
||||
expect(
|
||||
normalizeConfig({
|
||||
future: {
|
||||
experimental_vcs: vcs,
|
||||
},
|
||||
}),
|
||||
).toEqual(
|
||||
vcsContaining({
|
||||
...DEFAULT_VCS_CONFIG,
|
||||
...vcs,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts undefined', () => {
|
||||
const vcs: Partial<VcsConfig> = {
|
||||
getFileCreationInfo: undefined,
|
||||
};
|
||||
expect(
|
||||
normalizeConfig({
|
||||
future: {
|
||||
experimental_vcs: vcs,
|
||||
},
|
||||
}),
|
||||
).toEqual(
|
||||
vcsContaining({
|
||||
...DEFAULT_VCS_CONFIG,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects null', () => {
|
||||
const vcs: Partial<VcsConfig> = {
|
||||
// @ts-expect-error: invalid
|
||||
getFileCreationInfo: null,
|
||||
};
|
||||
expect(() =>
|
||||
normalizeConfig({
|
||||
future: {
|
||||
experimental_vcs: vcs,
|
||||
},
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(`
|
||||
""future.experimental_vcs.getFileCreationInfo" must be of type function
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('rejects number', () => {
|
||||
const vcs: Partial<VcsConfig> = {
|
||||
// @ts-expect-error: invalid
|
||||
getFileCreationInfo: 42,
|
||||
};
|
||||
expect(() =>
|
||||
normalizeConfig({
|
||||
future: {
|
||||
experimental_vcs: vcs,
|
||||
},
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(`
|
||||
""future.experimental_vcs.getFileCreationInfo" must be of type function
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('rejects fn()', () => {
|
||||
const vcs: Partial<VcsConfig> = {
|
||||
getFileCreationInfo: () => null,
|
||||
};
|
||||
expect(() =>
|
||||
normalizeConfig({
|
||||
future: {
|
||||
experimental_vcs: vcs,
|
||||
},
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(`
|
||||
""future.experimental_vcs.getFileCreationInfo" must have an arity of 1
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('rejects fn(filePath, anotherArg)', () => {
|
||||
const vcs: Partial<VcsConfig> = {
|
||||
// @ts-expect-error: invalid
|
||||
getFileCreationInfo: (_filePath, _anotherArg) => null,
|
||||
};
|
||||
expect(() =>
|
||||
normalizeConfig({
|
||||
future: {
|
||||
experimental_vcs: vcs,
|
||||
},
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(`
|
||||
""future.experimental_vcs.getFileCreationInfo" must have an arity of 1
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getFileLastUpdateInfo', () => {
|
||||
it('accepts fn(filePath)', () => {
|
||||
const vcs: Partial<VcsConfig> = {
|
||||
getFileLastUpdateInfo: (_filePath) => null,
|
||||
};
|
||||
expect(
|
||||
normalizeConfig({
|
||||
future: {
|
||||
experimental_vcs: vcs,
|
||||
},
|
||||
}),
|
||||
).toEqual(
|
||||
vcsContaining({
|
||||
...DEFAULT_VCS_CONFIG,
|
||||
...vcs,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('accepts undefined', () => {
|
||||
const vcs: Partial<VcsConfig> = {
|
||||
getFileLastUpdateInfo: undefined,
|
||||
};
|
||||
expect(
|
||||
normalizeConfig({
|
||||
future: {
|
||||
experimental_vcs: vcs,
|
||||
},
|
||||
}),
|
||||
).toEqual(
|
||||
vcsContaining({
|
||||
...DEFAULT_VCS_CONFIG,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects null', () => {
|
||||
const vcs: Partial<VcsConfig> = {
|
||||
// @ts-expect-error: invalid
|
||||
getFileLastUpdateInfo: null,
|
||||
};
|
||||
expect(() =>
|
||||
normalizeConfig({
|
||||
future: {
|
||||
experimental_vcs: vcs,
|
||||
},
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(`
|
||||
""future.experimental_vcs.getFileLastUpdateInfo" must be of type function
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('rejects number', () => {
|
||||
const vcs: Partial<VcsConfig> = {
|
||||
// @ts-expect-error: invalid
|
||||
getFileLastUpdateInfo: 42,
|
||||
};
|
||||
expect(() =>
|
||||
normalizeConfig({
|
||||
future: {
|
||||
experimental_vcs: vcs,
|
||||
},
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(`
|
||||
""future.experimental_vcs.getFileLastUpdateInfo" must be of type function
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('rejects fn()', () => {
|
||||
const vcs: Partial<VcsConfig> = {
|
||||
getFileLastUpdateInfo: () => null,
|
||||
};
|
||||
expect(() =>
|
||||
normalizeConfig({
|
||||
future: {
|
||||
experimental_vcs: vcs,
|
||||
},
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(`
|
||||
""future.experimental_vcs.getFileLastUpdateInfo" must have an arity of 1
|
||||
"
|
||||
`);
|
||||
});
|
||||
|
||||
it('rejects fn(filePath, anotherArg)', () => {
|
||||
const vcs: Partial<VcsConfig> = {
|
||||
// @ts-expect-error: invalid
|
||||
getFileLastUpdateInfo: (_filePath, _anotherArg) => null,
|
||||
};
|
||||
expect(() =>
|
||||
normalizeConfig({
|
||||
future: {
|
||||
experimental_vcs: vcs,
|
||||
},
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(`
|
||||
""future.experimental_vcs.getFileLastUpdateInfo" must have an arity of 1
|
||||
"
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('faster', () => {
|
||||
function fasterContaining(faster: Partial<FasterConfig>) {
|
||||
return futureContaining({
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
DEFAULT_PARSE_FRONT_MATTER,
|
||||
DEFAULT_STATIC_DIR_NAME,
|
||||
DEFAULT_I18N_DIR_NAME,
|
||||
DEFAULT_VCS_CONFIG,
|
||||
} from '@docusaurus/utils';
|
||||
import {Joi, printWarning} from '@docusaurus/utils-validation';
|
||||
import {
|
||||
|
|
@ -27,6 +28,7 @@ import type {
|
|||
MarkdownConfig,
|
||||
MarkdownHooks,
|
||||
I18nLocaleConfig,
|
||||
VcsConfig,
|
||||
} from '@docusaurus/types';
|
||||
|
||||
const DEFAULT_I18N_LOCALE = 'en';
|
||||
|
|
@ -106,6 +108,7 @@ export const DEFAULT_FUTURE_CONFIG: FutureConfig = {
|
|||
v4: DEFAULT_FUTURE_V4_CONFIG,
|
||||
experimental_faster: DEFAULT_FASTER_CONFIG,
|
||||
experimental_storage: DEFAULT_STORAGE_CONFIG,
|
||||
experimental_vcs: DEFAULT_VCS_CONFIG,
|
||||
experimental_router: 'browser',
|
||||
};
|
||||
|
||||
|
|
@ -331,10 +334,24 @@ const STORAGE_CONFIG_SCHEMA = Joi.object({
|
|||
.optional()
|
||||
.default(DEFAULT_STORAGE_CONFIG);
|
||||
|
||||
const VCS_CONFIG_SCHEMA = Joi.object<VcsConfig>({
|
||||
getFileCreationInfo: Joi.function()
|
||||
.arity(1)
|
||||
.optional()
|
||||
.default(() => DEFAULT_VCS_CONFIG.getFileCreationInfo),
|
||||
getFileLastUpdateInfo: Joi.function()
|
||||
.arity(1)
|
||||
.optional()
|
||||
.default(() => DEFAULT_VCS_CONFIG.getFileLastUpdateInfo),
|
||||
})
|
||||
.optional()
|
||||
.default(DEFAULT_VCS_CONFIG);
|
||||
|
||||
const FUTURE_CONFIG_SCHEMA = Joi.object<FutureConfig>({
|
||||
v4: FUTURE_V4_SCHEMA,
|
||||
experimental_faster: FASTER_CONFIG_SCHEMA,
|
||||
experimental_storage: STORAGE_CONFIG_SCHEMA,
|
||||
experimental_vcs: VCS_CONFIG_SCHEMA,
|
||||
experimental_router: Joi.string()
|
||||
.equal('browser', 'hash')
|
||||
.default(DEFAULT_FUTURE_CONFIG.experimental_router),
|
||||
|
|
|
|||
Loading…
Reference in New Issue