mirror of
https://github.com/facebook/docusaurus.git
synced 2025-05-13 09:07:29 +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-metadata-prop-751.json": "{
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
\\"label\\": \\"Next\\",
|
||||||
\\"docsSidebars\\": {
|
\\"docsSidebars\\": {
|
||||||
\\"docs\\": [
|
\\"docs\\": [
|
||||||
{
|
{
|
||||||
|
@ -613,6 +614,7 @@ Object {
|
||||||
}",
|
}",
|
||||||
"version-1-0-0-metadata-prop-608.json": "{
|
"version-1-0-0-metadata-prop-608.json": "{
|
||||||
\\"version\\": \\"1.0.0\\",
|
\\"version\\": \\"1.0.0\\",
|
||||||
|
\\"label\\": \\"1.0.0\\",
|
||||||
\\"docsSidebars\\": {
|
\\"docsSidebars\\": {
|
||||||
\\"version-1.0.0/community\\": [
|
\\"version-1.0.0/community\\": [
|
||||||
{
|
{
|
||||||
|
@ -628,6 +630,7 @@ Object {
|
||||||
}",
|
}",
|
||||||
"version-current-metadata-prop-751.json": "{
|
"version-current-metadata-prop-751.json": "{
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
\\"label\\": \\"Next\\",
|
||||||
\\"docsSidebars\\": {
|
\\"docsSidebars\\": {
|
||||||
\\"community\\": [
|
\\"community\\": [
|
||||||
{
|
{
|
||||||
|
@ -1069,6 +1072,7 @@ Object {
|
||||||
}",
|
}",
|
||||||
"version-1-0-0-metadata-prop-608.json": "{
|
"version-1-0-0-metadata-prop-608.json": "{
|
||||||
\\"version\\": \\"1.0.0\\",
|
\\"version\\": \\"1.0.0\\",
|
||||||
|
\\"label\\": \\"1.0.0\\",
|
||||||
\\"docsSidebars\\": {
|
\\"docsSidebars\\": {
|
||||||
\\"version-1.0.0/docs\\": [
|
\\"version-1.0.0/docs\\": [
|
||||||
{
|
{
|
||||||
|
@ -1110,6 +1114,7 @@ Object {
|
||||||
}",
|
}",
|
||||||
"version-1-0-1-metadata-prop-e87.json": "{
|
"version-1-0-1-metadata-prop-e87.json": "{
|
||||||
\\"version\\": \\"1.0.1\\",
|
\\"version\\": \\"1.0.1\\",
|
||||||
|
\\"label\\": \\"1.0.1\\",
|
||||||
\\"docsSidebars\\": {
|
\\"docsSidebars\\": {
|
||||||
\\"version-1.0.1/docs\\": [
|
\\"version-1.0.1/docs\\": [
|
||||||
{
|
{
|
||||||
|
@ -1145,6 +1150,7 @@ Object {
|
||||||
}",
|
}",
|
||||||
"version-current-metadata-prop-751.json": "{
|
"version-current-metadata-prop-751.json": "{
|
||||||
\\"version\\": \\"current\\",
|
\\"version\\": \\"current\\",
|
||||||
|
\\"label\\": \\"Next\\",
|
||||||
\\"docsSidebars\\": {
|
\\"docsSidebars\\": {
|
||||||
\\"docs\\": [
|
\\"docs\\": [
|
||||||
{
|
{
|
||||||
|
@ -1180,6 +1186,7 @@ Object {
|
||||||
}",
|
}",
|
||||||
"version-with-slugs-metadata-prop-2bf.json": "{
|
"version-with-slugs-metadata-prop-2bf.json": "{
|
||||||
\\"version\\": \\"withSlugs\\",
|
\\"version\\": \\"withSlugs\\",
|
||||||
|
\\"label\\": \\"withSlugs\\",
|
||||||
\\"docsSidebars\\": {
|
\\"docsSidebars\\": {
|
||||||
\\"version-1.0.1/docs\\": [
|
\\"version-1.0.1/docs\\": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -135,7 +135,8 @@ describe('simple site', () => {
|
||||||
|
|
||||||
test('readVersionDocs', async () => {
|
test('readVersionDocs', async () => {
|
||||||
const docs = await readVersionDocs(currentVersion, options);
|
const docs = await readVersionDocs(currentVersion, options);
|
||||||
expect(docs.map((doc) => doc.source)).toMatchObject([
|
expect(docs.map((doc) => doc.source).sort()).toEqual(
|
||||||
|
[
|
||||||
'hello.md',
|
'hello.md',
|
||||||
'ipsum.md',
|
'ipsum.md',
|
||||||
'lorem.md',
|
'lorem.md',
|
||||||
|
@ -149,7 +150,8 @@ describe('simple site', () => {
|
||||||
'slugs/relativeSlug.md',
|
'slugs/relativeSlug.md',
|
||||||
'slugs/resolvedSlug.md',
|
'slugs/resolvedSlug.md',
|
||||||
'slugs/tryToEscapeSlug.md',
|
'slugs/tryToEscapeSlug.md',
|
||||||
]);
|
].sort(),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('normal docs', async () => {
|
test('normal docs', async () => {
|
||||||
|
|
|
@ -36,6 +36,16 @@ describe('normalizeDocsPluginOptions', () => {
|
||||||
excludeNextVersionDocs: true,
|
excludeNextVersionDocs: true,
|
||||||
includeCurrentVersion: false,
|
includeCurrentVersion: false,
|
||||||
disableVersioning: true,
|
disableVersioning: true,
|
||||||
|
versions: {
|
||||||
|
current: {
|
||||||
|
path: 'next',
|
||||||
|
label: 'next',
|
||||||
|
},
|
||||||
|
version1: {
|
||||||
|
path: 'hello',
|
||||||
|
label: 'world',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const {value, error} = await OptionsSchema.validate(userOptions);
|
const {value, error} = await OptionsSchema.validate(userOptions);
|
||||||
expect(value).toEqual(userOptions);
|
expect(value).toEqual(userOptions);
|
||||||
|
@ -117,4 +127,32 @@ describe('normalizeDocsPluginOptions', () => {
|
||||||
`"\\"remarkPlugins\\" must be an array"`,
|
`"\\"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(() =>
|
expect(() =>
|
||||||
readVersionsMetadata({
|
readVersionsMetadata({
|
||||||
options: {...defaultOptions, disableVersioning: true},
|
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(() =>
|
expect(() =>
|
||||||
readVersionsMetadata({
|
readVersionsMetadata({
|
||||||
options: {...defaultOptions, includeCurrentVersion: false},
|
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', () => {
|
test('readVersionsMetadata versioned site with disableVersioning', () => {
|
||||||
const versionsMetadata = readVersionsMetadata({
|
const versionsMetadata = readVersionsMetadata({
|
||||||
options: {...defaultOptions, disableVersioning: true},
|
options: {...defaultOptions, disableVersioning: true},
|
||||||
|
|
|
@ -33,8 +33,19 @@ export const DEFAULT_OPTIONS: Omit<PluginOptions, 'id'> = {
|
||||||
excludeNextVersionDocs: false,
|
excludeNextVersionDocs: false,
|
||||||
includeCurrentVersion: true,
|
includeCurrentVersion: true,
|
||||||
disableVersioning: false,
|
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({
|
export const OptionsSchema = Joi.object({
|
||||||
path: Joi.string().default(DEFAULT_OPTIONS.path),
|
path: Joi.string().default(DEFAULT_OPTIONS.path),
|
||||||
editUrl: URISchema,
|
editUrl: URISchema,
|
||||||
|
@ -58,6 +69,8 @@ export const OptionsSchema = Joi.object({
|
||||||
DEFAULT_OPTIONS.includeCurrentVersion,
|
DEFAULT_OPTIONS.includeCurrentVersion,
|
||||||
),
|
),
|
||||||
disableVersioning: Joi.bool().default(DEFAULT_OPTIONS.disableVersioning),
|
disableVersioning: Joi.bool().default(DEFAULT_OPTIONS.disableVersioning),
|
||||||
|
lastVersion: Joi.string().optional(),
|
||||||
|
versions: VersionsOptionsSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO bad validation function types
|
// TODO bad validation function types
|
||||||
|
|
|
@ -8,14 +8,13 @@
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
|
|
||||||
declare module '@docusaurus/plugin-content-docs-types' {
|
declare module '@docusaurus/plugin-content-docs-types' {
|
||||||
export type VersionName = string;
|
|
||||||
|
|
||||||
export type PermalinkToSidebar = {
|
export type PermalinkToSidebar = {
|
||||||
[permalink: string]: string;
|
[permalink: string]: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type PropVersionMetadata = {
|
export type PropVersionMetadata = {
|
||||||
version: VersionName;
|
version: string;
|
||||||
|
label: string;
|
||||||
docsSidebars: PropSidebars;
|
docsSidebars: PropSidebars;
|
||||||
permalinkToSidebar: PermalinkToSidebar;
|
permalinkToSidebar: PermalinkToSidebar;
|
||||||
};
|
};
|
||||||
|
|
|
@ -66,6 +66,7 @@ export function toVersionMetadataProp(
|
||||||
): PropVersionMetadata {
|
): PropVersionMetadata {
|
||||||
return {
|
return {
|
||||||
version: loadedVersion.versionName,
|
version: loadedVersion.versionName,
|
||||||
|
label: loadedVersion.versionLabel,
|
||||||
docsSidebars: toSidebarsProp(loadedVersion),
|
docsSidebars: toSidebarsProp(loadedVersion),
|
||||||
permalinkToSidebar: loadedVersion.permalinkToSidebar,
|
permalinkToSidebar: loadedVersion.permalinkToSidebar,
|
||||||
};
|
};
|
||||||
|
|
|
@ -39,8 +39,19 @@ export type PathOptions = {
|
||||||
sidebarPath: string;
|
sidebarPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type VersionOptions = {
|
||||||
|
path?: string;
|
||||||
|
label?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type VersionsOptions = {
|
||||||
|
lastVersion?: string;
|
||||||
|
versions: Record<string, VersionOptions>;
|
||||||
|
};
|
||||||
|
|
||||||
export type PluginOptions = MetadataOptions &
|
export type PluginOptions = MetadataOptions &
|
||||||
PathOptions & {
|
PathOptions &
|
||||||
|
VersionsOptions & {
|
||||||
id: string;
|
id: string;
|
||||||
include: string[];
|
include: string[];
|
||||||
docLayoutComponent: string;
|
docLayoutComponent: string;
|
||||||
|
|
|
@ -7,7 +7,12 @@
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import {PluginOptions, VersionMetadata} from './types';
|
import {
|
||||||
|
PluginOptions,
|
||||||
|
VersionMetadata,
|
||||||
|
VersionOptions,
|
||||||
|
VersionsOptions,
|
||||||
|
} from './types';
|
||||||
import {
|
import {
|
||||||
VERSIONS_JSON_FILE,
|
VERSIONS_JSON_FILE,
|
||||||
VERSIONED_DOCS_DIR,
|
VERSIONED_DOCS_DIR,
|
||||||
|
@ -18,6 +23,7 @@ import {
|
||||||
import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants';
|
import {DEFAULT_PLUGIN_ID} from '@docusaurus/core/lib/constants';
|
||||||
import {LoadContext} from '@docusaurus/types';
|
import {LoadContext} from '@docusaurus/types';
|
||||||
import {normalizeUrl} from '@docusaurus/utils';
|
import {normalizeUrl} from '@docusaurus/utils';
|
||||||
|
import {difference} from 'lodash';
|
||||||
|
|
||||||
// retro-compatibility: no prefix for the default plugin id
|
// retro-compatibility: no prefix for the default plugin id
|
||||||
function addPluginIdPrefix(fileOrDir: string, pluginId: string): string {
|
function addPluginIdPrefix(fileOrDir: string, pluginId: string): string {
|
||||||
|
@ -161,7 +167,10 @@ function createVersionMetadata({
|
||||||
versionName: string;
|
versionName: string;
|
||||||
isLast: boolean;
|
isLast: boolean;
|
||||||
context: Pick<LoadContext, 'siteDir' | 'baseUrl'>;
|
context: Pick<LoadContext, 'siteDir' | 'baseUrl'>;
|
||||||
options: Pick<PluginOptions, 'id' | 'path' | 'sidebarPath' | 'routeBasePath'>;
|
options: Pick<
|
||||||
|
PluginOptions,
|
||||||
|
'id' | 'path' | 'sidebarPath' | 'routeBasePath' | 'versions'
|
||||||
|
>;
|
||||||
}): VersionMetadata {
|
}): VersionMetadata {
|
||||||
const {sidebarFilePath, docsDirPath} = getVersionMetadataPaths({
|
const {sidebarFilePath, docsDirPath} = getVersionMetadataPaths({
|
||||||
versionName,
|
versionName,
|
||||||
|
@ -169,16 +178,20 @@ function createVersionMetadata({
|
||||||
options,
|
options,
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO hardcoded for retro-compatibility
|
// retro-compatible values
|
||||||
// TODO Need to make this configurable
|
const defaultVersionLabel =
|
||||||
const versionLabel =
|
|
||||||
versionName === CURRENT_VERSION_NAME ? 'Next' : versionName;
|
versionName === CURRENT_VERSION_NAME ? 'Next' : versionName;
|
||||||
const versionPathPart = isLast
|
const defaultVersionPathPart = isLast
|
||||||
? ''
|
? ''
|
||||||
: versionName === CURRENT_VERSION_NAME
|
: versionName === CURRENT_VERSION_NAME
|
||||||
? 'next'
|
? 'next'
|
||||||
: versionName;
|
: versionName;
|
||||||
|
|
||||||
|
const versionOptions: VersionOptions = options.versions[versionName] ?? {};
|
||||||
|
|
||||||
|
const versionLabel = versionOptions.label ?? defaultVersionLabel;
|
||||||
|
const versionPathPart = versionOptions.path ?? defaultVersionPathPart;
|
||||||
|
|
||||||
const versionPath = normalizeUrl([
|
const versionPath = normalizeUrl([
|
||||||
context.baseUrl,
|
context.baseUrl,
|
||||||
options.routeBasePath,
|
options.routeBasePath,
|
||||||
|
@ -219,7 +232,7 @@ function checkVersionMetadataPaths({
|
||||||
// TODO for retrocompatibility with existing behavior
|
// TODO for retrocompatibility with existing behavior
|
||||||
// We should make this configurable
|
// We should make this configurable
|
||||||
// "last version" is not a very good concept nor api surface
|
// "last version" is not a very good concept nor api surface
|
||||||
function getLastVersionName(versionNames: string[]) {
|
function getDefaultLastVersionName(versionNames: string[]) {
|
||||||
if (versionNames.length === 1) {
|
if (versionNames.length === 1) {
|
||||||
return versionNames[0];
|
return versionNames[0];
|
||||||
} else {
|
} 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({
|
export function readVersionsMetadata({
|
||||||
context,
|
context,
|
||||||
options,
|
options,
|
||||||
|
@ -242,10 +283,17 @@ export function readVersionsMetadata({
|
||||||
| 'routeBasePath'
|
| 'routeBasePath'
|
||||||
| 'includeCurrentVersion'
|
| 'includeCurrentVersion'
|
||||||
| 'disableVersioning'
|
| 'disableVersioning'
|
||||||
|
| 'lastVersion'
|
||||||
|
| 'versions'
|
||||||
>;
|
>;
|
||||||
}): VersionMetadata[] {
|
}): VersionMetadata[] {
|
||||||
const versionNames = readVersionNames(context.siteDir, options);
|
const versionNames = readVersionNames(context.siteDir, options);
|
||||||
const lastVersionName = getLastVersionName(versionNames);
|
|
||||||
|
checkVersionsOptions(versionNames, options);
|
||||||
|
|
||||||
|
const lastVersionName =
|
||||||
|
options.lastVersion ?? getDefaultLastVersionName(versionNames);
|
||||||
|
|
||||||
const versionsMetadata = versionNames.map((versionName) =>
|
const versionsMetadata = versionNames.map((versionName) =>
|
||||||
createVersionMetadata({
|
createVersionMetadata({
|
||||||
versionName,
|
versionName,
|
||||||
|
|
|
@ -17,6 +17,16 @@ import TOC from '@theme/TOC';
|
||||||
|
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import styles from './styles.module.css';
|
import styles from './styles.module.css';
|
||||||
|
import {useActivePlugin, useActiveVersion} from '@theme/hooks/useDocs';
|
||||||
|
|
||||||
|
// TODO can't we receive the version as props instead?
|
||||||
|
const useDocVersion = () => {
|
||||||
|
const version = useActiveVersion(useActivePlugin().pluginId);
|
||||||
|
if (!version) {
|
||||||
|
throw new Error("unexpected, can't get version data of doc"); // should not happen
|
||||||
|
}
|
||||||
|
return version;
|
||||||
|
};
|
||||||
|
|
||||||
function DocItem(props: Props): JSX.Element {
|
function DocItem(props: Props): JSX.Element {
|
||||||
const {siteConfig = {}} = useDocusaurusContext();
|
const {siteConfig = {}} = useDocusaurusContext();
|
||||||
|
@ -30,7 +40,6 @@ function DocItem(props: Props): JSX.Element {
|
||||||
editUrl,
|
editUrl,
|
||||||
lastUpdatedAt,
|
lastUpdatedAt,
|
||||||
lastUpdatedBy,
|
lastUpdatedBy,
|
||||||
version,
|
|
||||||
} = metadata;
|
} = metadata;
|
||||||
const {
|
const {
|
||||||
frontMatter: {
|
frontMatter: {
|
||||||
|
@ -40,6 +49,7 @@ function DocItem(props: Props): JSX.Element {
|
||||||
hide_table_of_contents: hideTableOfContents,
|
hide_table_of_contents: hideTableOfContents,
|
||||||
},
|
},
|
||||||
} = DocContent;
|
} = DocContent;
|
||||||
|
const version = useDocVersion();
|
||||||
|
|
||||||
const metaTitle = title ? `${title} | ${siteTitle}` : siteTitle;
|
const metaTitle = title ? `${title} | ${siteTitle}` : siteTitle;
|
||||||
const metaImageUrl = useBaseUrl(metaImage, {absolute: true});
|
const metaImageUrl = useBaseUrl(metaImage, {absolute: true});
|
||||||
|
@ -76,7 +86,7 @@ function DocItem(props: Props): JSX.Element {
|
||||||
{version && (
|
{version && (
|
||||||
<div>
|
<div>
|
||||||
<span className="badge badge--secondary">
|
<span className="badge badge--secondary">
|
||||||
Version: {version}
|
Version: {version.label}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -43,8 +43,6 @@ function DocVersionSuggestions(): JSX.Element {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeVersionName = activeVersion.name;
|
|
||||||
|
|
||||||
// try to link to same doc in latest version (not always possible)
|
// try to link to same doc in latest version (not always possible)
|
||||||
// fallback to main doc of latest version
|
// fallback to main doc of latest version
|
||||||
const suggestedDoc =
|
const suggestedDoc =
|
||||||
|
@ -54,15 +52,15 @@ function DocVersionSuggestions(): JSX.Element {
|
||||||
<div className="alert alert--warning margin-bottom--md" role="alert">
|
<div className="alert alert--warning margin-bottom--md" role="alert">
|
||||||
{
|
{
|
||||||
// TODO need refactoring
|
// TODO need refactoring
|
||||||
activeVersionName === 'current' ? (
|
activeVersion.name === 'current' ? (
|
||||||
<div>
|
<div>
|
||||||
This is unreleased documentation for {siteTitle}{' '}
|
This is unreleased documentation for {siteTitle}{' '}
|
||||||
<strong>{activeVersionName}</strong> version.
|
<strong>{activeVersion.label}</strong> version.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
This is documentation for {siteTitle}{' '}
|
This is documentation for {siteTitle}{' '}
|
||||||
<strong>v{activeVersionName}</strong>, which is no longer actively
|
<strong>{activeVersion.label}</strong>, which is no longer actively
|
||||||
maintained.
|
maintained.
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -72,7 +70,7 @@ function DocVersionSuggestions(): JSX.Element {
|
||||||
<strong>
|
<strong>
|
||||||
<Link to={suggestedDoc.path}>latest version</Link>
|
<Link to={suggestedDoc.path}>latest version</Link>
|
||||||
</strong>{' '}
|
</strong>{' '}
|
||||||
({latestVersionSuggestion.name}).
|
({latestVersionSuggestion.label}).
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -335,6 +335,33 @@ module.exports = {
|
||||||
* in `/docs/next` directory, only versioned docs.
|
* in `/docs/next` directory, only versioned docs.
|
||||||
*/
|
*/
|
||||||
excludeNextVersionDocs: false,
|
excludeNextVersionDocs: false,
|
||||||
|
/**
|
||||||
|
* The last version is the one we navigate to in priority on versioned sites
|
||||||
|
* It is the one displayed by default in docs navbar items
|
||||||
|
* By default, the last version is the first one to appear in versions.json
|
||||||
|
* By default, the last version is at the "root" (docs have path=/docs/myDoc)
|
||||||
|
* Note: it is possible to configure the path and label of the last version
|
||||||
|
* Tip: using lastVersion: 'current' make sense in many cases
|
||||||
|
*/
|
||||||
|
lastVersion: undefined,
|
||||||
|
/**
|
||||||
|
* The docusaurus versioning defaults don't make sense for all projects
|
||||||
|
* This gives the ability customize the label and path of each version
|
||||||
|
* You may not like that default versin
|
||||||
|
*/
|
||||||
|
versions: {
|
||||||
|
/*
|
||||||
|
Example configuration:
|
||||||
|
current: {
|
||||||
|
label: 'Android SDK v2.0.0 (WIP)',
|
||||||
|
path: 'android-2.0.0',
|
||||||
|
},
|
||||||
|
'1.0.0': {
|
||||||
|
label: 'Android SDK v1.0.0',
|
||||||
|
path: 'android-1.0.0',
|
||||||
|
},
|
||||||
|
*/
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
|
@ -64,9 +64,9 @@ When tagging a new version, the document versioning mechanism will:
|
||||||
- Create a versioned sidebars file based from your current [sidebar](docs.md#sidebar) configuration (if it exists) - saved as `versioned_sidebars/version-<version>-sidebars.json`.
|
- Create a versioned sidebars file based from your current [sidebar](docs.md#sidebar) configuration (if it exists) - saved as `versioned_sidebars/version-<version>-sidebars.json`.
|
||||||
- Append the new version number to `versions.json`.
|
- Append the new version number to `versions.json`.
|
||||||
|
|
||||||
## Files
|
## Docs
|
||||||
|
|
||||||
### Creating new files
|
### Creating new docs
|
||||||
|
|
||||||
1. Place the new file into the corresponding version folder.
|
1. Place the new file into the corresponding version folder.
|
||||||
1. Include the reference for the new file into the corresponding sidebar file, according to version number.
|
1. Include the reference for the new file into the corresponding sidebar file, according to version number.
|
||||||
|
@ -91,7 +91,7 @@ versioned_docs/version-1.0.0/new.md
|
||||||
versioned_sidebars/version-1.0.0-sidebars.json
|
versioned_sidebars/version-1.0.0-sidebars.json
|
||||||
```
|
```
|
||||||
|
|
||||||
### Linking files
|
### Linking docs
|
||||||
|
|
||||||
- Remember to include the `.md` extension.
|
- Remember to include the `.md` extension.
|
||||||
- Files will be linked to correct corresponding version.
|
- Files will be linked to correct corresponding version.
|
||||||
|
@ -138,6 +138,35 @@ Example:
|
||||||
|
|
||||||
## Recommended practices
|
## Recommended practices
|
||||||
|
|
||||||
|
### Figure out the behavior for the "current" version
|
||||||
|
|
||||||
|
The "current" version is the version name for the `./docs` folder.
|
||||||
|
|
||||||
|
There are different ways to manage versioning, but two very common patterns are:
|
||||||
|
|
||||||
|
- You release v1, and start immediately working on v2 (including its docs)
|
||||||
|
- You release v1, and will maintain it for some time before thinking about v2.
|
||||||
|
|
||||||
|
Docusaurus defaults work great for the first usecase.
|
||||||
|
|
||||||
|
**For the 2nd usecase**: if you release v1 and don't plan to work on v2 anytime soon, instead of versioning v1 and having to maintain the docs in 2 folders (`./docs` + `./versioned_docs/version-1.0.0`), you may consider using the following configuration instead:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"lastVersion": "current",
|
||||||
|
"versions": {
|
||||||
|
"current": {
|
||||||
|
"label": "1.0.0",
|
||||||
|
"path": "1.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The docs in `./docs` will be served at `/docs/1.0.0` instead of `/docs/next`, and `1.0.0` will become the default version we link to in the navbar dropdown, and you will only need to maintain a single `./docs` folder.
|
||||||
|
|
||||||
|
See [docs plugin configuration](using-plugins#docusaurusplugin-content-docs) for more details.
|
||||||
|
|
||||||
### Version your documentation only when needed
|
### Version your documentation only when needed
|
||||||
|
|
||||||
For example, you are building a documentation for your npm package `foo` and you are currently in version 1.0.0. You then release a patch version for a minor bug fix and it's now 1.0.1.
|
For example, you are building a documentation for your npm package `foo` and you are currently in version 1.0.0. You then release a patch version for a minor bug fix and it's now 1.0.1.
|
||||||
|
@ -156,3 +185,23 @@ Don't use relative paths import within the docs. Because when we cut a version t
|
||||||
- import Foo from '../src/components/Foo';
|
- import Foo from '../src/components/Foo';
|
||||||
+ import Foo from '@site/src/components/Foo';
|
+ import Foo from '@site/src/components/Foo';
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Global or versioned colocated assets
|
||||||
|
|
||||||
|
You should decide if assets like images and files are per version or shared between versions
|
||||||
|
|
||||||
|
If your assets should be versioned, put them in the docs version, and use relative paths:
|
||||||
|
|
||||||
|
```md
|
||||||
|

|
||||||
|
|
||||||
|
[dowload this file](./file.pdf)
|
||||||
|
```
|
||||||
|
|
||||||
|
If your assets are global, put them in `/static` and use absolute paths:
|
||||||
|
|
||||||
|
```md
|
||||||
|

|
||||||
|
|
||||||
|
[dowload this file](/file.pdf)
|
||||||
|
```
|
||||||
|
|
|
@ -14,13 +14,15 @@ const allDocHomesPaths = [
|
||||||
...versions.slice(1).map((version) => `/docs/${version}/`),
|
...versions.slice(1).map((version) => `/docs/${version}/`),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const isDev = process.env.NODE_ENV === 'development';
|
||||||
|
|
||||||
|
const isDeployPreview =
|
||||||
|
process.env.NETLIFY && process.env.CONTEXT === 'deploy-preview';
|
||||||
|
|
||||||
const baseUrl = process.env.BASE_URL || '/';
|
const baseUrl = process.env.BASE_URL || '/';
|
||||||
const isBootstrapPreset = process.env.DOCUSAURUS_PRESET === 'bootstrap';
|
const isBootstrapPreset = process.env.DOCUSAURUS_PRESET === 'bootstrap';
|
||||||
const isVersioningDisabled = !!process.env.DISABLE_VERSIONING;
|
|
||||||
|
|
||||||
if (isBootstrapPreset) {
|
const isVersioningDisabled = !!process.env.DISABLE_VERSIONING;
|
||||||
console.log('Will use bootstrap preset!');
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
title: 'Docusaurus',
|
title: 'Docusaurus',
|
||||||
|
@ -175,6 +177,16 @@ module.exports = {
|
||||||
showLastUpdateTime: true,
|
showLastUpdateTime: true,
|
||||||
remarkPlugins: [require('./src/plugins/remark-npm2yarn')],
|
remarkPlugins: [require('./src/plugins/remark-npm2yarn')],
|
||||||
disableVersioning: isVersioningDisabled,
|
disableVersioning: isVersioningDisabled,
|
||||||
|
lastVersion: isDev || isDeployPreview ? 'current' : undefined,
|
||||||
|
versions: {
|
||||||
|
current: {
|
||||||
|
// path: isDev || isDeployPreview ? '' : 'next',
|
||||||
|
label:
|
||||||
|
isDev || isDeployPreview
|
||||||
|
? `Next (${isDeployPreview ? 'deploy preview' : 'dev'})`
|
||||||
|
: 'Next',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
blog: {
|
blog: {
|
||||||
// routeBasePath: '/',
|
// routeBasePath: '/',
|
||||||
|
|
|
@ -6,20 +6,21 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import Layout from '@theme/Layout';
|
|
||||||
|
|
||||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
||||||
import Link from '@docusaurus/Link';
|
import Link from '@docusaurus/Link';
|
||||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
import Layout from '@theme/Layout';
|
||||||
|
|
||||||
import versions from '../../versions.json';
|
import {useVersions, useLatestVersion} from '@theme/hooks/useDocs';
|
||||||
|
|
||||||
function Version() {
|
function Version() {
|
||||||
const context = useDocusaurusContext();
|
const {siteConfig} = useDocusaurusContext();
|
||||||
const {siteConfig = {}} = context;
|
const versions = useVersions();
|
||||||
const latestVersion = versions[0];
|
const latestVersion = useLatestVersion();
|
||||||
const pastVersions = versions.filter((version) => version !== latestVersion);
|
const currentVersion = versions.find((version) => version.name === 'current');
|
||||||
|
const pastVersions = versions.filter(
|
||||||
|
(version) => version !== latestVersion && version.name !== 'current',
|
||||||
|
);
|
||||||
|
|
||||||
const repoUrl = `https://github.com/${siteConfig.organizationName}/${siteConfig.projectName}`;
|
const repoUrl = `https://github.com/${siteConfig.organizationName}/${siteConfig.projectName}`;
|
||||||
return (
|
return (
|
||||||
<Layout
|
<Layout
|
||||||
|
@ -34,12 +35,12 @@ function Version() {
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<th>{latestVersion}</th>
|
<th>{latestVersion.label}</th>
|
||||||
<td>
|
<td>
|
||||||
<Link to={useBaseUrl('/docs')}>Documentation</Link>
|
<Link to={latestVersion.path}>Documentation</Link>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href={`${repoUrl}/releases/tag/v${latestVersion}`}>
|
<a href={`${repoUrl}/releases/tag/v${latestVersion.name}`}>
|
||||||
Release Notes
|
Release Notes
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
@ -47,6 +48,7 @@ function Version() {
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
{currentVersion !== latestVersion && (
|
||||||
<div className="margin-bottom--lg">
|
<div className="margin-bottom--lg">
|
||||||
<h3 id="next">Next version (Unreleased)</h3>
|
<h3 id="next">Next version (Unreleased)</h3>
|
||||||
<p>Here you can find the documentation for unreleased version.</p>
|
<p>Here you can find the documentation for unreleased version.</p>
|
||||||
|
@ -55,7 +57,7 @@ function Version() {
|
||||||
<tr>
|
<tr>
|
||||||
<th>master</th>
|
<th>master</th>
|
||||||
<td>
|
<td>
|
||||||
<Link to={useBaseUrl('/docs/next')}>Documentation</Link>
|
<Link to={currentVersion.path}>Documentation</Link>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href={repoUrl}>Source Code</a>
|
<a href={repoUrl}>Source Code</a>
|
||||||
|
@ -64,6 +66,7 @@ function Version() {
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
{pastVersions.length > 0 && (
|
{pastVersions.length > 0 && (
|
||||||
<div className="margin-bottom--lg">
|
<div className="margin-bottom--lg">
|
||||||
<h3 id="archive">Past Versions</h3>
|
<h3 id="archive">Past Versions</h3>
|
||||||
|
@ -74,15 +77,13 @@ function Version() {
|
||||||
<table>
|
<table>
|
||||||
<tbody>
|
<tbody>
|
||||||
{pastVersions.map((version) => (
|
{pastVersions.map((version) => (
|
||||||
<tr key={version}>
|
<tr key={version.name}>
|
||||||
<th>{version}</th>
|
<th>{version.label}</th>
|
||||||
<td>
|
<td>
|
||||||
<Link to={useBaseUrl(`/docs/${version}`)}>
|
<Link to={version.path}>Documentation</Link>
|
||||||
Documentation
|
|
||||||
</Link>
|
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href={`${repoUrl}/releases/tag/v${version}`}>
|
<a href={`${repoUrl}/releases/tag/v${version.name}`}>
|
||||||
Release Notes
|
Release Notes
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue