UNPKG

boats

Version:

Beautiful Open / Async Template System - Write less yaml with BOATS and Nunjucks.

317 lines (316 loc) 15.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const fs_1 = require("fs"); const fs_extra_1 = tslib_1.__importDefault(require("fs-extra")); const _ = tslib_1.__importStar(require("lodash")); const nunjucks_1 = tslib_1.__importDefault(require("nunjucks")); const upath_1 = tslib_1.__importDefault(require("upath")); const calculateIndentFromLineBreak_1 = tslib_1.__importDefault(require("./calculateIndentFromLineBreak")); const cloneObject_1 = tslib_1.__importDefault(require("./cloneObject")); const defaults_1 = tslib_1.__importDefault(require("./defaults")); const stripFromEndOfString_1 = tslib_1.__importDefault(require("./stripFromEndOfString")); const apiTypeFromString_1 = tslib_1.__importDefault(require("./apiTypeFromString")); const Injector_1 = tslib_1.__importDefault(require("./Injector")); const autoChannelIndexer_1 = tslib_1.__importDefault(require("./nunjucksHelpers/autoChannelIndexer")); const autoComponentIndexer_1 = tslib_1.__importDefault(require("./nunjucksHelpers/autoComponentIndexer")); const autoPathIndexer_1 = tslib_1.__importDefault(require("./nunjucksHelpers/autoPathIndexer")); const autoSummary_1 = tslib_1.__importDefault(require("./nunjucksHelpers/autoSummary")); const schemaRef_1 = tslib_1.__importDefault(require("./nunjucksHelpers/schemaRef")); const autoTag_1 = tslib_1.__importDefault(require("./nunjucksHelpers/autoTag")); const fileName_1 = tslib_1.__importDefault(require("./nunjucksHelpers/fileName")); const inject_1 = tslib_1.__importDefault(require("./nunjucksHelpers/inject")); const merge_1 = tslib_1.__importDefault(require("./nunjucksHelpers/merge")); const mixin_1 = tslib_1.__importDefault(require("./nunjucksHelpers/mixin")); const packageJson_1 = tslib_1.__importDefault(require("./nunjucksHelpers/packageJson")); const routePermission_1 = tslib_1.__importDefault(require("./nunjucksHelpers/routePermission")); const uniqueOpId_1 = tslib_1.__importDefault(require("./nunjucksHelpers/uniqueOpId")); const optionalProps_1 = tslib_1.__importDefault(require("./nunjucksHelpers/optionalProps")); const pickProps_1 = tslib_1.__importDefault(require("./nunjucksHelpers/pickProps")); const pathInjector_1 = require("./pathInjector"); const isAsyncApi_1 = tslib_1.__importDefault(require("./isAsyncApi")); const constants_1 = require("./constants"); const dirListFilesSync_1 = require("./utils/dirListFilesSync"); const fileArraySortIndexToTop_1 = tslib_1.__importDefault(require("./utils/fileArraySortIndexToTop")); class Template { /** * Parses all files in a folder against the nunjuck tpl engine and outputs in a mirrored path the in provided outputDirectory * @param inputFile The input directory to start parsing from * @param output The directory to output/mirror to * @param originalIndent The original indent (currently hard coded to 2) * @param stripValue The strip value for the uniqueOpIp * @param variables The variables for the tpl engine * @param helpFunctionPaths Array of fully qualified local file paths to nunjucks helper functions * @param boatsrc * @param oneFileOutput When passed will output the tpl compiled files into a tmp folder, TMP_COMPILED_DIR_NAME */ // eslint-disable-next-line max-lines-per-function directoryParse(inputFile, output, originalIndent = defaults_1.default.DEFAULT_ORIGINAL_INDENTATION, stripValue = defaults_1.default.DEFAULT_STRIP_VALUE, variables, helpFunctionPaths, boatsrc, oneFileOutput) { if (!inputFile || !output) { throw new Error('You must pass an input file and output directory when parsing multiple files.'); } this.originalIndentation = originalIndent; this.mixinVarNamePrefix = defaults_1.default.DEFAULT_MIXIN_VAR_PREFIX; this.helpFunctionPaths = helpFunctionPaths || []; this.variables = variables || []; this.boatsrc = boatsrc; this.inputFile = inputFile = this.cleanInputString(inputFile); this.isAsyncApiFile = (0, isAsyncApi_1.default)(this.inputFile); // ensure we parse the input file 1st as this typically contains the inject function // this will also allow us to determine the api type and correctly set the stripValue let renderedIndex; try { console.log('Render index file 1st', inputFile); renderedIndex = this.renderFile(fs_extra_1.default.readFileSync(inputFile, 'utf8'), inputFile); } catch (e) { console.error(`Error parsing nunjucks file ${inputFile}: `.red.bold); console.error('Common errors in the index file are JSON syntax errors with the `inject` tpl function'.red); throw e; } this.stripValue = this.setDefaultStripValue(stripValue, renderedIndex); let returnFileinput; const files = (0, fileArraySortIndexToTop_1.default)((0, dirListFilesSync_1.dirListFilesSync)(upath_1.default.dirname(inputFile))); for (let i = 0; i < files.length; i++) { const file = upath_1.default.toUnix(files[i]); try { const outputFile = this.calculateOutputFile({ inputFile, currentFile: file, output, oneFileOutput }); const rendered = this.renderFile(fs_extra_1.default.readFileSync(file, 'utf8'), file); if (upath_1.default.normalize(inputFile) === upath_1.default.normalize(file)) { returnFileinput = outputFile; } fs_extra_1.default.outputFileSync(outputFile, rendered); } catch (e) { console.error(`Error parsing nunjucks file ${file}: `.red.bold); throw e; } } return this.stripNjkExtension(returnFileinput); } setDefaultStripValue(stripValue, inputString) { if (stripValue) { return stripValue; } switch ((0, apiTypeFromString_1.default)(inputString)) { case 'swagger': return 'src/paths/'; case 'openapi': return 'src/paths/'; case 'asyncapi': return 'src/channels/'; } throw new Error('Non supported api type provided. BOATS only supports swagger/openapi/asyncapi'); } /** * Cleans the input string to ensure a match with the walker package when mirroring * @param relativeFilePath */ cleanInputString(relativeFilePath) { relativeFilePath = upath_1.default.toUnix(relativeFilePath); if (relativeFilePath.substring(0, 2) === './') { return relativeFilePath.substring(2, relativeFilePath.length); } if (relativeFilePath.substring(0, 1) === '/') { return relativeFilePath.substring(1, relativeFilePath.length); } return relativeFilePath; } /** * Calculates the output file based on the input file, used for mirroring the input src dir. * Any .njk ext will automatically be removed. */ calculateOutputFile(input) { const inputDir = upath_1.default.dirname(input.inputFile); const filePathWithoutBuildOrSrcPath = input.currentFile.replace(inputDir, ''); return this.stripNjkExtension(upath_1.default.join(process.cwd(), // add the tmp folder name or not - the tmp folder has to be in the same relative positive as // the final output to ensure included from directory traversing still function (input.oneFileOutput) ? inputDir + constants_1.TMP_COMPILED_DIR_NAME : upath_1.default.dirname(input.output), filePathWithoutBuildOrSrcPath)); } /** * Strips out the njk ext from a given string * @param input * @return string */ stripNjkExtension(input) { return (0, stripFromEndOfString_1.default)(input, '.njk'); } /** * After render use only, takes a rendered njk file and replaces the .yml.njk with .njk * @param multiLineBlock */ stripNjkExtensionFrom$Refs(multiLineBlock) { const pattern = '.yml.njk'; const regex = new RegExp(pattern, 'g'); return multiLineBlock.replace(regex, '.yml'); } /** * Loads and renders a tpl file * @param inputString The string to parse * @param fileLocation The file location the string for the current */ renderFile(inputString, fileLocation) { this.currentFilePointer = upath_1.default.toUnix(fileLocation); this.mixinObject = this.setMixinPositions(inputString, this.originalIndentation); this.mixinNumber = 0; this.indentObject = this.setIndentPositions(inputString, 0); this.indentNumber = 0; this.nunjucksSetup(); const renderedYaml = Injector_1.default.injectAndRender(fileLocation, this.inputFile, this.boatsrc, this.isAsyncApiFile); return this.stripNjkExtensionFrom$Refs(renderedYaml); } /** * * @param str The string to look for mixins * @param originalIndentation The original indentation setting, defaults to 2 * @returns {Array} */ setMixinPositions(str, originalIndentation = 2) { const regexp = RegExp(/(mixin\(["'`]([^"`']*)["'`].*\))/, 'g'); let matches; const matched = []; while ((matches = regexp.exec(str)) !== null) { const mixinObj = { index: regexp.lastIndex, match: matches[0], mixinPath: matches[2], mixinLinePadding: '' }; const indent = (0, calculateIndentFromLineBreak_1.default)(str, mixinObj.index) + originalIndentation; for (let i = 0; i < indent; ++i) { mixinObj.mixinLinePadding += ' '; } matched.push(mixinObj); } return matched; } /** * * @param str The string to look for helpers that need indentations * @param originalIndentation The original indentation setting, defaults to 2 * @returns {Array} */ setIndentPositions(str, originalIndentation = 0) { const regexp = RegExp(/((optionalProps|pickProps)\(.*\))/, 'g'); let matches; const matched = []; const preparedString = str .split('\n') .map((s) => (/^\s*\-/.test(s) ? s.replace('-', ' ') : s)) .join('\n'); while ((matches = regexp.exec(preparedString)) !== null) { const indentObject = { index: regexp.lastIndex, match: matches[0], linePadding: '' }; const indent = (0, calculateIndentFromLineBreak_1.default)(preparedString, indentObject.index) + originalIndentation; for (let i = 0; i < indent; ++i) { indentObject.linePadding += ' '; } matched.push(indentObject); } return matched; } /** * Sets up the tpl engine for the current file being rendered */ nunjucksSetup() { const env = this.setupDefaultNunjucksEnv(); env.addGlobal('currentFilePointer', this.currentFilePointer); env.addGlobal('mixinObject', this.mixinObject); env.addGlobal('mixinNumber', this.mixinNumber); env.addGlobal('indentObject', this.indentObject); env.addGlobal('indentNumber', this.indentNumber); env.addGlobal('pathInjector', new pathInjector_1.PathInjector(this.boatsrc.paths, upath_1.default.relative('.', upath_1.default.dirname(this.inputFile)))); } /** * Default nunjucks env configuration * * @return {nunjucks.Environment} */ setupDefaultNunjucksEnv() { const env = nunjucks_1.default.configure(this.boatsrc.nunjucksOptions); const processEnvVars = (0, cloneObject_1.default)(process.env); for (const key in processEnvVars) { env.addGlobal(key, processEnvVars[key]); } if (Array.isArray(this.variables)) { this.variables.forEach((varObj) => { const keys = Object.keys(varObj); env.addGlobal(keys[0], varObj[keys[0]]); }); } env.addGlobal('_', _); env.addGlobal('autoChannelIndexer', autoChannelIndexer_1.default); env.addGlobal('autoComponentIndexer', autoComponentIndexer_1.default); env.addGlobal('autoPathIndexer', autoPathIndexer_1.default); env.addGlobal('autoSummary', autoSummary_1.default); env.addGlobal('autoTag', autoTag_1.default); env.addGlobal('boatsConfig', this.boatsrc); env.addGlobal('fileName', fileName_1.default); env.addGlobal('inject', inject_1.default); env.addGlobal('merge', merge_1.default); env.addGlobal('mixin', mixin_1.default); env.addGlobal('mixinVarNamePrefix', this.mixinVarNamePrefix); env.addGlobal('optionalProps', optionalProps_1.default); env.addGlobal('packageJson', packageJson_1.default); env.addGlobal('pickProps', pickProps_1.default); env.addGlobal('routePermission', routePermission_1.default); env.addGlobal('schemaRef', schemaRef_1.default); env.addGlobal('uniqueOpId', uniqueOpId_1.default); env.addGlobal('uniqueOpIdStripValue', this.stripValue); this.loadHelpers(env); return env; } /** * Loads js and ts helpers from files / folders, overriding existing if they * exist. * * @param {nunjucks.Environment} env The environment */ loadHelpers(env) { let tsNodeLoaded = false; const helpers = this.helpFunctionPaths.slice(); while (helpers === null || helpers === void 0 ? void 0 : helpers.length) { const filePath = helpers.shift(); if ((0, fs_1.statSync)(filePath).isDirectory()) { const files = (0, fs_1.readdirSync)(filePath).map((dir) => upath_1.default.join(filePath, dir)); helpers.push(...files); continue; } if (filePath.endsWith('.ts') && !tsNodeLoaded) { tsNodeLoaded = true; // eslint-disable-next-line @typescript-eslint/no-require-imports require('ts-node').register(); } if (!filePath.endsWith('.ts') && !filePath.endsWith('.js')) { continue; } // eslint-disable-next-line @typescript-eslint/no-require-imports let helper = require(filePath); if (typeof helper !== 'function' && typeof helper.default === 'function') { helper = helper.default; } const helperType = helper(nunjucks_1.default); if (typeof helperType === 'function') { helper = helperType; } env.addGlobal(this.getHelperFunctionNameFromPath(filePath), helper); } } /** * Returns an alpha numeric underscore helper function name * @param filePath */ getHelperFunctionNameFromPath(filePath) { return upath_1.default.basename(filePath, upath_1.default.extname(filePath)).replace(/[^0-9a-z_]/gi, ''); } } exports.default = new Template();