docusaurus/v1/lib/server/versionFallback.js
Dom Corvasce 61078e38a9 feat: Allow modifying docs url prefix (#914)
* Allow other routes than /docs in the URL

siteConfig.js has a new mandatory field named *docsRoute* which default
value is 'docs' and that can be customized by the user.

This change will allow users who uses the library to host guides and
tutorials to customize their websites by assign 'docsRoute' values
like 'tutorials' or 'guides'.

Fixes #879

* Make "docsRoute" field optional

* Isolate docsRoute login in getDocsRoute function

* Rename docsRoute to docsUrl

* Run prettier

* Remove old folders

* fix: Restore docusaurus reference link

* fix: Add `docsUrl` param fallback. Refactor multiple function calls

* Fix linting errors

* Update description for docsUrl field

* Reduce redundant calls to getDocsUrl

* Replace a missed use case for `docsUrl` instead of the function call

* Move `getDocsUrl` out from `server/routing.js` to `server/utils.js`

**Why?**
Because `routing.js` is exporting all router RegEx's, and the
`getDocsUrl` suffices more as a util

* WiP: Align leading slashes and fix routing around `docsUrl`

Checklist:
- [x] Added `removeDuplicateLeadingSlashes` util to make sure there is only
one leading slash
- [-] Fix edge cases for routing:
  - [x] `docsUrl: ''`
  - [ ] `docsUrl: '/'`
  - [ ] make it work with languages
  - [ ] make it work with versioning

* Make leading slashes canonical cross routing and generated links

This ensures correct routing for customized `baseUrl` and `docsUrl`.

- Changed all routing functions to take `siteConfig` instead of
`siteConfig.baseUrl`
- Updated tests accordingly

* Alternative fallback for `docsUrl`

* rework/ fix implementation

* cleanup

* refactor and add docs for config props

* fix typo

* fix broken url
2018-11-28 15:34:16 +08:00

332 lines
9.4 KiB
JavaScript

/**
* Copyright (c) 2017-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const CWD = process.cwd();
const glob = require('glob');
const fs = require('fs');
const path = require('path');
const metadataUtils = require('./metadataUtils');
const env = require('./env.js');
const utils = require('./utils.js');
const loadConfig = require('./config');
const siteConfig = loadConfig(`${CWD}/siteConfig.js`);
const ENABLE_TRANSLATION = fs.existsSync(`${CWD}/languages.js`);
let versions;
if (fs.existsSync(`${CWD}/versions.json`)) {
versions = require(`${CWD}/versions.json`);
} else {
versions = [];
}
let languages;
if (fs.existsSync(`${CWD}/languages.js`)) {
languages = require(`${CWD}/languages.js`);
} else {
languages = [
{
enabled: true,
name: 'English',
tag: 'en',
},
];
}
const versionFolder = `${CWD}/versioned_docs/`;
// available stores doc ids of documents that are available for
// each version
const available = {};
// versionFiles is used to keep track of what file to use with a
// given version/id of a document
const versionFiles = {};
const files = glob.sync(`${versionFolder}**`);
files.forEach(file => {
const ext = path.extname(file);
if (ext !== '.md' && ext !== '.markdown') {
return;
}
const res = metadataUtils.extractMetadata(fs.readFileSync(file, 'utf8'));
const metadata = res.metadata;
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?`,
);
}
// The version will be between "version-" and "-<metadata.original_id>"
// e.g. version-1.0.0-beta.2-doc1 => 1.0.0-beta.2
// e.g. version-1.0.0-doc2 => 1.0.0
// e.g. version-1.0.0-getting-started => 1.0.0
const version = metadata.id.substring(
metadata.id.indexOf('version-') + 8, // version- is 8 characters
metadata.id.lastIndexOf(`-${metadata.original_id}`),
);
// the original_id should be namespaced according to subdir to allow duplicate id in different subfolder
const subDir = utils.getSubDir(
file,
path.join(versionFolder, `version-${version}`),
);
if (subDir) {
metadata.original_id = `${subDir}/${metadata.original_id}`;
}
if (!(metadata.original_id in available)) {
available[metadata.original_id] = new Set();
}
available[metadata.original_id].add(version);
if (!(version in versionFiles)) {
versionFiles[version] = {};
}
versionFiles[version][metadata.original_id] = file;
});
// returns the version to use for a document based on its id and
// what the requested version is
function docVersion(id, reqVersion) {
if (!available[id]) {
return null;
}
// iterate through versions until a version less than or equal to the requested
// is found, then check if that version has an available file to use
let requestedFound = false;
for (let i = 0; i < versions.length; i++) {
if (versions[i] === reqVersion) {
requestedFound = true;
}
if (requestedFound && available[id].has(versions[i])) {
return versions[i];
}
}
return null;
}
// returns whether a given file has content that differ from the
// document with the given id
function diffLatestDoc(file, id) {
if (versions.length === 0) {
return true;
}
const latest = versions[0];
let version;
try {
version = docVersion(id, latest);
} catch (e) {
console.error(e);
process.exit(1);
}
if (!version) {
return true;
}
const latestFile = versionFiles[version][id];
if (!latestFile || !fs.existsSync(latestFile)) {
return true;
}
return (
metadataUtils
.extractMetadata(fs.readFileSync(latestFile, 'utf8'))
.rawContent.trim() !==
metadataUtils
.extractMetadata(fs.readFileSync(file, 'utf8'))
.rawContent.trim()
);
}
// return metadata for a versioned file given the file, its version (requested),
// the version of the file to be used, and its language
function processVersionMetadata(file, version, useVersion, language) {
const metadata = metadataUtils.extractMetadata(fs.readFileSync(file, 'utf8'))
.metadata;
// Add subdirectory information to versioned_doc metadata
// Example: `versioned_docs/version-1.1.6/projectA/readme.md` file with id `version-1.1.6-readme`
// and original_id `readme` will have metadata id of `version-1.1.6-projectA/readme` and original_id `projectA/readme`
const subDir = utils.getSubDir(
file,
path.join(CWD, 'versioned_docs', `version-${useVersion}`),
);
if (subDir) {
metadata.original_id = `${subDir}/${metadata.original_id}`;
metadata.id = metadata.id.replace(
`version-${useVersion}-`,
`version-${useVersion}-${subDir}/`,
);
}
metadata.source = subDir
? `version-${useVersion}/${subDir}/${path.basename(file)}`
: `version-${useVersion}/${path.basename(file)}`;
const latestVersion = versions[0];
const docsPart = `${siteConfig.docsUrl ? `${siteConfig.docsUrl}/` : ''}`;
const versionPart = `${version !== latestVersion ? `${version}/` : ''}`;
if (!ENABLE_TRANSLATION && !siteConfig.useEnglishUrl) {
metadata.permalink = `${docsPart}${versionPart}${
metadata.original_id
}.html`;
} else {
metadata.permalink = `${docsPart}${language}/${versionPart}${
metadata.original_id
}.html`;
}
metadata.id = metadata.id.replace(
`version-${useVersion}-`,
`version-${version}-`,
);
metadata.localized_id = metadata.id;
metadata.id = (env.translation.enabled ? `${language}-` : '') + metadata.id;
metadata.language = language;
metadata.version = version;
return metadata;
}
// return all metadata of versioned documents
function docData() {
const allIds = new Set();
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 => {
let useVersion;
try {
useVersion = docVersion(id, version);
} catch (e) {
console.log(e);
process.exit(1);
}
if (!useVersion) {
return;
}
const file = versionFiles[useVersion][id];
metadatas.push(
processVersionMetadata(file, version, useVersion, language.tag),
);
});
});
});
return metadatas;
}
// return the version of the sidebar to use given a requested version
function sidebarVersion(reqVersion) {
// iterate through versions until a version less than or equal to the requested
// is found, then check if that version has an available file to use
let requestedFound = false;
for (let i = 0; i < versions.length; i++) {
if (versions[i] === reqVersion) {
requestedFound = true;
}
if (
requestedFound &&
fs.existsSync(
`${CWD}/versioned_sidebars/version-${versions[i]}-sidebars.json`,
)
) {
return versions[i];
}
}
throw new Error(
`No sidebar file available to use for version ${reqVersion}. Verify that 'version-${reqVersion}-sidebars.json' exists.`,
);
}
// return whether or not the current sidebars.json file differs from the
// latest versioned one
function diffLatestSidebar() {
if (versions.length === 0) {
return true;
}
const latest = versions[0];
const version = sidebarVersion(latest);
const latestSidebar = `${CWD}/versioned_sidebars/version-${version}-sidebars.json`;
if (!fs.existsSync(latestSidebar)) {
return true;
}
const currentSidebar = `${CWD}/sidebars.json`;
// if no current sidebar file, return false so no sidebar file gets copied
if (!fs.existsSync(currentSidebar)) {
return false;
}
// 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')))
);
}
// return all versioned sidebar data
function sidebarData() {
const allSidebars = {};
for (let i = 0; i < versions.length; i++) {
const version = sidebarVersion(versions[i]);
const sidebar = JSON.parse(
fs
.readFileSync(
`${CWD}/versioned_sidebars/version-${version}-sidebars.json`,
'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,
};