diff --git a/lib/generator.js b/lib/generator.js index dd5bff8b4..0203f3a64 100644 --- a/lib/generator.js +++ b/lib/generator.js @@ -139,6 +139,11 @@ class Generator { /** * Generates files from a given template and an AsyncAPIDocument object. * + * @async + * @example + * await generator.generate(myAsyncAPIdocument); + * console.log('Done!'); + * * @example * generator * .generate(myAsyncAPIdocument) @@ -155,48 +160,162 @@ class Generator { * console.error(e); * } * - * @param {AsyncAPIDocument | string} asyncapiDocument AsyncAPIDocument object to use as source. - * @param {Object} [parseOptions={}] AsyncAPI Parser parse options. Check out {@link https://www.github.com/asyncapi/parser-js|@asyncapi/parser} for more information. Remember to use the right options to the right parser depending on the template you are using. - * @return {Promise} + * @param {AsyncAPIDocument | string} asyncapiDocument - AsyncAPIDocument object to use as source. + * @param {Object} [parseOptions={}] - AsyncAPI Parser parse options. + * Check out {@link https://www.github.com/asyncapi/parser-js|@asyncapi/parser} for more information. + * Remember to use the right options for the right parser depending on the template you are using. + * @return {Promise} A Promise that resolves when the generation is completed. */ - // eslint-disable-next-line sonarjs/cognitive-complexity async generate(asyncapiDocument, parseOptions = {}) { + this.validateAsyncAPIDocument(asyncapiDocument); + this.setupOutput(); + this.setLogLevel(); + + await this.installAndSetupTemplate(); + await this.configureTemplateWorkflow(parseOptions); + await this.handleEntrypoint(); + await this.executeAfterHook(); + } + + /** + * Validates the provided AsyncAPI document. + * + * @param {*} asyncapiDocument - The AsyncAPI document to be validated. + * @throws {Error} Throws an error if the document is not valid. + * @since 10/9/2023 - 4:26:33 PM + */ + validateAsyncAPIDocument(asyncapiDocument) { const isAlreadyParsedDocument = isAsyncAPIDocument(asyncapiDocument); const isParsableCompatible = asyncapiDocument && typeof asyncapiDocument === 'string'; + if (!isAlreadyParsedDocument && !isParsableCompatible) { throw new Error('Parameter "asyncapiDocument" must be a non-empty string or an already parsed AsyncAPI document.'); } + this.asyncapi = this.originalAsyncAPI = asyncapiDocument; + } + /** + * Sets up the output configuration based on the specified output type. + * + * @example + * const generator = new Generator(); + * generator.setupOutput(); + * + * @throws {Error} If 'output' is set to 'string' without providing 'entrypoint'. + */ + setupOutput() { if (this.output === 'fs') { - xfs.mkdirpSync(this.targetDir); - if (!this.forceWrite) await this.verifyTargetDir(this.targetDir); + this.setupFSOutput(); } else if (this.output === 'string' && this.entrypoint === undefined) { throw new Error('Parameter entrypoint is required when using output = "string"'); } + } + + /** + * Sets up the file system (FS) output configuration. + * + * This function creates the target directory if it does not exist and verifies + * the target directory if forceWrite is not enabled. + * + * @async + * @returns {Promise} A promise that fulfills when the setup is complete. + * + * @throws {Error} If verification of the target directory fails and forceWrite is not enabled. + */ + async setupFSOutput() { + // Create directory if not exists + xfs.mkdirpSync(this.targetDir); + // Verify target directory if forceWrite is not enabled + if (!this.forceWrite) { + await this.verifyTargetDir(this.targetDir); + } + } + + /** + * Sets the log level based on the debug option. + * + * If the debug option is enabled, the log level is set to 'debug'. + * + * @returns {void} + */ + setLogLevel() { if (this.debug) log.setLevel('debug'); + } + /** + * Installs and sets up the template for code generation. + * + * This function installs the specified template using the provided installation option, + * sets up the necessary directory paths, loads the template configuration, and returns + * information about the installed template. + * + * @async + * @returns {Promise<{ templatePkgName: string, templatePkgPath: string }>} + * A promise that resolves to an object containing the name and path of the installed template. + */ + async installAndSetupTemplate() { const { name: templatePkgName, path: templatePkgPath } = await this.installTemplate(this.install); + this.templateDir = templatePkgPath; this.templateName = templatePkgName; this.templateContentDir = path.resolve(this.templateDir, TEMPLATE_CONTENT_DIRNAME); + await this.loadTemplateConfig(); - await this.parseInput(this.asyncapi, parseOptions); + return { templatePkgName, templatePkgPath }; + } + /** + * Configures the template workflow based on provided parsing options. + * + * This function performs the following steps: + * 1. Parses the input AsyncAPI document using the specified parse options. + * 2. Validates the template configuration and parameters. + * 3. Configures the template based on the parsed AsyncAPI document. + * 4. Registers filters, hooks, and launches the 'generate:before' hook if applicable. + * + * @async + * @param {*} parseOptions - Options for parsing the AsyncAPI document. + * @returns {Promise} A promise that resolves when the configuration is completed. + */ + async configureTemplateWorkflow(parseOptions) { + // Parse input and validate template configuration + await this.parseInput(this.asyncapi, parseOptions); validateTemplateConfig(this.templateConfig, this.templateParams, this.asyncapi); await this.configureTemplate(); if (!isReactTemplate(this.templateConfig)) { await registerFilters(this.nunjucks, this.templateConfig, this.templateDir, FILTERS_DIRNAME); } + await registerHooks(this.hooks, this.templateConfig, this.templateDir, HOOKS_DIRNAME); await this.launchHook('generate:before'); + } + /** + * Handles the logic for the template entrypoint. + * + * If an entrypoint is specified: + * - Resolves the absolute path of the entrypoint file. + * - Throws an error if the entrypoint file doesn't exist. + * - Generates a file or renders content based on the output type. + * - Launches the 'generate:after' hook if the output is 'fs'. + * + * If no entrypoint is specified, generates the directory structure. + * + * @async + * @returns {Promise} A promise that resolves when the entrypoint logic is completed. + */ + async handleEntrypoint() { if (this.entrypoint) { const entrypointPath = path.resolve(this.templateContentDir, this.entrypoint); - if (!(await exists(entrypointPath))) throw new Error(`Template entrypoint "${entrypointPath}" couldn't be found.`); + + if (!(await exists(entrypointPath))) { + throw new Error(`Template entrypoint "${entrypointPath}" couldn't be found.`); + } + if (this.output === 'fs') { await this.generateFile(this.asyncapi, path.basename(entrypointPath), path.dirname(entrypointPath)); await this.launchHook('generate:after'); @@ -205,10 +324,21 @@ class Generator { } } else { await this.generateDirectoryStructure(this.asyncapi); - await this.launchHook('generate:after'); } } + /** + * Executes the 'generate:after' hook. + * + * Launches the after-hook to perform additional actions after code generation. + * + * @async + * @returns {Promise} A promise that resolves when the after-hook execution is completed. + */ + async executeAfterHook() { + await this.launchHook('generate:after'); + } + /** * Parse the generator input based on the template `templateConfig.apiVersion` value. */ diff --git a/package-lock.json b/package-lock.json index 8721c9b93..b135420dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -58,6 +58,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.0", "@jridgewell/trace-mapping": "^0.3.9" @@ -369,6 +370,7 @@ "version": "7.22.20", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.20.tgz", "integrity": "sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA==", + "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", @@ -398,6 +400,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "bin": { "semver": "bin/semver.js" } @@ -3321,7 +3324,7 @@ "version": "7.20.2", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.2.tgz", "integrity": "sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==", - "devOptional": true, + "dev": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -3334,7 +3337,7 @@ "version": "7.6.5", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.5.tgz", "integrity": "sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==", - "devOptional": true, + "dev": true, "dependencies": { "@babel/types": "^7.0.0" } @@ -3343,7 +3346,7 @@ "version": "7.4.2", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz", "integrity": "sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==", - "devOptional": true, + "dev": true, "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" @@ -3353,7 +3356,7 @@ "version": "7.20.2", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.2.tgz", "integrity": "sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==", - "devOptional": true, + "dev": true, "dependencies": { "@babel/types": "^7.20.7" }