@asyncapi/generator 2.5.0 → 2.7.0

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 (50) hide show
  1. package/CHANGELOG.md +146 -0
  2. package/Dockerfile +7 -8
  3. package/cli.js +7 -0
  4. package/docs/api.md +12 -3
  5. package/docs/asyncapi-document.md +2 -1
  6. package/docs/configuration-file.md +112 -33
  7. package/docs/file-templates.md +2 -0
  8. package/docs/generator-template-java.md +560 -0
  9. package/docs/generator-template.md +62 -29
  10. package/docs/hooks.md +3 -3
  11. package/docs/installation-guide.md +5 -51
  12. package/docs/migration-cli.md +70 -0
  13. package/docs/migration-nunjucks-react.md +144 -0
  14. package/docs/nunjucks-render-engine.md +6 -2
  15. package/docs/template.md +2 -2
  16. package/docs/usage.md +14 -32
  17. package/docs/versioning.md +3 -1
  18. package/lib/conditionalGeneration.js +162 -0
  19. package/lib/filtersRegistry.js +5 -1
  20. package/lib/generator.js +96 -43
  21. package/lib/hooksRegistry.js +8 -1
  22. package/lib/logMessages.js +6 -1
  23. package/lib/parser.js +10 -0
  24. package/lib/renderer/nunjucks.js +2 -2
  25. package/lib/templateConfigValidator.js +30 -1
  26. package/package.json +4 -6
  27. package/test/generator.test.js +1 -1
  28. package/test/hooksRegistry.test.js +173 -0
  29. package/test/integration.test.js +51 -4
  30. package/test/parser.test.js +100 -2
  31. package/test/templateConfigValidator.test.js +18 -1
  32. package/test/test-project/README.md +5 -1
  33. package/test/test-project/docker-compose.yml +7 -15
  34. package/test/test-project/package.json +0 -1
  35. package/test/test-project/test-project.test.js +6 -2
  36. package/test/test-project/test-registry.test.js +7 -2
  37. package/test/test-project/test.sh +6 -1
  38. package/test/test-project/verdaccio/config.yaml +6 -0
  39. package/test/test-templates/nunjucks-template/package-lock.json +110 -183
  40. package/test/test-templates/react-template/.ageneratorrc +33 -0
  41. package/test/test-templates/react-template/package.json +5 -20
  42. package/test/test-templates/react-template/template/conditionalFile.txt +0 -0
  43. package/test/test-templates/react-template/template/conditionalFolder/conditionalFile.txt +0 -0
  44. package/test/test-templates/react-template/template/conditionalFolder2/input.txt +0 -0
  45. package/test/test-templates/react-template/template/models.js +6 -0
  46. package/test/utils.test.js +53 -0
  47. package/test/test-project/test-entrypoint.sh +0 -12
  48. package/test/test-templates/react-template/__transpiled/test-file.md.js +0 -24
  49. package/test/test-templates/react-template/__transpiled/test-file.md.js.map +0 -1
  50. package/test/test-templates/react-template/package-lock.json +0 -4135
