2020-08-17 11:50:22 -04:00
/ * *
* 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 path from 'path' ;
import fs from 'fs-extra' ;
2020-08-28 12:37:49 -04:00
import {
PluginOptions ,
VersionMetadata ,
VersionOptions ,
VersionsOptions ,
} from './types' ;
2020-08-17 11:50:22 -04:00
import {
VERSIONS_JSON_FILE ,
VERSIONED_DOCS_DIR ,
VERSIONED_SIDEBARS_DIR ,
CURRENT_VERSION_NAME ,
} from './constants' ;
import { DEFAULT_PLUGIN_ID } from '@docusaurus/core/lib/constants' ;
import { LoadContext } from '@docusaurus/types' ;
2020-12-28 04:25:47 -05:00
import { getPluginI18nPath , normalizeUrl , posixPath } from '@docusaurus/utils' ;
2020-08-28 12:37:49 -04:00
import { difference } from 'lodash' ;
2020-09-01 10:31:33 -04:00
import chalk from 'chalk' ;
2020-08-17 11:50:22 -04:00
// retro-compatibility: no prefix for the default plugin id
function addPluginIdPrefix ( fileOrDir : string , pluginId : string ) : string {
if ( pluginId === DEFAULT_PLUGIN_ID ) {
return fileOrDir ;
} else {
return ` ${ pluginId } _ ${ fileOrDir } ` ;
}
}
export function getVersionedDocsDirPath (
siteDir : string ,
pluginId : string ,
) : string {
return path . join ( siteDir , addPluginIdPrefix ( VERSIONED_DOCS_DIR , pluginId ) ) ;
}
export function getVersionedSidebarsDirPath (
siteDir : string ,
pluginId : string ,
) : string {
return path . join (
siteDir ,
addPluginIdPrefix ( VERSIONED_SIDEBARS_DIR , pluginId ) ,
) ;
}
export function getVersionsFilePath ( siteDir : string , pluginId : string ) : string {
return path . join ( siteDir , addPluginIdPrefix ( VERSIONS_JSON_FILE , pluginId ) ) ;
}
function ensureValidVersionString ( version : unknown ) : asserts version is string {
if ( typeof version !== 'string' ) {
throw new Error (
` versions should be strings. Found type=[ ${ typeof version } ] for version=[ ${ version } ] ` ,
) ;
}
// Should we forbid versions with special chars like / ?
if ( version . trim ( ) . length === 0 ) {
throw new Error ( ` Invalid version=[ ${ version } ] ` ) ;
}
}
function ensureValidVersionArray (
versionArray : unknown ,
) : asserts versionArray is string [ ] {
if ( ! ( versionArray instanceof Array ) ) {
throw new Error (
` The versions file should contain an array of versions! Found content= ${ JSON . stringify (
versionArray ,
) } ` ,
) ;
}
versionArray . forEach ( ensureValidVersionString ) ;
}
// TODO not easy to make async due to many deps
function readVersionsFile ( siteDir : string , pluginId : string ) : string [ ] | null {
const versionsFilePath = getVersionsFilePath ( siteDir , pluginId ) ;
if ( fs . existsSync ( versionsFilePath ) ) {
const content = JSON . parse ( fs . readFileSync ( versionsFilePath , 'utf8' ) ) ;
ensureValidVersionArray ( content ) ;
return content ;
} else {
return null ;
}
}
// TODO not easy to make async due to many deps
function readVersionNames (
siteDir : string ,
options : Pick <
PluginOptions ,
'id' | 'disableVersioning' | 'includeCurrentVersion'
> ,
) : string [ ] {
const versionFileContent = readVersionsFile ( siteDir , options . id ) ;
if ( ! versionFileContent && options . disableVersioning ) {
throw new Error (
` Docs: using disableVersioning= ${ options . disableVersioning } option on a non-versioned site does not make sense ` ,
) ;
}
const versions = options . disableVersioning ? [ ] : versionFileContent ? ? [ ] ;
// We add the current version at the beginning, unless
// - user don't want to
// - it's been explicitly added to versions.json
if (
options . includeCurrentVersion &&
! versions . includes ( CURRENT_VERSION_NAME )
) {
versions . unshift ( CURRENT_VERSION_NAME ) ;
}
if ( versions . length === 0 ) {
throw new Error (
` It is not possible to use docs without any version. Please check the configuration of these options: includeCurrentVersion= ${ options . includeCurrentVersion } disableVersioning= ${ options . disableVersioning } ` ,
) ;
}
return versions ;
}
2020-12-28 04:25:47 -05:00
function getDocsDirPathLocalized ( {
siteDir ,
locale ,
pluginId ,
versionName ,
} : {
siteDir : string ;
locale : string ;
pluginId : string ;
versionName : string ;
} ) {
return getPluginI18nPath ( {
siteDir ,
locale ,
pluginName : 'docusaurus-plugin-content-docs' ,
pluginId ,
subPaths : [
versionName === CURRENT_VERSION_NAME
? CURRENT_VERSION_NAME
: ` version- ${ versionName } ` ,
] ,
} ) ;
}
2020-08-17 11:50:22 -04:00
function getVersionMetadataPaths ( {
versionName ,
context ,
options ,
} : {
versionName : string ;
2020-11-26 06:16:46 -05:00
context : Pick < LoadContext , ' siteDir ' | ' i18n ' > ;
2020-08-17 11:50:22 -04:00
options : Pick < PluginOptions , ' id ' | ' path ' | ' sidebarPath ' > ;
2020-11-26 06:16:46 -05:00
} ) : Pick <
VersionMetadata ,
'docsDirPath' | 'docsDirPathLocalized' | 'sidebarFilePath'
> {
2020-08-17 11:50:22 -04:00
const isCurrentVersion = versionName === CURRENT_VERSION_NAME ;
const docsDirPath = isCurrentVersion
? path . resolve ( context . siteDir , options . path )
: path . join (
getVersionedDocsDirPath ( context . siteDir , options . id ) ,
` version- ${ versionName } ` ,
) ;
2020-12-28 04:25:47 -05:00
const docsDirPathLocalized = getDocsDirPathLocalized ( {
2020-11-26 06:16:46 -05:00
siteDir : context.siteDir ,
locale : context.i18n.currentLocale ,
pluginId : options.id ,
2020-12-28 04:25:47 -05:00
versionName ,
2020-11-26 06:16:46 -05:00
} ) ;
2020-08-17 11:50:22 -04:00
const sidebarFilePath = isCurrentVersion
? path . resolve ( context . siteDir , options . sidebarPath )
: path . join (
getVersionedSidebarsDirPath ( context . siteDir , options . id ) ,
` version- ${ versionName } -sidebars.json ` ,
) ;
2020-11-26 06:16:46 -05:00
return { docsDirPath , docsDirPathLocalized , sidebarFilePath } ;
2020-08-17 11:50:22 -04:00
}
2020-12-28 04:25:47 -05:00
function getVersionEditUrls ( {
docsDirPath ,
docsDirPathLocalized ,
context : { siteDir , i18n } ,
options : { id , path : currentVersionPath , editUrl , editCurrentVersion } ,
} : {
docsDirPath : string ;
docsDirPathLocalized : string ;
context : Pick < LoadContext , ' siteDir ' | ' i18n ' > ;
options : Pick <
PluginOptions ,
'id' | 'path' | 'editUrl' | 'editCurrentVersion'
> ;
} ) : { versionEditUrl : string ; versionEditUrlLocalized : string } | undefined {
if ( ! editUrl ) {
return undefined ;
}
2021-01-29 09:35:25 -05:00
// if the user is using the functional form of editUrl,
// he has total freedom and we can't compute a "version edit url"
if ( typeof editUrl === 'function' ) {
return undefined ;
}
2020-12-28 04:25:47 -05:00
const editDirPath = editCurrentVersion ? currentVersionPath : docsDirPath ;
const editDirPathLocalized = editCurrentVersion
? getDocsDirPathLocalized ( {
siteDir ,
locale : i18n.currentLocale ,
versionName : CURRENT_VERSION_NAME ,
pluginId : id ,
} )
: docsDirPathLocalized ;
const versionPathSegment = posixPath (
path . relative ( siteDir , path . resolve ( siteDir , editDirPath ) ) ,
) ;
const versionPathSegmentLocalized = posixPath (
path . relative ( siteDir , path . resolve ( siteDir , editDirPathLocalized ) ) ,
) ;
const versionEditUrl = normalizeUrl ( [ editUrl , versionPathSegment ] ) ;
const versionEditUrlLocalized = normalizeUrl ( [
editUrl ,
versionPathSegmentLocalized ,
] ) ;
return {
versionEditUrl ,
versionEditUrlLocalized ,
} ;
}
2020-08-17 11:50:22 -04:00
function createVersionMetadata ( {
versionName ,
isLast ,
context ,
options ,
} : {
versionName : string ;
isLast : boolean ;
2020-11-26 06:16:46 -05:00
context : Pick < LoadContext , ' siteDir ' | ' baseUrl ' | ' i18n ' > ;
2020-08-28 12:37:49 -04:00
options : Pick <
PluginOptions ,
2020-12-28 04:25:47 -05:00
| 'id'
| 'path'
| 'sidebarPath'
| 'routeBasePath'
| 'versions'
| 'editUrl'
| 'editCurrentVersion'
2020-08-28 12:37:49 -04:00
> ;
2020-08-17 11:50:22 -04:00
} ) : VersionMetadata {
2020-11-26 06:16:46 -05:00
const {
sidebarFilePath ,
docsDirPath ,
docsDirPathLocalized ,
} = getVersionMetadataPaths ( {
2020-08-17 11:50:22 -04:00
versionName ,
context ,
options ,
} ) ;
2020-08-28 12:37:49 -04:00
// retro-compatible values
const defaultVersionLabel =
2020-08-17 11:50:22 -04:00
versionName === CURRENT_VERSION_NAME ? 'Next' : versionName ;
2020-08-28 12:37:49 -04:00
const defaultVersionPathPart = isLast
2020-08-17 11:50:22 -04:00
? ''
: versionName === CURRENT_VERSION_NAME
? 'next'
: versionName ;
2020-08-28 12:37:49 -04:00
const versionOptions : VersionOptions = options . versions [ versionName ] ? ? { } ;
const versionLabel = versionOptions . label ? ? defaultVersionLabel ;
const versionPathPart = versionOptions . path ? ? defaultVersionPathPart ;
2020-08-17 11:50:22 -04:00
const versionPath = normalizeUrl ( [
context . baseUrl ,
options . routeBasePath ,
versionPathPart ,
] ) ;
2020-12-28 04:25:47 -05:00
const versionEditUrls = getVersionEditUrls ( {
docsDirPath ,
docsDirPathLocalized ,
context ,
options ,
} ) ;
2020-08-17 11:50:22 -04:00
// Because /docs/:route` should always be after `/docs/versionName/:route`.
const routePriority = versionPathPart === '' ? - 1 : undefined ;
return {
versionName ,
versionLabel ,
versionPath ,
2020-12-28 04:25:47 -05:00
versionEditUrl : versionEditUrls?.versionEditUrl ,
versionEditUrlLocalized : versionEditUrls?.versionEditUrlLocalized ,
2020-08-17 11:50:22 -04:00
isLast ,
routePriority ,
sidebarFilePath ,
docsDirPath ,
2020-11-26 06:16:46 -05:00
docsDirPathLocalized ,
2020-08-17 11:50:22 -04:00
} ;
}
function checkVersionMetadataPaths ( {
2020-12-28 13:50:12 -05:00
versionMetadata ,
context ,
} : {
versionMetadata : VersionMetadata ;
context : Pick < LoadContext , ' siteDir ' > ;
} ) {
const { versionName , docsDirPath , sidebarFilePath } = versionMetadata ;
const { siteDir } = context ;
2020-08-17 11:50:22 -04:00
if ( ! fs . existsSync ( docsDirPath ) ) {
throw new Error (
2020-12-28 13:50:12 -05:00
` The docs folder does not exist for version [ ${ versionName } ]. A docs folder is expected to be found at ${ path . relative (
siteDir ,
docsDirPath ,
) } ` ,
2020-08-17 11:50:22 -04:00
) ;
}
2020-09-01 10:31:33 -04:00
// See https://github.com/facebook/docusaurus/issues/3366
2020-08-17 11:50:22 -04:00
if ( ! fs . existsSync ( sidebarFilePath ) ) {
2020-09-01 10:31:33 -04:00
console . log (
chalk . yellow (
` The sidebar file of docs version [ ${ versionName } ] does not exist. It is optional, but should rather be provided at ${ sidebarFilePath } ` ,
) ,
2020-08-17 11:50:22 -04:00
) ;
}
}
// TODO for retrocompatibility with existing behavior
// We should make this configurable
// "last version" is not a very good concept nor api surface
2020-08-28 12:37:49 -04:00
function getDefaultLastVersionName ( versionNames : string [ ] ) {
2020-08-17 11:50:22 -04:00
if ( versionNames . length === 1 ) {
return versionNames [ 0 ] ;
} else {
return versionNames . filter (
( versionName ) = > versionName !== CURRENT_VERSION_NAME ,
) [ 0 ] ;
}
}
2020-08-28 12:37:49 -04:00
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 } ` ,
) ;
}
2020-08-31 11:08:24 -04:00
const unknownVersionConfigNames = difference (
2020-08-28 12:37:49 -04:00
Object . keys ( options . versions ) ,
availableVersionNames ,
) ;
2020-08-31 11:08:24 -04:00
if ( unknownVersionConfigNames . length > 0 ) {
2020-08-28 12:37:49 -04:00
throw new Error (
2020-08-31 11:08:24 -04:00
` Bad docs options.versions: unknown versions found: ${ unknownVersionConfigNames . join (
2020-08-28 12:37:49 -04:00
',' ,
) } . $ { availableVersionNamesMsg } ` ,
) ;
}
2020-08-31 11:08:24 -04:00
if ( options . onlyIncludeVersions ) {
if ( options . onlyIncludeVersions . length === 0 ) {
throw new Error (
` Bad docs options.onlyIncludeVersions: an empty array is not allowed, at least one version is needed ` ,
) ;
}
const unknownOnlyIncludeVersionNames = difference (
options . onlyIncludeVersions ,
availableVersionNames ,
) ;
if ( unknownOnlyIncludeVersionNames . length > 0 ) {
throw new Error (
` Bad docs options.onlyIncludeVersions: unknown versions found: ${ unknownOnlyIncludeVersionNames . join (
',' ,
) } . $ { availableVersionNamesMsg } ` ,
) ;
}
if (
options . lastVersion &&
! options . onlyIncludeVersions . includes ( options . lastVersion )
) {
throw new Error (
` Bad docs options.lastVersion: if you use both the onlyIncludeVersions and lastVersion options, then lastVersion must be present in the provided onlyIncludeVersions array ` ,
) ;
}
}
}
// Filter versions according to provided options
// Note: we preserve the order in which versions are provided
// the order of the onlyIncludeVersions array does not matter
function filterVersions (
versionNamesUnfiltered : string [ ] ,
options : Pick < PluginOptions , ' onlyIncludeVersions ' > ,
) {
if ( options . onlyIncludeVersions ) {
return versionNamesUnfiltered . filter ( ( name ) = >
options . onlyIncludeVersions ! . includes ( name ) ,
) ;
} else {
return versionNamesUnfiltered ;
}
2020-08-28 12:37:49 -04:00
}
2020-08-17 11:50:22 -04:00
export function readVersionsMetadata ( {
context ,
options ,
} : {
2020-11-26 06:16:46 -05:00
context : Pick < LoadContext , ' siteDir ' | ' baseUrl ' | ' i18n ' > ;
2020-08-17 11:50:22 -04:00
options : Pick <
PluginOptions ,
| 'id'
| 'path'
| 'sidebarPath'
| 'routeBasePath'
| 'includeCurrentVersion'
| 'disableVersioning'
2020-08-28 12:37:49 -04:00
| 'lastVersion'
| 'versions'
2020-08-31 11:08:24 -04:00
| 'onlyIncludeVersions'
2020-12-28 04:25:47 -05:00
| 'editUrl'
| 'editCurrentVersion'
2020-08-17 11:50:22 -04:00
> ;
} ) : VersionMetadata [ ] {
2020-08-31 11:08:24 -04:00
const versionNamesUnfiltered = readVersionNames ( context . siteDir , options ) ;
checkVersionsOptions ( versionNamesUnfiltered , options ) ;
2020-08-28 12:37:49 -04:00
2020-08-31 11:08:24 -04:00
const versionNames = filterVersions ( versionNamesUnfiltered , options ) ;
2020-08-28 12:37:49 -04:00
const lastVersionName =
options . lastVersion ? ? getDefaultLastVersionName ( versionNames ) ;
2020-08-17 11:50:22 -04:00
const versionsMetadata = versionNames . map ( ( versionName ) = >
createVersionMetadata ( {
versionName ,
isLast : versionName === lastVersionName ,
context ,
options ,
} ) ,
) ;
2020-12-28 13:50:12 -05:00
versionsMetadata . forEach ( ( versionMetadata ) = >
checkVersionMetadataPaths ( { versionMetadata , context } ) ,
) ;
2020-08-17 11:50:22 -04:00
return versionsMetadata ;
}
2020-11-26 06:16:46 -05:00
// order matter!
// Read in priority the localized path, then the unlocalized one
// We want the localized doc to "override" the unlocalized one
export function getDocsDirPaths (
versionMetadata : Pick <
VersionMetadata ,
'docsDirPath' | 'docsDirPathLocalized'
> ,
) : [ string , string ] {
return [ versionMetadata . docsDirPathLocalized , versionMetadata . docsDirPath ] ;
}