307 lines
8.2 KiB
JavaScript

const path = require('path');
const fsExtra = require('fs-extra');
const execa = require('execa');
const MULTI_FRAMEWORKED_CONTENT_DIR = '.build-tmp';
const FRAMEWORK_SUFFIX = '-data-grid';
const versionFromBranchRegExp = /^prod-docs\/(\d+\.\d+)$/;
let docsVersion = null;
let docsSHA = null;
// Please keep in mind that the first element is default framework.
const fullyProcessedFrameworks = [
'javascript',
'react',
];
const frameworkToPrettyName = new Map([
['javascript', 'JavaScript'],
['react', 'React'],
['angular', 'Angular'],
['vue', 'Vue 2'],
['vue3', 'Vue 3'],
]);
/**
* Gets all available frameworks.
*
* @returns {string[]}
*/
function getFrameworks() {
return Array.from(fullyProcessedFrameworks);
}
/**
* Gets default framework.
*
* @returns {string}
*/
function getDefaultFramework() {
return getFrameworks()[0];
}
/**
* Get pretty framework name.
*
* @param {string} framework Framework ID.
* @returns {string|undefined}
*/
function getPrettyFrameworkName(framework) {
return frameworkToPrettyName.get(framework);
}
/**
* Gets the current (this) version of docs.
*
* @returns {string}
*/
function getThisDocsVersion() {
const { DOCS_LATEST_VERSION } = process.env;
if (DOCS_LATEST_VERSION) {
docsVersion = DOCS_LATEST_VERSION;
return DOCS_LATEST_VERSION;
}
if (docsVersion === null) {
const branchName = execa.sync('git rev-parse --abbrev-ref HEAD', { shell: true }).stdout;
if (versionFromBranchRegExp.test(branchName) === true) {
docsVersion = branchName.match(versionFromBranchRegExp)[1];
} else {
docsVersion = 'next';
}
}
return docsVersion;
}
/**
* Gets the sidebar object for docs.
*
* @returns {object}
*/
function getSidebars() {
const filterByFramework = (guides, currentFramework) => {
const filterElementsForFramework = element =>
(Array.isArray(element.onlyFor) && element.onlyFor.includes(currentFramework)) ||
(typeof element.onlyFor === 'string' && element.onlyFor === currentFramework) ||
typeof element.onlyFor === 'undefined';
const guidesSections = JSON.parse(JSON.stringify(guides)); // Copy sidebar definition
const filteredGuidesSections = guidesSections.filter(filterElementsForFramework);
filteredGuidesSections.forEach((filteredGuidesSection) => {
filteredGuidesSection.children = filteredGuidesSection.children.reduce((newGuides, guide) => {
if (filterElementsForFramework(guide)) {
newGuides.push(guide.path);
}
return newGuides;
}, []);
});
return filteredGuidesSections;
};
// eslint-disable-next-line
const sidebarConfig = require(path.join(__dirname, '../content/sidebars.js'));
const sidebars = {};
getFrameworks().forEach((framework) => {
Object.entries(sidebarConfig).forEach(([parentPath, subjectChildren]) => {
const childrenClone = JSON.parse(JSON.stringify(subjectChildren));
const isGuidesPage = parentPath === 'guides';
const sidebarChildren = isGuidesPage ? filterByFramework(childrenClone, framework) : childrenClone;
const fullPath = `/${framework}${FRAMEWORK_SUFFIX}/${isGuidesPage ? '' : `${parentPath}/`}`;
sidebars[`/${MULTI_FRAMEWORKED_CONTENT_DIR}${fullPath}`] = sidebarChildren.map((child) => {
if (typeof child === 'object' && child.path) {
child.path = `${fullPath}${child.path}/`;
}
return child;
});
});
});
return sidebars;
}
/**
* Removes temporary directory from the path when needed.
*
* @param {string} normalizedPath Path for unification.
* @returns {string}
*/
function getNormalizedPath(normalizedPath) {
return normalizedPath
.replace(new RegExp(`^/?${MULTI_FRAMEWORKED_CONTENT_DIR}`), '')
.replace(/\.(html|md)$/, '');
}
/**
* Get object containing list of not searchable links from the guides section for specific version of documentation.
*
* @returns {object}
*/
function getIgnorePagesPatternList() {
const frameworks = getFrameworks();
const filterLinks = (guides, framework) => {
const links = [];
const isNotSearchable = element =>
(Array.isArray(element.onlyFor) && element.onlyFor.includes(framework) === false) ||
(typeof element.onlyFor === 'string' && element.onlyFor !== framework);
guides.forEach((guideSection) => {
if (isNotSearchable(guideSection)) {
links.push(...guideSection.children.map(guide => guide.path));
} else {
guideSection.children.forEach((guide) => {
if (isNotSearchable(guide)) {
links.push(guide.path);
}
});
}
});
return links;
};
// eslint-disable-next-line
const sidebarConfig = require(path.join(__dirname, `../content/sidebars.js`));
const pages = [];
frameworks.forEach((framework) => {
const linksWithFramework = filterLinks(sidebarConfig.guides, framework)
.map(link => `!${MULTI_FRAMEWORKED_CONTENT_DIR}/${framework}${FRAMEWORK_SUFFIX}/${link}.md`);
pages.push(...linksWithFramework);
});
return pages;
}
/**
* Parses the docs framework from the URL.
*
* @param {string} url The URL to parse.
* @returns {string}
*/
function parseFramework(url) {
const potentialFramework = getNormalizedPath(url).split('/')[1]?.replace(FRAMEWORK_SUFFIX, '');
if (getFrameworks().includes(potentialFramework)) {
return potentialFramework;
}
}
/**
* Parses the provided file path to find the "partial" framework name.
*
* @param {string} filePath The file path.
* @returns {string|null}
*/
function parsePartialFramework(filePath) {
const frameworkMatch = filePath.match(/guides\/integrate-with-(vue3|vue|angular)?/);
return frameworkMatch ? frameworkMatch[1] : null;
}
/**
* Create symlinks needed for multi-frameworked content.
*/
function createSymlinks() {
fsExtra.removeSync(MULTI_FRAMEWORKED_CONTENT_DIR);
getFrameworks().forEach((framework) => {
fsExtra.ensureSymlinkSync('content', `./${MULTI_FRAMEWORKED_CONTENT_DIR}/${framework}${FRAMEWORK_SUFFIX}`);
});
}
/**
* Returns the repository latest SHA.
*
* @returns {string}
*/
function getDocsRepoSHA() {
if (docsSHA === null) {
docsSHA = execa.sync('git rev-parse HEAD', { shell: true }).stdout;
}
return docsSHA;
}
/**
* Gets docs base path (eq: /docs/12.1).
*
* @returns {string}
*/
function getDocsBase() {
const buildMode = process.env.BUILD_MODE;
const docsBase = process.env.DOCS_BASE ?? getThisDocsVersion();
let base = '/docs/';
// For staging:
// * `docsBase` === 'next' generate docs with Docs base set as `/docs/next`;
// * `docsBase` === 'latest' generate docs with Docs base set as `/docs`;
// For other environments (prod, local build, local watch):
// * `docsBase` === 'next' (happens locally) generate docs with Docs base set as `/docs`;
// * `docsBase` === 'latest' generate docs with Docs base set as `/docs`;
// * `docsBase` === '12.1' (happens while testing production branch) generate docs with Docs base set as `/docs/12.1`;
if ((buildMode === 'staging' && docsBase !== 'latest') ||
(buildMode !== 'staging' && docsBase !== 'next' && docsBase !== 'latest')) {
base += `${docsBase}/`;
}
return base.replace(/\/$/, '');
}
/**
* Gets docs base full url with hostname (eq: https://handsontable.com/docs/12.1).
*
* @returns {string}
*/
function getDocsBaseFullUrl() {
return `${getDocsHostname()}${getDocsBase()}`;
}
/**
* Gets docs hostname (eq: https://handsontable.com).
*
* @param {boolean} [allowLocalhost=true] Returns localhost URL for local testing or official Docs domains as
* a source of truth for further "fetch" requests.
* @returns {string}
*/
function getDocsHostname(allowLocalhost = true) {
const buildMode = process.env.BUILD_MODE;
if (!buildMode && allowLocalhost) {
return 'http://localhost:8080';
}
return `https://${buildMode === 'staging' ? 'dev.' : ''}handsontable.com`;
}
module.exports = {
FRAMEWORK_SUFFIX,
MULTI_FRAMEWORKED_CONTENT_DIR,
getNormalizedPath,
getFrameworks,
getPrettyFrameworkName,
getSidebars,
getIgnorePagesPatternList,
parseFramework,
parsePartialFramework,
getDefaultFramework,
createSymlinks,
getThisDocsVersion,
getDocsRepoSHA,
getDocsBase,
getDocsBaseFullUrl,
getDocsHostname,
};