@@ -0,0 +1,162 @@
1
+ const log = require('loglevel');
2
+ const logMessage = require('./logMessages');
3
+ const jmespath = require('jmespath');
4
+
5
+ /**
6
+ * Determines whether the generation of a file or folder should be skipped
7
+ * based on conditions defined in the template configuration.
8
+ *
9
+ * @param {Object} templateConfig - The template configuration containing conditional logic.
10
+ * @param {string} matchedConditionPath - The matched path used to find applicable conditions.
11
+ * @param {Object} templateParams - Parameters passed to the template.
12
+ * @param {AsyncAPIDocument} asyncapiDocument - The AsyncAPI document used for evaluating conditions.
13
+ * @returns {Promise<boolean>} A promise that resolves to `true` if the condition is met, allowing the file or folder to render; otherwise, resolves to `false`.
14
+ */
15
+ async function isGenerationConditionMet (
16
+ templateConfig,
17
+ matchedConditionPath,
18
+ templateParams,
19
+ asyncapiDocument
20
+ ) {
21
+ const conditionFilesGeneration = templateConfig?.conditionalFiles?.[matchedConditionPath] || {};
22
+ const conditionalGeneration = templateConfig?.conditionalGeneration?.[matchedConditionPath] || {};
23
+
24
+ const config = Object.keys(conditionFilesGeneration).length > 0
25
+ ? conditionFilesGeneration
26
+ : conditionalGeneration;
27
+
28
+ const subject = config?.subject;
29
+
30
+ // conditionalFiles becomes deprecated with this PR, and soon will be removed.
31
+ // TODO: https://github.com/asyncapi/generator/issues/1553
32
+ if (Object.keys(conditionFilesGeneration).length > 0 && subject) {
33
+ return conditionalFilesGenerationDeprecatedVersion(
34
+ asyncapiDocument,
35
+ templateConfig,
36
+ matchedConditionPath,
37
+ templateParams
38
+ );
39
+ } else if (Object.keys(conditionalGeneration).length > 0) {
40
+ // Case when the subject is present in conditionalGeneration
41
+ if (subject) {
42
+ return conditionalSubjectGeneration(
43
+ asyncapiDocument,
44
+ templateConfig,
45
+ matchedConditionPath
46
+ );
47
+ }
48
+ return conditionalParameterGeneration(templateConfig,matchedConditionPath,templateParams);
49
+ }
50
+ };
51
+
52
+ /**
53
+ * Evaluates whether a template path should be conditionally generated
54
+ * based on a parameter defined in the template configuration.
55
+ * @private
56
+ * @async
57
+ * @function conditionalParameterGeneration
58
+ * @param {Object} templateConfig - The full template configuration object.
59
+ * @param {string} matchedConditionPath - The path of the file/folder being conditionally generated.
60
+ * @param {Object} templateParams - The parameters passed to the generator, usually user input or default values.
61
+ * @returns {Promise<boolean>} - Resolves to `true` if the parameter passes validation, `false` otherwise.
62
+ */
63
+ async function conditionalParameterGeneration(templateConfig, matchedConditionPath, templateParams) {
64
+ const conditionalGenerationConfig = templateConfig.conditionalGeneration?.[matchedConditionPath];
65
+ const parameterName = conditionalGenerationConfig.parameter;
66
+ const parameterValue = templateParams[parameterName];
67
+ return validateStatus(parameterValue, matchedConditionPath, templateConfig);
68
+ }
69
+
70
+ /**
71
+ * Determines whether a file should be conditionally included based on the provided subject expression
72
+ * and optional validation logic defined in the template configuration.
73
+ * @private
74
+ * @param {Object} asyncapiDocument - The parsed AsyncAPI document instance used for context evaluation.
75
+ * @param {Object} templateConfig - The configuration object that contains `conditionalFiles` rules.
76
+ * @param {string} matchedConditionPath - The path of the file/folder being conditionally generated.
77
+ * @param {Object} templateParams - The parameters passed to the generator, usually user input or default values.
78
+ * @returns {Boolean} - Returns `true` if the file should be included; `false` if it should be skipped.
79
+ */
80
+ async function conditionalFilesGenerationDeprecatedVersion (
81
+ asyncapiDocument,
82
+ templateConfig,
83
+ matchedConditionPath,
84
+ templateParams
85
+ ) {
86
+ return conditionalSubjectGeneration(asyncapiDocument, templateConfig, matchedConditionPath, templateParams);
87
+ };
88
+
89
+ /**
90
+ * Determines whether a file should be conditionally included based on the provided subject expression
91
+ * and optional validation logic defined in the template configuration.
92
+ * @private
93
+ * @param {Object} asyncapiDocument - The parsed AsyncAPI document instance used for context evaluation.
94
+ * @param {Object} templateConfig - The configuration object that contains `conditionalFiles` rules.
95
+ * @param {String} matchedConditionPath - The relative path to the directory of the source file.
96
+ * @param {Object} templateParams - Parameters passed to the template.
97
+ * @returns {Boolean} - Returns `true` if the file should be included; `false` if it should be skipped.
98
+ */
99
+ async function conditionalSubjectGeneration (
100
+ asyncapiDocument,
101
+ templateConfig,
102
+ matchedConditionPath,
103
+ templateParams
104
+
105
+ ) {
106
+ const fileCondition = templateConfig.conditionalGeneration?.[matchedConditionPath] || templateConfig.conditionalFiles?.[matchedConditionPath];
107
+ if (!fileCondition || !fileCondition.subject) {
108
+ return true;
109
+ }
110
+ const { subject } = fileCondition;
111
+ const server = templateParams.server && asyncapiDocument.servers().get(templateParams.server);
112
+ const source = jmespath.search({
113
+ ...asyncapiDocument.json(),
114
+ ...{
115
+ server: server ? server.json() : undefined,
116
+ },
117
+ }, subject);
118
+
119
+ if (!source) {
120
+ log.debug(logMessage.relativeSourceFileNotGenerated(matchedConditionPath, subject));
121
+ return false;
122
+ }
123
+ return validateStatus(source, matchedConditionPath, templateConfig);
124
+ }
125
+
126
+ /**
127
+ * Validates the argument value based on the provided validation schema.
128
+ *
129
+ * @param {any} argument The value to validate.
130
+ * @param {String} matchedConditionPath The matched condition path.
131
+ * @param {Object} templateConfig - The template configuration containing conditional logic.
132
+ * @return {Promise<Boolean>} A promise that resolves to false if the generation should be skipped, true otherwise.
133
+ */
134
+ async function validateStatus(
135
+ argument,
136
+ matchedConditionPath,
137
+ templateConfig
138
+ ) {
139
+ const validation = templateConfig.conditionalGeneration?.[matchedConditionPath]?.validate || templateConfig.conditionalFiles?.[matchedConditionPath]?.validate;
140
+ if (!validation) {
141
+ return false;
142
+ }
143
+
144
+ const isValid = validation(argument);
145
+
146
+ if (!isValid) {
147
+ if (templateConfig.conditionalGeneration?.[matchedConditionPath]) {
148
+ log.debug(logMessage.conditionalGenerationMatched(matchedConditionPath));
149
+ } else {
150
+ // conditionalFiles becomes deprecated with this PR, and soon will be removed.
151
+ // TODO: https://github.com/asyncapi/generator/issues/1553
152
+ log.debug(logMessage.conditionalFilesMatched(matchedConditionPath));
153
+ }
154
+
155
+ return false;
156
+ }
157
+ return true;
158
+ }
159
+
160
+ module.exports = {
161
+ isGenerationConditionMet
162
+ };
@@ -6,6 +6,7 @@ const nunjucksFilters = require('@asyncapi/nunjucks-filters');
6
6
 
