UNPKG

@cake-hub/core

Version:

The core rendering engine for cake documentation pages.

298 lines (262 loc) 9.8 kB
const deepmerge = require("deepmerge"); const path = require("path"); // Import interfaces const FileStructureInterface = require("./interfaces/file-structure.interface"); const FrontmatterInterface = require("./interfaces/frontmatter.interface"); const MarkdownComponentInterface = require("./interfaces/markdown-component.interface"); const PluginInterface = require("./interfaces/plugin.interface"); const TemplateInterface = require("./interfaces/template.interface"); // Import modules const CommandManager = require("./modules/command-manager.module"); const ConfigurationParser = require("./modules/configuration-parser.module"); const FileManager = require("./modules/file-manager.module"); const LoggingModule = require("./modules/logging.module"); const MarkdownInterpreter = require("./modules/markdown-interpreter.module"); const Queue = require("./modules/queue.module"); const TemplateEngine = require("./modules/template-engine.module"); // Import commands const parseProjectConfigurations = require("./commands/parse-project-configurations.command"); const runProjectCommands = require("./commands/run-project-commands.command"); const { runPluginMethods, initializeTemplateEngine, initializeMarkdownComponents, initializeFileStructure, initializePathPrefix, buildEntry, } = require("./commands/build-documentation.command"); const cleanup = require("./commands/cleanup.command"); // Manage configuration let configuration = { mode: "production", projectsDirectory: null, outputDirectory: null, pathPrefix: null, projects: { configurationFileName: "cake-project.yml", defaultConfigurationFilePath: path.resolve(__dirname, "./templates/cake-project.yml"), filterFiles: null, // (filePath, projectConfiguration) => {}, }, // options for the LoggingModule: logging: { logToConsole: true, logToFile: false, logFilePath: path.resolve(__dirname, "./logs.log"), }, markdown: { useHighlightJs: true, }, }; let plugins = []; let markdownComponents = []; let templates = []; let isInitialized = false; let initializationValues = { projectConfigurations: null, templateEngine: null, fileStructure: null, fileLists: null, options: null, }; // Export all relevant modules module.exports = { // Handle modules & interfaces interfaces: { FileStructureInterface, FrontmatterInterface, MarkdownComponentInterface, PluginInterface, TemplateInterface, }, modules: { CommandManager, ConfigurationParser, FileManager, LoggingModule, MarkdownInterpreter, Queue, TemplateEngine, }, // Handle configuration setOptions(options) { configuration = deepmerge(configuration, options); }, getOptions() { return configuration; }, // Handle extensions & plugins registerPlugin(plugin) { plugins.push(plugin); }, registerMarkdownComponent(markdownComponent) { markdownComponents.push(markdownComponent); }, registerTemplate(template) { templates.push(template); }, // Handle actual commands async initialize() { if (isInitialized === true) { return; } isInitialized = true; // Initialize Markdown-interpreter await MarkdownInterpreter.initialize ({configuration}); initializationValues.options = { configuration, plugins, markdownComponents, templates, }; initializationValues.projectConfigurations = await parseProjectConfigurations(initializationValues.options); await runProjectCommands(initializationValues.projectConfigurations, initializationValues.options); // initialize template-engine initializationValues.templateEngine = await initializeTemplateEngine(templates); // initialize file-structure const { fileStructure, fileLists } = await initializeFileStructure(initializationValues.projectConfigurations, initializationValues.options); initializationValues.fileStructure = fileStructure; initializationValues.fileLists = fileLists; // initialize markdown-plugins/components await initializeMarkdownComponents(markdownComponents, {...initializationValues, fileStructure}); await runPluginMethods( plugins, "afterInitialization", null, { ...initializationValues.options, projectConfigurations: initializationValues.projectConfigurations, fileStructure, fileLists, } ); }, async build() { // Create queue const queue = new Queue({ maxParallelRuns: 20, }); const { projectConfigurations, templateEngine, fileStructure, fileLists, options, } = initializationValues; // Iterate complete project structure for (const [idx, projectConfiguration] of projectConfigurations.entries()) { const documentationSourcePath = path.resolve(projectConfiguration.get("_.projectPath"), projectConfiguration.get("documentationDirectory")); // Create pathPrefix for each file to use const pathPrefix = await initializePathPrefix(projectConfiguration, { fileStructure, ...options }); // Iterate through each file for (const file of fileLists[idx]) { queue.push(async () => { const { sourcePath, // if null, we will create a file otherwise just copy it fileContent, destinationPath, } = await buildEntry(file, { ...options, projectConfiguration, pathPrefix, documentationSourcePath, fileStructure, templateEngine, }); // write / copy file to correct destination if (!!sourcePath) { await FileManager.copyFiles(sourcePath, destinationPath); } else { await FileManager.writeFile(destinationPath, fileContent); } }); } } // Start parallel execution of conversions await queue.start(); // Run last plugin commands await runPluginMethods( plugins, "afterBuild", null, { projectConfigurations, fileStructure, } ); await cleanup(options); }, async server() { // Reused utilities const { projectConfigurations, templateEngine, fileStructure, fileLists, options, } = initializationValues; // Create pathPrefix for each file to use const pathPrefixes = []; for (const projectConfiguration of projectConfigurations) { pathPrefixes.push( await initializePathPrefix( projectConfiguration, { fileStructure, ...options } ) ); } return async (req, res, next) => { if (!projectConfigurations || !fileStructure) { return next(); } // Get request-path to identify required file const requestPath = req.path.substring(1); // Find index of correct project const projectIdx = pathPrefixes.findIndex(pP => requestPath.startsWith(pP)); if (projectIdx < 0) { return next(); } const projectConfiguration = projectConfigurations[projectIdx]; const documentationSourcePath = path.resolve(projectConfiguration.get("_.projectPath"), projectConfiguration.get("documentationDirectory")); const projectFiles = fileLists[projectIdx]; const requestFilePath = path.relative(pathPrefixes[projectIdx], decodeURI (requestPath)); let foundFile = null; for (const projectFile of projectFiles) { const relativeFilePath = path.relative(documentationSourcePath, projectFile); if ( requestFilePath === relativeFilePath || // complete file match requestFilePath === relativeFilePath.split('.').slice(0, -1).join('.') + ".html" // found matching md-file ) { foundFile = projectFile; break; } } if (!foundFile) { return next(); } const { sourcePath, // if null, we will create a file otherwise just copy it fileContent, } = await buildEntry(foundFile, { ...options, projectConfiguration, pathPrefix: pathPrefixes[projectIdx], documentationSourcePath, fileStructure, templateEngine, }); if (sourcePath) { // send file to client const fileContent = await FileManager.readFile (foundFile, null); res.send (fileContent); } else { res.send (fileContent); } }; }, };