mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-11 16:17:25 +02:00
feat(v2): docs version configuration: lastVersion, version.{path,label} (#3357)
* add new docs versioning options * Add some tests for new versioning options * Add some docs for version configurations * try to fix broken link detection after /docs/ root paths have been removed on deploy previews * improve dev/deploypreview versioning configurations * disable custom current version path, as it produces broken links * readVersionDocs should not be order sensitive * fix versions page according to versioning config * fix versions page according to versioning config
This commit is contained in:
parent
4bfc3bbbe7
commit
ae877f2990
15 changed files with 387 additions and 79 deletions
|
@ -290,6 +290,7 @@ Object {
|
|||
}",
|
||||
"version-current-metadata-prop-751.json": "{
|
||||
\\"version\\": \\"current\\",
|
||||
\\"label\\": \\"Next\\",
|
||||
\\"docsSidebars\\": {
|
||||
\\"docs\\": [
|
||||
{
|
||||
|
@ -613,6 +614,7 @@ Object {
|
|||
}",
|
||||
"version-1-0-0-metadata-prop-608.json": "{
|
||||
\\"version\\": \\"1.0.0\\",
|
||||
\\"label\\": \\"1.0.0\\",
|
||||
\\"docsSidebars\\": {
|
||||
\\"version-1.0.0/community\\": [
|
||||
{
|
||||
|
@ -628,6 +630,7 @@ Object {
|
|||
}",
|
||||
"version-current-metadata-prop-751.json": "{
|
||||
\\"version\\": \\"current\\",
|
||||
\\"label\\": \\"Next\\",
|
||||
\\"docsSidebars\\": {
|
||||
\\"community\\": [
|
||||
{
|
||||
|
@ -1069,6 +1072,7 @@ Object {
|
|||
}",
|
||||
"version-1-0-0-metadata-prop-608.json": "{
|
||||
\\"version\\": \\"1.0.0\\",
|
||||
\\"label\\": \\"1.0.0\\",
|
||||
\\"docsSidebars\\": {
|
||||
\\"version-1.0.0/docs\\": [
|
||||
{
|
||||
|
@ -1110,6 +1114,7 @@ Object {
|
|||
}",
|
||||
"version-1-0-1-metadata-prop-e87.json": "{
|
||||
\\"version\\": \\"1.0.1\\",
|
||||
\\"label\\": \\"1.0.1\\",
|
||||
\\"docsSidebars\\": {
|
||||
\\"version-1.0.1/docs\\": [
|
||||
{
|
||||
|
@ -1145,6 +1150,7 @@ Object {
|
|||
}",
|
||||
"version-current-metadata-prop-751.json": "{
|
||||
\\"version\\": \\"current\\",
|
||||
\\"label\\": \\"Next\\",
|
||||
\\"docsSidebars\\": {
|
||||
\\"docs\\": [
|
||||
{
|
||||
|
@ -1180,6 +1186,7 @@ Object {
|
|||
}",
|
||||
"version-with-slugs-metadata-prop-2bf.json": "{
|
||||
\\"version\\": \\"withSlugs\\",
|
||||
\\"label\\": \\"withSlugs\\",
|
||||
\\"docsSidebars\\": {
|
||||
\\"version-1.0.1/docs\\": [
|
||||
{
|
||||
|
|
|
@ -135,21 +135,23 @@ describe('simple site', () => {
|
|||
|
||||
test('readVersionDocs', async () => {
|
||||
const docs = await readVersionDocs(currentVersion, options);
|
||||
expect(docs.map((doc) => doc.source)).toMatchObject([
|
||||
'hello.md',
|
||||
'ipsum.md',
|
||||
'lorem.md',
|
||||
'rootAbsoluteSlug.md',
|
||||
'rootRelativeSlug.md',
|
||||
'rootResolvedSlug.md',
|
||||
'rootTryToEscapeSlug.md',
|
||||
'foo/bar.md',
|
||||
'foo/baz.md',
|
||||
'slugs/absoluteSlug.md',
|
||||
'slugs/relativeSlug.md',
|
||||
'slugs/resolvedSlug.md',
|
||||
'slugs/tryToEscapeSlug.md',
|
||||
]);
|
||||
expect(docs.map((doc) => doc.source).sort()).toEqual(
|
||||
[
|
||||
'hello.md',
|
||||
'ipsum.md',
|
||||
'lorem.md',
|
||||
'rootAbsoluteSlug.md',
|
||||
'rootRelativeSlug.md',
|
||||
'rootResolvedSlug.md',
|
||||
'rootTryToEscapeSlug.md',
|
||||
'foo/bar.md',
|
||||
'foo/baz.md',
|
||||
'slugs/absoluteSlug.md',
|
||||
'slugs/relativeSlug.md',
|
||||
'slugs/resolvedSlug.md',
|
||||
'slugs/tryToEscapeSlug.md',
|
||||
].sort(),
|
||||
);
|
||||
});
|
||||
|
||||
test('normal docs', async () => {
|
||||
|
|
|
@ -36,6 +36,16 @@ describe('normalizeDocsPluginOptions', () => {
|
|||
excludeNextVersionDocs: true,
|
||||
includeCurrentVersion: false,
|
||||
disableVersioning: true,
|
||||
versions: {
|
||||
current: {
|
||||
path: 'next',
|
||||
label: 'next',
|
||||
},
|
||||
version1: {
|
||||
path: 'hello',
|
||||
label: 'world',
|
||||
},
|
||||
},
|
||||
};
|
||||
const {value, error} = await OptionsSchema.validate(userOptions);
|
||||
expect(value).toEqual(userOptions);
|
||||
|
@ -117,4 +127,32 @@ describe('normalizeDocsPluginOptions', () => {
|
|||
`"\\"remarkPlugins\\" must be an array"`,
|
||||
);
|
||||
});
|
||||
|
||||
test('should reject bad lastVersion', () => {
|
||||
expect(() => {
|
||||
normalizePluginOptions(OptionsSchema, {
|
||||
lastVersion: false,
|
||||
});
|
||||
}).toThrowErrorMatchingInlineSnapshot(
|
||||
`"\\"lastVersion\\" must be a string"`,
|
||||
);
|
||||
});
|
||||
|
||||
test('should reject bad versions', () => {
|
||||
expect(() => {
|
||||
normalizePluginOptions(OptionsSchema, {
|
||||
versions: {
|
||||
current: {
|
||||
hey: 3,
|
||||
},
|
||||
version1: {
|
||||
path: 'hello',
|
||||
label: 'world',
|
||||
},
|
||||
},
|
||||
});
|
||||
}).toThrowErrorMatchingInlineSnapshot(
|
||||
`"\\"versions.current.hey\\" is not allowed"`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -94,7 +94,63 @@ describe('simple site', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
test('readVersionsMetadata simple site with base url', () => {
|
||||
test('readVersionsMetadata simple site with current version config', () => {
|
||||
const versionsMetadata = readVersionsMetadata({
|
||||
options: {
|
||||
...defaultOptions,
|
||||
versions: {
|
||||
current: {
|
||||
label: 'current-label',
|
||||
path: 'current-path',
|
||||
},
|
||||
},
|
||||
},
|
||||
context: {
|
||||
...defaultContext,
|
||||
baseUrl: '/myBaseUrl',
|
||||
},
|
||||
});
|
||||
|
||||
expect(versionsMetadata).toEqual([
|
||||
{
|
||||
...vCurrent,
|
||||
versionPath: '/myBaseUrl/docs/current-path',
|
||||
versionLabel: 'current-label',
|
||||
routePriority: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('readVersionsMetadata simple site with unknown lastVersion should throw', () => {
|
||||
expect(() =>
|
||||
readVersionsMetadata({
|
||||
options: {...defaultOptions, lastVersion: 'unknownVersionName'},
|
||||
context: defaultContext,
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Docs option lastVersion=unknownVersionName is invalid. Available version names are: current"`,
|
||||
);
|
||||
});
|
||||
|
||||
test('readVersionsMetadata simple site with unknown version configurations should throw', () => {
|
||||
expect(() =>
|
||||
readVersionsMetadata({
|
||||
options: {
|
||||
...defaultOptions,
|
||||
versions: {
|
||||
current: {label: 'current'},
|
||||
unknownVersionName1: {label: 'unknownVersionName1'},
|
||||
unknownVersionName2: {label: 'unknownVersionName2'},
|
||||
},
|
||||
},
|
||||
context: defaultContext,
|
||||
}),
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Docs versions option provided configuration for unknown versions: unknownVersionName1,unknownVersionName2. Available version names are: current"`,
|
||||
);
|
||||
});
|
||||
|
||||
test('readVersionsMetadata simple site with disableVersioning while single version should throw', () => {
|
||||
expect(() =>
|
||||
readVersionsMetadata({
|
||||
options: {...defaultOptions, disableVersioning: true},
|
||||
|
@ -105,7 +161,7 @@ describe('simple site', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('readVersionsMetadata simple site with base url', () => {
|
||||
test('readVersionsMetadata simple site without including current version should throw', () => {
|
||||
expect(() =>
|
||||
readVersionsMetadata({
|
||||
options: {...defaultOptions, includeCurrentVersion: false},
|
||||
|
@ -205,6 +261,42 @@ describe('versioned site, pluginId=default', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
test('readVersionsMetadata versioned site with version options', () => {
|
||||
const versionsMetadata = readVersionsMetadata({
|
||||
options: {
|
||||
...defaultOptions,
|
||||
lastVersion: '1.0.0',
|
||||
versions: {
|
||||
current: {
|
||||
path: 'current-path',
|
||||
},
|
||||
'1.0.0': {
|
||||
label: '1.0.0-label',
|
||||
},
|
||||
},
|
||||
},
|
||||
context: defaultContext,
|
||||
});
|
||||
|
||||
expect(versionsMetadata).toEqual([
|
||||
{...vCurrent, versionPath: '/docs/current-path'},
|
||||
{
|
||||
...v101,
|
||||
isLast: false,
|
||||
routePriority: undefined,
|
||||
versionPath: '/docs/1.0.1',
|
||||
},
|
||||
{
|
||||
...v100,
|
||||
isLast: true,
|
||||
routePriority: -1,
|
||||
versionLabel: '1.0.0-label',
|
||||
versionPath: '/docs',
|
||||
},
|
||||
vwithSlugs,
|
||||
]);
|
||||
});
|
||||
|
||||
test('readVersionsMetadata versioned site with disableVersioning', () => {
|
||||
const versionsMetadata = readVersionsMetadata({
|
||||
options: {...defaultOptions, disableVersioning: true},
|
||||
|
|
|
@ -33,8 +33,19 @@ export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id'> = {
|
|||
excludeNextVersionDocs: false,
|
||||
includeCurrentVersion: true,
|
||||
disableVersioning: false,
|
||||
lastVersion: undefined,
|
||||
versions: {},
|
||||
};
|
||||
|
||||
const VersionOptionsSchema = Joi.object({
|
||||
path: Joi.string().allow('').optional(),
|
||||
label: Joi.string().optional(),
|
||||
});
|
||||
|
||||
const VersionsOptionsSchema = Joi.object()
|
||||
.pattern(Joi.string().required(), VersionOptionsSchema)
|
||||
.default(DEFAULT_OPTIONS.versions);
|
||||
|
||||
export const OptionsSchema = Joi.object({
|
||||
path: Joi.string().default(DEFAULT_OPTIONS.path),
|
||||
editUrl: URISchema,
|
||||
|
@ -58,6 +69,8 @@ export const OptionsSchema = Joi.object({
|
|||
DEFAULT_OPTIONS.includeCurrentVersion,
|
||||
),
|
||||
disableVersioning: Joi.bool().default(DEFAULT_OPTIONS.disableVersioning),
|
||||
lastVersion: Joi.string().optional(),
|
||||
versions: VersionsOptionsSchema,
|
||||
});
|
||||
|
||||
// TODO bad validation function types
|
||||
|
|
|
@ -8,14 +8,13 @@
|
|||
/* eslint-disable camelcase */
|
||||
|
||||
declare module '@docusaurus/plugin-content-docs-types' {
|
||||
export type VersionName = string;
|
||||
|
||||
export type PermalinkToSidebar = {
|
||||
[permalink: string]: string;
|
||||
};
|
||||
|
||||
export type PropVersionMetadata = {
|
||||
version: VersionName;
|
||||
version: string;
|
||||
label: string;
|
||||
docsSidebars: PropSidebars;
|
||||
permalinkToSidebar: PermalinkToSidebar;
|
||||
};
|
||||
|
|
|
@ -66,6 +66,7 @@ export function toVersionMetadataProp(
|
|||
): PropVersionMetadata {
|
||||
return {
|
||||
version: loadedVersion.versionName,
|
||||
label: loadedVersion.versionLabel,
|
||||
docsSidebars: toSidebarsProp(loadedVersion),
|
||||
permalinkToSidebar: loadedVersion.permalinkToSidebar,
|
||||
};
|
||||
|
|
|
@ -39,8 +39,19 @@ export type PathOptions = {
|
|||
sidebarPath: string;
|
||||
};
|
||||
|
||||
export type VersionOptions = {
|
||||
path?: string;
|
||||
label?: string;
|
||||
};
|
||||
|
||||
export type VersionsOptions = {
|
||||
lastVersion?: string;
|
||||
versions: Record<string, VersionOptions>;
|
||||
};
|
||||
|
||||
export type PluginOptions = MetadataOptions &
|
||||
PathOptions & {
|
||||
PathOptions &
|
||||
VersionsOptions & {
|
||||
id: string;
|
||||
include: string[];
|
||||
docLayoutComponent: string;
|
||||
|
|
|
@ -7,7 +7,12 @@
|
|||
|
||||
import path from 'path';
|
||||
import fs from 'fs-extra';
|
||||
import {PluginOptions, VersionMetadata} from './types';
|
||||
import {
|
||||
PluginOptions,
|
||||
VersionMetadata,
|
||||
VersionOptions,
|
||||
VersionsOptions,
|
||||
} from './types';
|
||||
import {
|
||||
VERSIONS_JSON_FILE,
|
||||
VERSIONED_DOCS_DIR,
|
||||
|
@ -18,6 +23,7 @@ import {
|
|||
import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants';
|
||||
import {LoadContext} from '@docusaurus/types';
|
||||
import {normalizeUrl} from '@docusaurus/utils';
|
||||
import {difference} from 'lodash';
|
||||
|
||||
// retro-compatibility: no prefix for the default plugin id
|
||||
function addPluginIdPrefix(fileOrDir: string, pluginId: string): string {
|
||||
|
@ -161,7 +167,10 @@ function createVersionMetadata({
|
|||
versionName: string;
|
||||
isLast: boolean;
|
||||
context: Pick<LoadContext, 'siteDir' | 'baseUrl'>;
|
||||
options: Pick<PluginOptions, 'id' | 'path' | 'sidebarPath' | 'routeBasePath'>;
|
||||
options: Pick<
|
||||
PluginOptions,
|
||||
'id' | 'path' | 'sidebarPath' | 'routeBasePath' | 'versions'
|
||||
>;
|
||||
}): VersionMetadata {
|
||||
const {sidebarFilePath, docsDirPath} = getVersionMetadataPaths({
|
||||
versionName,
|
||||
|
@ -169,16 +178,20 @@ function createVersionMetadata({
|
|||
options,
|
||||
});
|
||||
|
||||
// TODO hardcoded for retro-compatibility
|
||||
// TODO Need to make this configurable
|
||||
const versionLabel =
|
||||
// retro-compatible values
|
||||
const defaultVersionLabel =
|
||||
versionName === CURRENT_VERSION_NAME ? 'Next' : versionName;
|
||||
const versionPathPart = isLast
|
||||
const defaultVersionPathPart = isLast
|
||||
? ''
|
||||
: versionName === CURRENT_VERSION_NAME
|
||||
? 'next'
|
||||
: versionName;
|
||||
|
||||
const versionOptions: VersionOptions = options.versions[versionName] ?? {};
|
||||
|
||||
const versionLabel = versionOptions.label ?? defaultVersionLabel;
|
||||
const versionPathPart = versionOptions.path ?? defaultVersionPathPart;
|
||||
|
||||
const versionPath = normalizeUrl([
|
||||
context.baseUrl,
|
||||
options.routeBasePath,
|
||||
|
@ -219,7 +232,7 @@ function checkVersionMetadataPaths({
|
|||
// TODO for retrocompatibility with existing behavior
|
||||
// We should make this configurable
|
||||
// "last version" is not a very good concept nor api surface
|
||||
function getLastVersionName(versionNames: string[]) {
|
||||
function getDefaultLastVersionName(versionNames: string[]) {
|
||||
if (versionNames.length === 1) {
|
||||
return versionNames[0];
|
||||
} else {
|
||||
|
@ -229,6 +242,34 @@ function getLastVersionName(versionNames: string[]) {
|
|||
}
|
||||
}
|
||||
|
||||
function checkVersionsOptions(
|
||||
availableVersionNames: string[],
|
||||
options: VersionsOptions,
|
||||
) {
|
||||
const availableVersionNamesMsg = `Available version names are: ${availableVersionNames.join(
|
||||
', ',
|
||||
)}`;
|
||||
if (
|
||||
options.lastVersion &&
|
||||
!availableVersionNames.includes(options.lastVersion)
|
||||
) {
|
||||
throw new Error(
|
||||
`Docs option lastVersion=${options.lastVersion} is invalid. ${availableVersionNamesMsg}`,
|
||||
);
|
||||
}
|
||||
const unknownVersionNames = difference(
|
||||
Object.keys(options.versions),
|
||||
availableVersionNames,
|
||||
);
|
||||
if (unknownVersionNames.length > 0) {
|
||||
throw new Error(
|
||||
`Docs versions option provided configuration for unknown versions: ${unknownVersionNames.join(
|
||||
',',
|
||||
)}. ${availableVersionNamesMsg}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function readVersionsMetadata({
|
||||
context,
|
||||
options,
|
||||
|
@ -242,10 +283,17 @@ export function readVersionsMetadata({
|
|||
| 'routeBasePath'
|
||||
| 'includeCurrentVersion'
|
||||
| 'disableVersioning'
|
||||
| 'lastVersion'
|
||||
| 'versions'
|
||||
>;
|
||||
}): VersionMetadata[] {
|
||||
const versionNames = readVersionNames(context.siteDir, options);
|
||||
const lastVersionName = getLastVersionName(versionNames);
|
||||
|
||||
checkVersionsOptions(versionNames, options);
|
||||
|
||||
const lastVersionName =
|
||||
options.lastVersion ?? getDefaultLastVersionName(versionNames);
|
||||
|
||||
const versionsMetadata = versionNames.map((versionName) =>
|
||||
createVersionMetadata({
|
||||
versionName,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue