@asyncapi/generator 2.6.0 → 2.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +147 -0
  2. package/Dockerfile +7 -8
  3. package/docs/asyncapi-document.md +2 -1
  4. package/docs/configuration-file.md +112 -33
  5. package/docs/generator-template-java.md +560 -0
  6. package/docs/generator-template.md +62 -29
  7. package/docs/hooks.md +3 -3
  8. package/docs/installation-guide.md +5 -51
  9. package/docs/migration-cli.md +5 -5
  10. package/docs/migration-nunjucks-react.md +1 -1
  11. package/docs/nunjucks-render-engine.md +4 -2
  12. package/docs/template.md +2 -2
  13. package/docs/usage.md +14 -32
  14. package/docs/versioning.md +3 -1
  15. package/lib/conditionalGeneration.js +162 -0
  16. package/lib/filtersRegistry.js +1 -1
  17. package/lib/generator.js +93 -40
  18. package/lib/hooksRegistry.js +8 -1
  19. package/lib/logMessages.js +6 -1
  20. package/lib/parser.js +10 -0
  21. package/lib/templateConfigValidator.js +30 -1
  22. package/package.json +4 -5
  23. package/test/generator.test.js +1 -1
  24. package/test/hooksRegistry.test.js +173 -0
  25. package/test/integration.test.js +47 -0
  26. package/test/parser.test.js +100 -2
  27. package/test/templateConfigValidator.test.js +18 -1
  28. package/test/test-project/README.md +5 -1
  29. package/test/test-project/docker-compose.yml +7 -15
  30. package/test/test-project/test-project.test.js +6 -2
  31. package/test/test-project/test-registry.test.js +7 -2
  32. package/test/test-project/test.sh +1 -1
  33. package/test/test-templates/nunjucks-template/package-lock.json +43 -119
  34. package/test/test-templates/nunjucks-template/package.json +1 -1
  35. package/test/test-templates/react-template/.ageneratorrc +33 -0
  36. package/test/test-templates/react-template/package.json +5 -21
  37. package/test/test-templates/react-template/template/conditionalFile.txt +0 -0
  38. package/test/test-templates/react-template/template/conditionalFolder/conditionalFile.txt +0 -0
  39. package/test/test-templates/react-template/template/conditionalFolder2/input.txt +0 -0
  40. package/test/utils.test.js +53 -0
  41. package/test/test-project/test-entrypoint.sh +0 -12
package/lib/generator.js CHANGED
@@ -2,7 +2,6 @@ const path = require('path');
2
2
  const fs = require('fs');
3
3
  const xfs = require('fs.extra');
4
4
  const minimatch = require('minimatch');
5
- const jmespath = require('jmespath');
6
5
  const filenamify = require('filenamify');
7
6
  const git = require('simple-git');
8
7
  const log = require('loglevel');
@@ -10,12 +9,12 @@ const Arborist = require('@npmcli/arborist');
10
9
  const Config = require('@npmcli/config');
11
10
  const requireg = require('requireg');
12
11
  const npmPath = requireg.resolve('npm').replace('index.js','');
13
-
14
12
  const { isAsyncAPIDocument } = require('@asyncapi/parser/cjs/document');
15
13
 
16
14
  const { configureReact, renderReact, saveRenderedReactContent } = require('./renderer/react');
17
15
  const { configureNunjucks, renderNunjucks } = require('./renderer/nunjucks');
18
16
  const { validateTemplateConfig } = require('./templateConfigValidator');
17
+ const { isGenerationConditionMet } = require('./conditionalGeneration');
19
18
  const {
20
19
  convertMapToObject,
21
20
  isFileSystemPath,
@@ -135,7 +134,7 @@ class Generator {
135
134
  enumerable: true,
136
135
  get() {
137
136
  if (!self.templateConfig.parameters?.[key]) {
138
- throw new Error(`Template parameter "${key}" has not been defined in the package.json file under generator property. Please make sure it's listed there before you use it in your template.`);
137
+ throw new Error(`Template parameter "${key}" has not been defined in the Generator Configuration. Please make sure it's listed there before you use it in your template.`);
139
138
  }
140
139
  return templateParams[key];
141
140
  }
@@ -620,7 +619,7 @@ class Generator {
620
619
  path: packagePath,
621
620
  };
622
621
  } catch (err) {
623
- throw new Error('Installation failed', err);
622
+ throw new Error(`Installation failed: ${ err.message }`);
624
623
  }
625
624
  }
626
625
 
@@ -687,7 +686,7 @@ class Generator {
687
686
 
688
687
  walker.on('directory', async (root, stats, next) => {
689
688
  try {
690
- this.ignoredDirHandler(root, stats, next);
689
+ await this.ignoredDirHandler(root, stats, next);
691
690
  } catch (e) {
692
691
  reject(e);
693
692
  }
@@ -711,10 +710,23 @@ class Generator {
711
710
  * @param {String} stats Information about the file.
712
711
  * @param {Function} next Callback function
713
712
  */
714
- ignoredDirHandler(root, stats, next) {
713
+ async ignoredDirHandler(root, stats, next) {
715
714
  const relativeDir = path.relative(this.templateContentDir, path.resolve(root, stats.name));
716
715
  const dirPath = path.resolve(this.targetDir, relativeDir);
717
- if (!shouldIgnoreDir(relativeDir)) {
716
+ const conditionalEntry = this.templateConfig?.conditionalGeneration?.[relativeDir];
717
+ let shouldGenerate = true;
718
+ if (conditionalEntry) {
719
+ shouldGenerate = await isGenerationConditionMet(
720
+ this.templateConfig,
721
+ relativeDir,
722
+ this.templateParams,
723
+ this.asyncapiDocument
724
+ );
725
+ if (!shouldGenerate) {
726
+ log.debug(logMessage.conditionalGenerationMatched(relativeDir));
727
+ }
728
+ }
729
+ if (!shouldIgnoreDir(relativeDir) && shouldGenerate) {
718
730
  xfs.mkdirpSync(dirPath);
719
731
  }
720
732
  next();
@@ -854,7 +866,7 @@ class Generator {
854
866
  await writeFile(outputpath, renderContent);
855
867
  }
856
868
  }
857
-
869
+
858
870
  /**
859
871
  * Generates a file.
860
872
  *
@@ -867,34 +879,61 @@ class Generator {
867
879
  async generateFile(asyncapiDocument, fileName, baseDir) {
868
880
  const sourceFile = path.resolve(baseDir, fileName);
869
881
  const relativeSourceFile = path.relative(this.templateContentDir, sourceFile);
882
+ const relativeSourceDirectory = relativeSourceFile.split(path.sep)[0] || '.';
883
+
870
884
  const targetFile = path.resolve(this.targetDir, this.maybeRenameSourceFile(relativeSourceFile));
871
885
  const relativeTargetFile = path.relative(this.targetDir, targetFile);
872
-
886
+ let shouldGenerate = true;
887
+
873
888
  if (shouldIgnoreFile(relativeSourceFile)) return;
889
+
890
+ if (!(await this.shouldOverwriteFile(relativeTargetFile))) return;
891
+
892
+ // conditionalFiles becomes deprecated with this PR, and soon will be removed.
893
+ // TODO: https://github.com/asyncapi/generator/issues/1553
894
+ let conditionalPath = '';
895
+ if (
896
+ this.templateConfig.conditionalFiles &&
897
+ this.templateConfig.conditionalGeneration
898
+ ) {
899
+ log.debug(
900
+ 'Both \'conditionalFiles\' and \'conditionalGeneration\' are defined. Ignoring \'conditionalFiles\' and using \'conditionalGeneration\' only.'
901
+ );
902
+ }
874
903
 
875
- const shouldOverwriteFile = await this.shouldOverwriteFile(relativeTargetFile);
876
- if (!shouldOverwriteFile) return;
877
-
878
- if (this.templateConfig.conditionalFiles?.[relativeSourceFile]) {
879
- const server = this.templateParams.server && asyncapiDocument.servers().get(this.templateParams.server);
880
- const source = jmespath.search({
881
- ...asyncapiDocument.json(),
882
- ...{
883
- server: server ? server.json() : undefined,
884
- },
885
- }, this.templateConfig.conditionalFiles[relativeSourceFile].subject);
886
-
887
- if (!source) return log.debug(logMessage.relativeSourceFileNotGenerated(relativeSourceFile, this.templateConfig.conditionalFiles[relativeSourceFile].subject));
888
-
889
- if (source) {
890
- const validate = this.templateConfig.conditionalFiles[relativeSourceFile].validate;
891
- const valid = validate(source);
892
- if (!valid) return log.debug(logMessage.conditionalFilesMatched(relativeSourceFile));
904
+ if (this.templateConfig.conditionalGeneration?.[relativeSourceDirectory]) {
905
+ conditionalPath = relativeSourceDirectory;
906
+ } else if (this.templateConfig.conditionalGeneration?.[relativeSourceFile]) {
907
+ conditionalPath = relativeSourceFile;
908
+ } else
909
+ if (this.templateConfig.conditionalFiles?.[relativeSourceFile]) {
910
+ // conditionalFiles becomes deprecated with this PR, and soon will be removed.
911
+ // TODO: https://github.com/asyncapi/generator/issues/1553
912
+ conditionalPath = relativeSourceDirectory;
913
+ }
914
+
915
+ if (conditionalPath) {
916
+ shouldGenerate = await isGenerationConditionMet(
917
+ this.templateConfig,
918
+ conditionalPath,
919
+ this.templateParams,
920
+ asyncapiDocument
921
+ );
922
+ }
923
+
924
+ if (!shouldGenerate) {
925
+ if (this.templateConfig.conditionalFiles?.[relativeSourceFile]) {
926
+ // conditionalFiles becomes deprecated with this PR, and soon will be removed.
927
+ // TODO: https://github.com/asyncapi/generator/issues/1553
928
+ return log.debug(logMessage.conditionalFilesMatched(relativeSourceFile));
893
929
  }
930
+
931
+ return log.debug(logMessage.conditionalGenerationMatched(conditionalPath));
894
932
  }
895
-
933
+
896
934
  if (this.isNonRenderableFile(relativeSourceFile)) return await copyFile(sourceFile, targetFile);
897
935
  await this.renderAndWriteToFile(asyncapiDocument, sourceFile, targetFile);
936
+ log.debug(`Successfully rendered template and wrote file ${relativeSourceFile} to location: ${targetFile}`);
898
937
  }
899
938
 
900
939
  /**
@@ -942,10 +981,9 @@ class Generator {
942
981
  */
943
982
  isNonRenderableFile(fileName) {
944
983
  const nonRenderableFiles = this.templateConfig.nonRenderableFiles || [];
945
- if (!Array.isArray(nonRenderableFiles)) return false;
946
- if (nonRenderableFiles.some(globExp => minimatch(fileName, globExp))) return true;
947
- if (isReactTemplate(this.templateConfig) && !isJsFile(fileName)) return true;
948
- return false;
984
+ return Array.isArray(nonRenderableFiles) &&
985
+ (nonRenderableFiles.some(globExp => minimatch(fileName, globExp)) ||
986
+ (isReactTemplate(this.templateConfig) && !isJsFile(fileName)));
949
987
  }
950
988
 
951
989
  /**
@@ -968,19 +1006,34 @@ class Generator {
968
1006
  * @private
969
1007
  */
970
1008
  async loadTemplateConfig() {
1009
+ this.templateConfig = {};
1010
+
1011
+ // Try to load config from .ageneratorrc
1012
+ try {
1013
+ const rcConfigPath = path.resolve(this.templateDir, '.ageneratorrc');
1014
+ const yaml = await readFile(rcConfigPath, { encoding: 'utf8' });
1015
+ const yamlConfig = require('js-yaml').load(yaml);
1016
+ this.templateConfig = yamlConfig || {};
1017
+
1018
+ await this.loadDefaultValues();
1019
+ return;
1020
+ } catch (rcError) {
1021
+ // console.error('Could not load .ageneratorrc file:', rcError);
1022
+ log.debug('Could not load .ageneratorrc file:', rcError);
1023
+ // Continue to try package.json if .ageneratorrc fails
1024
+ }
1025
+
1026
+ // Try to load config from package.json
971
1027
  try {
972
1028
  const configPath = path.resolve(this.templateDir, CONFIG_FILENAME);
973
- if (!fs.existsSync(configPath)) {
974
- this.templateConfig = {};
975
- return;
976
- }
977
-
978
1029
  const json = await readFile(configPath, { encoding: 'utf8' });
979
1030
  const generatorProp = JSON.parse(json).generator;
980
1031
  this.templateConfig = generatorProp || {};
981
- } catch (e) {
982
- this.templateConfig = {};
1032
+ } catch (packageError) {
1033
+ // console.error('Could not load generator config from package.json:', packageError);
1034
+ log.debug('Could not load generator config from package.json:', packageError);
983
1035
  }
1036
+
984
1037
  await this.loadDefaultValues();
985
1038
  }
986
1039
 
@@ -1085,4 +1138,4 @@ class Generator {
1085
1138
  Generator.DEFAULT_TEMPLATES_DIR = DEFAULT_TEMPLATES_DIR;
1086
1139
  Generator.TRANSPILED_TEMPLATE_LOCATION = TRANSPILED_TEMPLATE_LOCATION;
1087
1140
 
1088
- module.exports = Generator;
1141
+ module.exports = Generator;
@@ -9,7 +9,7 @@ const { exists, registerTypeScript } = require('./utils');
9
9
  * @param {String} templateDir Directory where template is located.
10
10
  * @param {String} hooksDir Directory where local hooks are located.
11
11
  */
12
- module.exports.registerHooks = async (hooks, templateConfig, templateDir, hooksDir) => {
12
+ async function registerHooks (hooks, templateConfig, templateDir, hooksDir) {
13
13
  await registerLocalHooks(hooks, templateDir, hooksDir);
14
14
 
15
15
  if (templateConfig && Object.keys(templateConfig).length > 0) await registerConfigHooks(hooks, templateDir, templateConfig);
@@ -122,3 +122,10 @@ function addHook(hooks, mod, config) {
122
122
  });
123
123
  return hooks;
124
124
  }
125
+
126
+ module.exports = {
127
+ registerHooks,
128
+ registerLocalHooks,
129
+ registerConfigHooks,
130
+ addHook
131
+ };
@@ -22,7 +22,6 @@ function packageNotAvailable(packageDetails) {
22
22
  if (packageDetails?.pkgPath) {
23
23
  return `Unable to resolve template location at ${packageDetails.pkgPath}. Package is not available locally.`;
24
24
  }
25
-
26
25
  return `Template is not available locally and expected location is undefined. Known details are: ${JSON.stringify(packageDetails, null, 2)}`;
27
26
  }
28
27
 
@@ -42,6 +41,11 @@ function skipOverwrite(testFilePath) {
42
41
  return `Skipping overwrite for: ${testFilePath}`;
43
42
  }
44
43
 
44
+ function conditionalGenerationMatched(conditionalPath) {
45
+ return `${conditionalPath} was not generated because condition specified for this location in template configuration in conditionalGeneration matched.`;
46
+ }
47
+ // conditionalFiles becomes deprecated with this PR, and soon will be removed.
48
+ // TODO: https://github.com/asyncapi/generator/issues/1553
45
49
  function conditionalFilesMatched(relativeSourceFile) {
46
50
  return `${relativeSourceFile} was not generated because condition specified for this file in template configuration in conditionalFiles matched.`;
47
51
  }
@@ -62,6 +66,7 @@ module.exports = {
62
66
  installationDebugMessage,
63
67
  templateSuccessfullyInstalled,
64
68
  relativeSourceFileNotGenerated,
69
+ conditionalGenerationMatched,
65
70
  conditionalFilesMatched,
66
71
  compileEnabled,
67
72
  skipOverwrite
package/lib/parser.js CHANGED
@@ -50,6 +50,14 @@ parser.getProperApiDocument = (asyncapiDocument, templateConfig = {}) => {
50
50
  return ConvertDocumentParserAPIVersion(asyncapiDocument, apiVersion);
51
51
  };
52
52
 
53
+ /**
54
+ * Converts old parser options to the new format.
55
+ *
56
+ * @private - This function should not be used outside this module.
57
+ * @param {object} oldOptions - The old options to convert.
58
+ * @param {object} generator - The generator instance.
59
+ * @returns {object} The converted options.
60
+ */
53
61
  // The new options for the v2 Parser are different from those for the v1 version, but in order not to release Generator v2, we are converting the old options of Parser to the new ones.
54
62
  function convertOldOptionsToNew(oldOptions, generator) {
55
63
  if (!oldOptions) return;
@@ -143,3 +151,5 @@ function canReadFn(uri, canRead) {
143
151
  }
144
152
  return false;
145
153
  }
154
+
155
+ module.exports.convertOldOptionsToNew = convertOldOptionsToNew;
@@ -24,9 +24,14 @@ const supportedParserAPIMajorVersions = [
24
24
  * @return {Boolean}
25
25
  */
26
26
  module.exports.validateTemplateConfig = (templateConfig, templateParams, asyncapiDocument) => {
27
- const { parameters, supportedProtocols, conditionalFiles, generator, apiVersion } = templateConfig;
27
+ // conditionalFiles becomes deprecated with this PR, and soon will be removed.
28
+ // TODO: https://github.com/asyncapi/generator/issues/1553
29
+ const { parameters, supportedProtocols, conditionalFiles, conditionalGeneration, generator, apiVersion } = templateConfig;
28
30
 
31
+ // conditionalFiles becomes deprecated with this PR, and soon will be removed.
32
+ // TODO: https://github.com/asyncapi/generator/issues/1553
29
33
  validateConditionalFiles(conditionalFiles);
34
+ validateConditionalGeneration(conditionalGeneration);
30
35
  isTemplateCompatible(generator, apiVersion);
31
36
  isRequiredParamProvided(parameters, templateParams);
32
37
  isProvidedTemplateRendererSupported(templateConfig);
@@ -148,6 +153,8 @@ function isServerProvidedInDocument(server, paramsServerName) {
148
153
  if (typeof paramsServerName === 'string' && !server) throw new Error(`Couldn't find server with name ${paramsServerName}.`);
149
154
  }
150
155
 
156
+ // conditionalFiles becomes deprecated with this PR, and soon will be removed.
157
+ // TODO: https://github.com/asyncapi/generator/issues/1553
151
158
  /**
152
159
  * Checks if conditional files are specified properly in the template
153
160
  * @private
@@ -165,3 +172,25 @@ function validateConditionalFiles(conditionalFiles) {
165
172
  });
166
173
  }
167
174
  }
175
+
176
+ /**
177
+ * Validates conditionalGeneration settings in the template config.
178
+ * @private
179
+ * @param {Object} conditionalGeneration - The conditions specified in the template config.
180
+ */
181
+ function validateConditionalGeneration(conditionalGeneration) {
182
+ if (!conditionalGeneration || typeof conditionalGeneration !== 'object') return;
183
+
184
+ for (const [fileName, def] of Object.entries(conditionalGeneration)) {
185
+ const { subject, parameter, validation } = def;
186
+ if (subject && typeof subject !== 'string')
187
+ throw new Error(`Invalid 'subject' for ${fileName}: ${subject}`);
188
+ if (parameter && typeof parameter !== 'string')
189
+ throw new Error(`Invalid 'parameter' for ${fileName}: ${parameter}`);
190
+ if (subject && parameter)
191
+ throw new Error(`Both 'subject' and 'parameter' cannot be defined for ${fileName}`);
192
+ if (typeof validation !== 'object')
193
+ throw new Error(`Invalid 'validation' object for ${fileName}: ${validation}`);
194
+ def.validate = ajv.compile(validation);
195
+ }
196
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asyncapi/generator",
3
- "version": "2.6.0",
3
+ "version": "2.7.1",
4
4
  "description": "The AsyncAPI generator. It can generate documentation, code, anything!",
5
5
  "main": "./lib/generator.js",
6
6
  "bin": {
@@ -16,12 +16,11 @@
16
16
  "test:unit": "jest --coverage --testPathIgnorePatterns=integration --testPathIgnorePatterns=test-project",
17
17
  "test:dev": "npm run test:unit -- --watchAll",
18
18
  "test:integration": "npm run test:cleanup && jest --testPathPattern=integration --modulePathIgnorePatterns='./__mocks__(?!\\/loglevel\\.js$)'",
19
- "test:integration:update": "jest --updateSnapshot --testPathPattern=integration --modulePathIgnorePatterns='./__mocks__(?!\\/loglevel\\.js$)'",
19
+ "test:integration:update": "npm run test:integration -- -u",
20
20
  "test:cleanup": "rimraf \"test/temp\"",
21
21
  "docs": "jsdoc2md --partial docs/jsdoc2md-handlebars/custom-sig-name.hbs docs/jsdoc2md-handlebars/main.hbs docs/jsdoc2md-handlebars/docs.hbs docs/jsdoc2md-handlebars/header.hbs docs/jsdoc2md-handlebars/defaultvalue.hbs docs/jsdoc2md-handlebars/link.hbs docs/jsdoc2md-handlebars/params-table.hbs --files lib/generator.js > docs/api.md",
22
22
  "docker:build": "docker build -t asyncapi/generator:latest .",
23
23
  "lint": "eslint --max-warnings 0 --config ../../.eslintrc --ignore-path ../../.eslintignore .",
24
- "lint:tpl:validator": "eslint --fix --config ../../.eslintrc ../../.github/templates-list-validator",
25
24
  "generate:readme:toc": "markdown-toc -i README.md",
26
25
  "generate:assets": "npm run docs && npm run generate:readme:toc",
27
26
  "bump:version": "npm --no-git-tag-version --allow-same-version version $VERSION"
@@ -48,10 +47,10 @@
48
47
  "license": "Apache-2.0",
49
48
  "homepage": "https://github.com/asyncapi/generator",
50
49
  "dependencies": {
51
- "@asyncapi/generator-react-sdk": "^1.1.2",
50
+ "@asyncapi/generator-hooks": "*",
51
+ "@asyncapi/generator-react-sdk": "*",
52
52
  "@asyncapi/multi-parser": "^2.1.1",
53
53
  "@asyncapi/nunjucks-filters": "*",
54
- "@asyncapi/generator-hooks": "*",
55
54
  "@asyncapi/parser": "^3.0.14",
56
55
  "@npmcli/arborist": "5.6.3",
57
56
  "@npmcli/config": "^8.0.2",
@@ -51,7 +51,7 @@ describe('Generator', () => {
51
51
  expect(gen.forceWrite).toStrictEqual(true);
52
52
  expect(gen.install).toStrictEqual(true);
53
53
  expect(gen.compile).toStrictEqual(false);
54
- expect(() => gen.templateParams.test).toThrow('Template parameter "test" has not been defined in the package.json file under generator property. Please make sure it\'s listed there before you use it in your template.');
54
+ expect(() => gen.templateParams.test).toThrow('Template parameter "test" has not been defined in the Generator Configuration. Please make sure it\'s listed there before you use it in your template.');
55
55
 
56
56
  // Mock params on templateConfig so it doesn't fail.
57
57
  gen.templateConfig.parameters = { test: {} };
@@ -0,0 +1,173 @@
1
+ /**
2
+ * @jest-environment node
3
+ */
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const { addHook, registerLocalHooks, registerConfigHooks, registerHooks } = require('../lib/hooksRegistry');
7
+
8
+ jest.mock('fs');
9
+ jest.mock('path');
10
+
11
+ describe('hooksRegistry', () => {
12
+ let hooks;
13
+
14
+ beforeEach(() => {
15
+ hooks = {}; // reset hooks for each test
16
+ jest.clearAllMocks(); // Reset all mocks
17
+ jest.resetModules(); // Reset modules
18
+ });
19
+
20
+ describe('registerHooks', () => {
21
+ it('registers both local and config hooks', async () => {
22
+ const templateDir = path.join(__dirname, 'fixtures', 'template', 'hooks');
23
+ const hooksDir = path.join(__dirname, 'hooks');
24
+
25
+ fs.mkdirSync(hooksDir, { recursive: true });
26
+ fs.writeFileSync(path.join(hooksDir, 'preGenerate.js'), `
27
+ module.exports = function localPreGenerateHook() {};
28
+ `);
29
+
30
+ const templateConfig = {
31
+ hooks: {
32
+ '@asyncapi/hooks-module': ['configPreGenerateHook']
33
+ }
34
+ };
35
+
36
+ jest.mock('@asyncapi/hooks-module', () => ({
37
+ preGenerate: [function configPreGenerateHook() {}]
38
+ }), { virtual: true });
39
+
40
+ const result = await registerHooks(hooks, templateConfig, templateDir, 'hooks');
41
+
42
+ expect(result.preGenerate).toHaveLength(1);
43
+ expect(result.preGenerate[0].name).toBe('configPreGenerateHook');
44
+
45
+ fs.rmSync(hooksDir, { recursive: true, force: true });
46
+ });
47
+ });
48
+
49
+ describe('registerLocalHooks', () => {
50
+ const mockPreGenerateHook = function preGenerateHook() {};
51
+
52
+ beforeAll(() => {
53
+ path.join.mockImplementation((...args) => args.join('/'));
54
+ });
55
+
56
+ beforeEach(() => {
57
+ fs.existsSync.mockReturnValue(true);
58
+ fs.readdirSync.mockReturnValue(['preGenerate.js']);
59
+
60
+ jest.mock('fixtures/template/hooks/preGenerate.js', () => mockPreGenerateHook, { virtual: true });
61
+ });
62
+
63
+ it('handles missing hooks directory', async () => {
64
+ fs.existsSync.mockReturnValueOnce(false);
65
+
66
+ const result = await registerLocalHooks(hooks, '/non/existent/path', 'hooks');
67
+ expect(result).toBe(hooks);
68
+ expect(result.preGenerate).toBeUndefined();
69
+ });
70
+
71
+ it('handles errors during hook loading', async () => {
72
+ fs.existsSync.mockReturnValue(true);
73
+ fs.readdirSync.mockReturnValue(['errorHook.js']);
74
+
75
+ jest.mock('fixtures/template/hooks/errorHook.js', () => {
76
+ throw new Error('Mock import error');
77
+ }, { virtual: true });
78
+
79
+ await expect(registerLocalHooks(hooks, 'fixtures/template', 'hooks'))
80
+ .resolves.not.toThrow();
81
+
82
+ expect(hooks).toEqual({});
83
+ });
84
+ });
85
+
86
+ describe('registerConfigHooks', () => {
87
+ it('registers hooks from template config', async () => {
88
+ const templateDir = path.join(__dirname, 'fixtures', 'template');
89
+ const templateConfig = {
90
+ hooks: {
91
+ '@asyncapi/hooks-module': ['preGenerateHook']
92
+ }
93
+ };
94
+
95
+ // Mock the hook module
96
+ jest.mock('@asyncapi/hooks-module', () => ({
97
+ preGenerate: [function preGenerateHook() {}]
98
+ }), { virtual: true });
99
+
100
+ const result = await registerConfigHooks(hooks, templateDir, templateConfig);
101
+
102
+ expect(result.preGenerate).toHaveLength(1);
103
+ expect(result.preGenerate[0].name).toBe('preGenerateHook');
104
+ });
105
+
106
+ it('handles missing hooks in config', async () => {
107
+ const result = await registerConfigHooks(hooks, '', {});
108
+ expect(result).toBeUndefined();
109
+ expect(hooks).toEqual({});
110
+ });
111
+ });
112
+
113
+ describe('addHooks', () => {
114
+ it('adds hooks from module to hooks object', () => {
115
+ const mod = {
116
+ preGenerate: [function preGenerateHook() {}],
117
+ postGenerate: [function postGenerateHook() {}]
118
+ };
119
+
120
+ addHook(hooks, mod, null);
121
+
122
+ expect(hooks.preGenerate).toHaveLength(1);
123
+ expect(hooks.postGenerate).toHaveLength(1);
124
+ expect(hooks.preGenerate[0].name).toBe('preGenerateHook');
125
+ expect(hooks.postGenerate[0].name).toBe('postGenerateHook');
126
+ });
127
+
128
+ it('adds hooks from module.default if it exists', () => {
129
+ const mod = {
130
+ default: {
131
+ preGenerate: [function preGenerateHook() {}],
132
+ postGenerate: [function postGenerateHook() {}]
133
+ }
134
+ };
135
+
136
+ addHook(hooks, mod, null);
137
+
138
+ expect(hooks.preGenerate).toHaveLength(1);
139
+ expect(hooks.postGenerate).toHaveLength(1);
140
+ expect(hooks.preGenerate[0].name).toBe('preGenerateHook');
141
+ expect(hooks.postGenerate[0].name).toBe('postGenerateHook');
142
+ });
143
+
144
+ it('does not add hooks that are not in config', () => {
145
+ const mod = {
146
+ preGenerate: [function preGenerateHook() {}],
147
+ postGenerate: [function postGenerateHook() {}]
148
+ };
149
+ const config = ['preGenerateHook'];
150
+
151
+ addHook(hooks, mod, config);
152
+
153
+ expect(hooks.preGenerate).toHaveLength(1);
154
+ expect(hooks.postGenerate).toBeUndefined();
155
+ expect(hooks.preGenerate[0].name).toBe('preGenerateHook');
156
+ });
157
+
158
+ it('adds hooks that are in config', () => {
159
+ const mod = {
160
+ preGenerate: [function preGenerateHook() {}],
161
+ postGenerate: [function postGenerateHook() {}]
162
+ };
163
+ const config = ['preGenerateHook', 'postGenerateHook'];
164
+
165
+ addHook(hooks, mod, config);
166
+
167
+ expect(hooks.preGenerate).toHaveLength(1);
168
+ expect(hooks.postGenerate).toHaveLength(1);
169
+ expect(hooks.preGenerate[0].name).toBe('preGenerateHook');
170
+ expect(hooks.postGenerate[0].name).toBe('postGenerateHook');
171
+ });
172
+ });
173
+ });
@@ -153,4 +153,51 @@ describe('Integration testing generateFromFile() to make sure the result of the
153
153
  Include log message test in the future to ensure that the log.debug for skipping overwrite is called
154
154
  */
155
155
  });
156
+
157
+ it('should not generate the conditionalFolder if the singleFolder parameter is set true', async () => {
158
+ const outputDir = generateFolderName();
159
+ const generator = new Generator(reactTemplate, outputDir, {
160
+ forceWrite: true ,
161
+ templateParams: { version: 'v1', mode: 'production', singleFolder: 'true' }
162
+ });
163
+ await generator.generateFromFile(dummySpecPath);
164
+ const conditionalFolderPath = path.join(outputDir, 'conditionalFolder');
165
+ const exists = await access(conditionalFolderPath).then(() => true).catch(() => false);
166
+ expect(exists).toBe(false);
167
+ });
168
+ it('should not generate the conditionalFile if the singleFile parameter is set true', async () => {
169
+ const outputDir = generateFolderName();
170
+ const generator = new Generator(reactTemplate, outputDir, {
171
+ forceWrite: true ,
172
+ templateParams: { version: 'v1', mode: 'production', singleFile: 'true' }
173
+ });
174
+ await generator.generateFromFile(dummySpecPath);
175
+ const conditionalFilePath = path.join(outputDir, 'conditionalFile.txt');
176
+ const exists = await readFile(conditionalFilePath).then(() => true).catch(() => false);
177
+ expect(exists).toBe(false);
178
+ });
179
+
180
+ it('should generate the conditionalFile if the singleFile parameter is set false', async () => {
181
+ const outputDir = generateFolderName();
182
+ const generator = new Generator(reactTemplate, outputDir, {
183
+ forceWrite: true ,
184
+ templateParams: { version: 'v1', mode: 'production', singleFile: 'false' }
185
+ });
186
+ await generator.generateFromFile(dummySpecPath);
187
+ const conditionalFilePath = path.join(outputDir, 'conditionalFile.txt');
188
+ const exists = await readFile(conditionalFilePath).then(() => true).catch(() => false);
189
+ expect(exists).toBe(true);
190
+ });
191
+
192
+ it('should generate the conditionalFile if the singleFile parameter is set false using enum validation', async () => {
193
+ const outputDir = generateFolderName();
194
+ const generator = new Generator(reactTemplate, outputDir, {
195
+ forceWrite: true ,
196
+ templateParams: { version: 'v1', mode: 'production', singleFile: 'false' }
197
+ });
198
+ await generator.generateFromFile(dummySpecPath);
199
+ const conditionalFilePath = path.join(outputDir, 'conditionalFolder2/input.txt');
200
+ const exists = await readFile(conditionalFilePath).then(() => true).catch(() => false);
201
+ expect(exists).toBe(true);
202
+ });
156
203
  });