7
7
  /**
8
8
  * Registers all template filters.
9
+ * @deprecated This method is deprecated. For more details, see the release notes: https://github.com/asyncapi/generator/releases/tag/%40asyncapi%2Fgenerator%402.6.0
9
10
  * @param {Object} nunjucks Nunjucks environment.
10
11
  * @param {Object} templateConfig Template configuration.
11
12
  * @param {String} templateDir Directory where template is located.
@@ -15,12 +16,13 @@ module.exports.registerFilters = async (nunjucks, templateConfig, templateDir, f
15
16
  await registerLocalFilters(nunjucks, templateDir, filtersDir);
16
17
  registerConfigFilters(nunjucks, templateDir, templateConfig);
17
18
 
18
- // Register Nunjucks filters from the 'nunjucks-filters' module without needing to list them explicitly in package.json
19
+ // Register Nunjucks filters from the 'nunjucks-filters' module without needing to list them in package.json or .ageneratorrc file.
19
20
  addFilters(nunjucks, nunjucksFilters);
20
21
  };
21
22
 
22
23
  /**
23
24
  * Registers the local template filters.
25
+ * @deprecated This method is deprecated. For more details, see the release notes: https://github.com/asyncapi/generator/releases/tag/%40asyncapi%2Fgenerator%402.6.0
24
26
  * @private
25
27
  * @param {Object} nunjucks Nunjucks environment.
26
28
  * @param {String} templateDir Directory where template is located.
@@ -68,6 +70,7 @@ function registerLocalFilters(nunjucks, templateDir, filtersDir) {
68
70
 
69
71
  /**
70
72
  * Registers the additionally configured filters.
73
+ * @deprecated This method is deprecated. For more details, see the release notes: https://github.com/asyncapi/generator/releases/tag/%40asyncapi%2Fgenerator%402.6.0
71
74
  * @private
72
75
  * @param {Object} nunjucks Nunjucks environment.
73
76
  * @param {String} templateDir Directory where template is located.
@@ -112,6 +115,7 @@ async function registerConfigFilters(nunjucks, templateDir, templateConfig) {
112
115
 
113
116
  /**
114
117
  * Add filter functions to Nunjucks environment. Only owned functions from the module are added.
118
+ * @deprecated This method is deprecated. For more details, see the release notes: https://github.com/asyncapi/generator/releases/tag/%40asyncapi%2Fgenerator%402.6.0
115
119
  * @private
116
120
  * @param {Object} nunjucks Nunjucks environment.
117
121
  * @param {Object} filters Module with functions.
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
  }
@@ -436,7 +435,7 @@ class Generator {
436
435
  * @param {String} asyncapiString AsyncAPI string to use as source.
437
436
  * @param {Object} [parseOptions={}] AsyncAPI Parser parse options. Check out {@link https://www.github.com/asyncapi/parser-js|@asyncapi/parser} for more information.
438
437
  * @deprecated Use the `generate` function instead. Just change the function name and it works out of the box.
439
- * @return {Promise}
438
+ * @return {Promise<TemplateRenderResult|undefined>}
440
439
  */
