@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.
- package/CHANGELOG.md +147 -0
- package/Dockerfile +7 -8
- package/docs/asyncapi-document.md +2 -1
- package/docs/configuration-file.md +112 -33
- package/docs/generator-template-java.md +560 -0
- package/docs/generator-template.md +62 -29
- package/docs/hooks.md +3 -3
- package/docs/installation-guide.md +5 -51
- package/docs/migration-cli.md +5 -5
- package/docs/migration-nunjucks-react.md +1 -1
- package/docs/nunjucks-render-engine.md +4 -2
- package/docs/template.md +2 -2
- package/docs/usage.md +14 -32
- package/docs/versioning.md +3 -1
- package/lib/conditionalGeneration.js +162 -0
- package/lib/filtersRegistry.js +1 -1
- package/lib/generator.js +93 -40
- package/lib/hooksRegistry.js +8 -1
- package/lib/logMessages.js +6 -1
- package/lib/parser.js +10 -0
- package/lib/templateConfigValidator.js +30 -1
- package/package.json +4 -5
- package/test/generator.test.js +1 -1
- package/test/hooksRegistry.test.js +173 -0
- package/test/integration.test.js +47 -0
- package/test/parser.test.js +100 -2
- package/test/templateConfigValidator.test.js +18 -1
- package/test/test-project/README.md +5 -1
- package/test/test-project/docker-compose.yml +7 -15
- package/test/test-project/test-project.test.js +6 -2
- package/test/test-project/test-registry.test.js +7 -2
- package/test/test-project/test.sh +1 -1
- package/test/test-templates/nunjucks-template/package-lock.json +43 -119
- package/test/test-templates/nunjucks-template/package.json +1 -1
- package/test/test-templates/react-template/.ageneratorrc +33 -0
- package/test/test-templates/react-template/package.json +5 -21
- package/test/test-templates/react-template/template/conditionalFile.txt +0 -0
- package/test/test-templates/react-template/template/conditionalFolder/conditionalFile.txt +0 -0
- package/test/test-templates/react-template/template/conditionalFolder2/input.txt +0 -0
- package/test/utils.test.js +53 -0
- 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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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
|
-
|
|
946
|
-
|
|
947
|
-
|
|
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 (
|
|
982
|
-
|
|
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;
|
package/lib/hooksRegistry.js
CHANGED
|
@@ -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
|
-
|
|
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
|
+
};
|
package/lib/logMessages.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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": "
|
|
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-
|
|
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",
|
package/test/generator.test.js
CHANGED
|
@@ -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
|
|
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
|
+
});
|
package/test/integration.test.js
CHANGED
|
@@ -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
|
});
|