@cake-hub/core
Version:
The core rendering engine for cake documentation pages.
298 lines (262 loc) • 9.8 kB
JavaScript
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);
}
};
},
};