2017-08-03 13:25:01 -04:00
/ * *
* Copyright ( c ) 2017 - present , Facebook , Inc .
*
2017-10-05 14:14:49 -04:00
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree .
2017-08-03 13:25:01 -04:00
* /
const CWD = process . cwd ( ) ;
const glob = require ( "glob" ) ;
const fs = require ( "fs" ) ;
const path = require ( "path" ) ;
const diff = require ( "diff" ) ;
const assert = require ( "assert" ) ;
2017-08-03 18:58:56 -04:00
2017-08-03 13:25:01 -04:00
const siteConfig = require ( CWD + "/siteConfig.js" ) ;
const ENABLE _TRANSLATION = fs . existsSync ( CWD + "/languages.js" ) ;
2017-08-08 14:44:30 -04:00
let versions ;
if ( fs . existsSync ( CWD + "/versions.json" ) ) {
2017-08-09 19:17:39 -04:00
versions = require ( CWD + "/versions.json" ) ;
2017-08-08 14:44:30 -04:00
} else {
versions = [ ] ;
}
2017-08-03 13:25:01 -04:00
let languages ;
if ( fs . existsSync ( CWD + "/languages.js" ) ) {
languages = require ( CWD + "/languages.js" ) ;
} else {
languages = [
{
enabled : true ,
name : "English" ,
tag : "en"
}
] ;
}
/*****************************************************************/
// included to prevent cyclical dependency with readMetadata.js
function splitHeader ( content ) {
const lines = content . split ( "\n" ) ;
let i = 1 ;
for ( ; i < lines . length - 1 ; ++ i ) {
if ( lines [ i ] === "---" ) {
break ;
}
}
return {
header : lines . slice ( 1 , i + 1 ) . join ( "\n" ) ,
content : lines . slice ( i + 1 ) . join ( "\n" )
} ;
}
// Extract markdown metadata header
function extractMetadata ( content ) {
const metadata = { } ;
const both = splitHeader ( content ) ;
2017-08-10 17:51:34 -04:00
// if no content returned, then that means there was no header, and both.header is the content
if ( ! both . content ) {
return { metadata , rawContent : both . header } ;
}
2017-08-03 13:25:01 -04:00
const lines = both . header . split ( "\n" ) ;
for ( let i = 0 ; i < lines . length - 1 ; ++ i ) {
const keyvalue = lines [ i ] . split ( ":" ) ;
const key = keyvalue [ 0 ] . trim ( ) ;
let value = keyvalue . slice ( 1 ) . join ( ":" ) . trim ( ) ;
try {
value = JSON . parse ( value ) ;
} catch ( e ) { }
metadata [ key ] = value ;
}
return { metadata , rawContent : both . content } ;
}
/*****************************************************************/
2017-08-03 18:58:56 -04:00
const versionFolder = CWD + "/versioned_docs/" ;
2017-08-03 13:25:01 -04:00
2017-08-03 14:14:56 -04:00
// available stores doc ids of documents that are available for
// each version
2017-08-03 13:25:01 -04:00
const available = { } ;
2017-08-03 14:14:56 -04:00
// versionFiles is used to keep track of what file to use with a
// given version/id of a document
2017-08-03 13:25:01 -04:00
const versionFiles = { } ;
2017-08-08 14:44:30 -04:00
let files = glob . sync ( versionFolder + "**" ) ;
2017-08-03 13:25:01 -04:00
files . forEach ( file => {
const ext = path . extname ( file ) ;
if ( ext !== ".md" && ext !== ".markdown" ) {
return ;
}
const res = extractMetadata ( fs . readFileSync ( file , "utf8" ) ) ;
const metadata = res . metadata ;
2017-10-30 14:42:45 -04:00
if ( ! metadata . original _id ) {
console . error ( ` No 'original_id' field found in ${ file } . Perhaps you forgot to add it when importing prior versions of your docs? ` ) ;
throw new Error (
` No 'original_id' field found in ${ file } . Perhaps you forgot to add it when importing prior versions of your docs? `
) ;
}
if ( ! metadata . id ) {
console . error ( ` No 'id' field found in ${ file } . ` ) ;
throw new Error (
` No 'id' field found in ${ file } . `
) ;
} else if ( metadata . id . indexOf ( 'version-' ) === - 1 ) {
console . error ( ` The 'id' field in ${ file } is missing the expected 'version-XX-' prefix. Perhaps you forgot to add it when importing prior versions of your docs? ` ) ;
throw new Error (
` The 'id' field in ${ file } is missing the expected 'version-XX-' prefix. Perhaps you forgot to add it when importing prior versions of your docs? `
) ;
}
2017-08-03 13:25:01 -04:00
if ( ! ( metadata . original _id in available ) ) {
available [ metadata . original _id ] = new Set ( ) ;
}
const version = metadata . id . split ( "-" ) [ 1 ] ;
available [ metadata . original _id ] . add ( version ) ;
if ( ! ( version in versionFiles ) ) {
versionFiles [ version ] = { } ;
}
versionFiles [ version ] [ metadata . original _id ] = file ;
} ) ;
2017-08-03 14:14:56 -04:00
// returns the version to use for a document based on its id and
// what the requested version is
2017-08-03 13:25:01 -04:00
function docVersion ( id , req _version ) {
2017-08-04 15:57:43 -04:00
// iterate through versions until a version less than or equal to the requested
2017-10-30 14:42:45 -04:00
// is found, then check if that version has an available file to use
2017-08-04 15:57:43 -04:00
let requestedFound = false ;
2017-08-03 13:25:01 -04:00
for ( let i = 0 ; i < versions . length ; i ++ ) {
2017-08-04 15:57:43 -04:00
if ( versions [ i ] === req _version ) {
requestedFound = true ;
}
if ( ! requestedFound ) {
2017-08-03 13:25:01 -04:00
continue ;
}
if ( ! available [ id ] ) {
return null ;
}
if ( available [ id ] . has ( versions [ i ] ) ) {
return versions [ i ] ;
}
}
2017-08-09 19:17:39 -04:00
throw new Error (
2017-10-30 14:42:45 -04:00
` No document with id ' ${ id } ' available for use in version ${ req _version } of the website. Verify that all version files are correct. Was the document deleted in a past version? `
2017-08-09 19:17:39 -04:00
) ;
2017-08-03 13:25:01 -04:00
}
2017-08-03 14:14:56 -04:00
// returns whether a given file has content that differ from the
// document with the given id
2017-08-03 13:25:01 -04:00
function diffLatestDoc ( file , id ) {
if ( versions . length === 0 ) {
return true ;
}
const latest = versions [ 0 ] ;
2017-10-30 14:42:45 -04:00
let version ;
try {
version = docVersion ( id , latest ) ;
} catch ( e ) {
console . error ( e ) ;
process . exit ( 1 ) ;
}
2017-08-03 13:25:01 -04:00
if ( ! version ) {
return true ;
}
const latestFile = versionFiles [ version ] [ id ] ;
if ( ! latestFile || ! fs . existsSync ( latestFile ) ) {
return true ;
}
const diffs = diff . diffChars (
extractMetadata ( fs . readFileSync ( latestFile , "utf8" ) ) . rawContent ,
extractMetadata ( fs . readFileSync ( file , "utf8" ) ) . rawContent
) ;
diffs . forEach ( part => {
if ( part . added || part . removed ) {
return true ;
}
} ) ;
return false ;
}
2017-08-03 14:14:56 -04:00
// return metadata for a versioned file given the file, its version (requested),
// the version of the file to be used, and its language
2017-08-03 13:25:01 -04:00
function processVersionMetadata ( file , version , useVersion , language ) {
const metadata = extractMetadata ( fs . readFileSync ( file , "utf8" ) ) . metadata ;
metadata . source = "version-" + useVersion + "/" + path . basename ( file ) ;
2017-08-04 15:41:13 -04:00
const latestVersion = versions [ 0 ] ;
2017-08-03 13:25:01 -04:00
if ( ! ENABLE _TRANSLATION && ! siteConfig . useEnglishUrl ) {
metadata . permalink =
2017-08-04 15:41:13 -04:00
"docs/" +
( version !== latestVersion ? version + "/" : "" ) +
metadata . original _id +
".html" ;
2017-08-03 13:25:01 -04:00
} else {
metadata . permalink =
2017-08-04 15:41:13 -04:00
"docs/" +
language +
"/" +
( version !== latestVersion ? version + "/" : "" ) +
metadata . original _id +
".html" ;
2017-08-03 13:25:01 -04:00
}
metadata . id = metadata . id . replace (
"version-" + useVersion + "-" ,
"version-" + version + "-"
) ;
metadata . localized _id = metadata . id ;
metadata . id = language + "-" + metadata . id ;
metadata . language = language ;
metadata . version = version ;
return metadata ;
}
2017-08-03 14:14:56 -04:00
// return all metadata of versioned documents
2017-08-03 13:25:01 -04:00
function docData ( ) {
2017-08-08 14:44:30 -04:00
const allIds = new Set ( ) ;
2017-08-03 13:25:01 -04:00
Object . keys ( versionFiles ) . forEach ( version => {
Object . keys ( versionFiles [ version ] ) . forEach ( id => {
allIds . add ( id ) ;
} ) ;
} ) ;
const metadatas = [ ] ;
languages . filter ( language => language . enabled ) . forEach ( language => {
versions . forEach ( version => {
allIds . forEach ( id => {
2017-10-30 14:42:45 -04:00
let useVersion ;
try {
useVersion = docVersion ( id , version ) ;
} catch ( e ) {
console . log ( e ) ;
process . exit ( 1 ) ;
}
2017-08-03 13:25:01 -04:00
if ( ! useVersion ) {
return ;
}
const file = versionFiles [ useVersion ] [ id ] ;
metadatas . push (
processVersionMetadata ( file , version , useVersion , language . tag )
) ;
} ) ;
} ) ;
} ) ;
return metadatas ;
}
2017-08-03 14:14:56 -04:00
// return the version of the sidebar to use given a requested version
2017-08-03 13:25:01 -04:00
function sidebarVersion ( req _version ) {
2017-08-04 15:57:43 -04:00
// iterate through versions until a version less than or equal to the requested
2017-08-09 19:17:39 -04:00
// is found, then check if that version has an available file to use
2017-08-04 15:57:43 -04:00
let requestedFound = false ;
2017-08-03 13:25:01 -04:00
for ( let i = 0 ; i < versions . length ; i ++ ) {
2017-08-04 15:57:43 -04:00
if ( versions [ i ] === req _version ) {
requestedFound = true ;
}
if ( ! requestedFound ) {
2017-08-03 13:25:01 -04:00
continue ;
}
if (
fs . existsSync (
2017-08-09 18:50:20 -04:00
CWD + "/versioned_sidebars/version-" + versions [ i ] + "-sidebars.json"
2017-08-03 13:25:01 -04:00
)
) {
return versions [ i ] ;
}
}
2017-08-09 19:17:39 -04:00
throw new Error (
2017-10-30 14:42:45 -04:00
` No sidebar file available to use for version ${ req _version } . Verify that 'version- ${ req _version } -sidebars.json' exists. `
2017-08-09 19:17:39 -04:00
) ;
2017-08-03 13:25:01 -04:00
}
2017-08-09 18:50:20 -04:00
// return whether or not the current sidebars.json file differs from the
2017-08-03 14:14:56 -04:00
// latest versioned one
2017-08-03 13:25:01 -04:00
function diffLatestSidebar ( ) {
if ( versions . length === 0 ) {
return true ;
}
const latest = versions [ 0 ] ;
const version = sidebarVersion ( latest ) ;
const latestSidebar =
2017-08-09 18:50:20 -04:00
CWD + "/versioned_sidebars/version-" + version + "-sidebars.json" ;
2017-08-03 13:25:01 -04:00
if ( ! fs . existsSync ( latestSidebar ) ) {
return true ;
}
2017-08-09 18:50:20 -04:00
const currentSidebar = CWD + "/sidebars.json" ;
2017-08-10 17:51:34 -04:00
// if no current sidebar file, return false so no sidebar file gets copied
2017-08-03 13:25:01 -04:00
if ( ! fs . existsSync ( currentSidebar ) ) {
2017-08-10 17:51:34 -04:00
return false ;
2017-08-03 13:25:01 -04:00
}
// compare for equality between latest version sidebar with version prefixes
// stripped and current sidebar
return (
JSON . stringify ( JSON . parse ( fs . readFileSync ( latestSidebar , "utf8" ) ) ) . replace (
new RegExp ( "version-" + version + "-" , "g" ) ,
""
) !== JSON . stringify ( JSON . parse ( fs . readFileSync ( currentSidebar , "utf8" ) ) )
) ;
}
2017-08-03 14:14:56 -04:00
// return all versioned sidebar data
2017-08-03 13:25:01 -04:00
function sidebarData ( ) {
const allSidebars = { } ;
for ( let i = 0 ; i < versions . length ; i ++ ) {
const version = sidebarVersion ( versions [ i ] ) ;
const sidebar = JSON . parse (
fs
. readFileSync (
2017-08-09 18:50:20 -04:00
CWD + "/versioned_sidebars/version-" + version + "-sidebars.json" ,
2017-08-03 13:25:01 -04:00
"utf8"
)
. replace (
new RegExp ( "version-" + version + "-" , "g" ) ,
"version-" + versions [ i ] + "-"
)
) ;
Object . assign ( allSidebars , sidebar ) ;
}
return allSidebars ;
}
module . exports = {
docVersion ,
diffLatestDoc ,
processVersionMetadata ,
docData ,
sidebarVersion ,
diffLatestSidebar ,
sidebarData
} ;