441
440
  async generateFromString(asyncapiString, parseOptions = {}) {
442
441
  const isParsableCompatible = asyncapiString && typeof asyncapiString === 'string';
@@ -466,7 +465,7 @@ class Generator {
466
465
  * }
467
466
  *
468
467
  * @param {String} asyncapiURL Link to AsyncAPI file
469
- * @return {Promise}
468
+ * @return {Promise<TemplateRenderResult|undefined>}
470
469
  */
471
470
  async generateFromURL(asyncapiURL) {
472
471
  const doc = await fetchSpec(asyncapiURL);
@@ -493,7 +492,7 @@ class Generator {
493
492
  * }
494
493
  *
495
494
  * @param {String} asyncapiFile AsyncAPI file to use as source.
496
- * @return {Promise}
495
+ * @return {Promise<TemplateRenderResult|undefined>}
497
496
  */
498
497
  async generateFromFile(asyncapiFile) {
499
498
  const doc = await readFile(asyncapiFile, { encoding: 'utf8' });
@@ -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;
@@ -3,7 +3,7 @@ const nunjucksExport = module.exports;
3
3
 
4
4
  /**
5
5
  * Configures Nunjucks templating system
6
- *
6
+ * @deprecated This method is deprecated. For more details, see the release notes: https://github.com/asyncapi/generator/releases/tag/%40asyncapi%2Fgenerator%402.6.0
7
7
  * @private
8
8
  * @param {boolean} debug flag
9
9
  * @param {string} templateDir path
@@ -17,7 +17,7 @@ nunjucksExport.configureNunjucks = (debug, templateDir) => {
17
17
 
18
18
  /**
19
19
  * Renders the template with nunjucks and returns a string.
20
- *
20
+ * @deprecated This method is deprecated. For more details, see the release notes: https://github.com/asyncapi/generator/releases/tag/%40asyncapi%2Fgenerator%402.6.0
21
21
  * @param {import('@asyncapi/parser').AsyncAPIDocument} asyncapiDocument
22
22
  * @param {string} templateString template filecontent to be rendered with nunjucks
23
23
  * @param {string} filePath path to the template file
@@ -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.5.0",
3
+ "version": "2.7.0",
4
4
  "description": "The AsyncAPI generator. It can generate documentation, code, anything!",
5
5
  "main": "./lib/generator.js",
6
6
  "bin": {
@@ -12,17 +12,15 @@
12
12
  "npm": ">=8.19.0"
13
13
  },
14
14
  "scripts": {
15
- "test": "npm run test:unit && npm run test:integration && npm run test:cli",
15
+ "test": "npm run test:unit && npm run test:integration",
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
19
  "test:integration:update": "jest --updateSnapshot --testPathPattern=integration --modulePathIgnorePatterns='./__mocks__(?!\\/loglevel\\.js$)'",
20
- "test:cli": "node cli.js ./test/docs/dummy.yml ./test/test-templates/react-template -o test/output --force-write --debug && test -e test/output/test-file.md",
21
20
  "test:cleanup": "rimraf \"test/temp\"",
22
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",
23
22
  "docker:build": "docker build -t asyncapi/generator:latest .",
24
23
  "lint": "eslint --max-warnings 0 --config ../../.eslintrc --ignore-path ../../.eslintignore .",
25
- "lint:tpl:validator": "eslint --fix --config ../../.eslintrc ../../.github/templates-list-validator",
26
24
  "generate:readme:toc": "markdown-toc -i README.md",
27
25
  "generate:assets": "npm run docs && npm run generate:readme:toc",
28
26
  "bump:version": "npm --no-git-tag-version --allow-same-version version $VERSION"
@@ -49,10 +47,10 @@
49
47
  "license": "Apache-2.0",
50
48
  "homepage": "https://github.com/asyncapi/generator",
51
49
  "dependencies": {
50
+ "@asyncapi/generator-hooks": "*",
52
51
  "@asyncapi/generator-react-sdk": "^1.1.2",
53
52
  "@asyncapi/multi-parser": "^2.1.1",
54
53
  "@asyncapi/nunjucks-filters": "*",
55
- "@asyncapi/generator-hooks": "*",
56
54
  "@asyncapi/parser": "^3.0.14",
57
55
  "@npmcli/arborist": "5.6.3",
58
56
  "@npmcli/config": "^8.0.2",
@@ -82,7 +80,7 @@
82
80
  "eslint-plugin-jest": "^23.8.2",
83
81
  "eslint-plugin-react": "^7.34.1",
84
82
  "eslint-plugin-sonarjs": "^0.5.0",
85
- "fs-extra": "9.1.0",
83
+ "fs-extra": "11.2.0",
86
84
  "jest": "^27.3.1",
87
85
  "jsdoc-to-markdown": "^7.1.1",
88
86
  "markdown-toc": "^1.2.0",
@@ -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: {} };