@asyncapi/generator 1.9.18 → 1.10.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.
package/.releaserc ADDED
@@ -0,0 +1,22 @@
1
+ ---
2
+ branches:
3
+ - master
4
+ # by default release workflow reacts on push not only to master.
5
+ #This is why out of the box sematic release is configured for all these branches
6
+ - name: next-spec
7
+ prerelease: true
8
+ - name: next-major
9
+ prerelease: true
10
+ - name: next-major-spec
11
+ prerelease: true
12
+ - name: beta
13
+ prerelease: true
14
+ - name: alpha
15
+ prerelease: true
16
+ plugins:
17
+ - - "@semantic-release/commit-analyzer"
18
+ - preset: conventionalcommits
19
+ - - "@semantic-release/release-notes-generator"
20
+ - preset: conventionalcommits
21
+ - "@semantic-release/npm"
22
+ - "@semantic-release/github"
package/CODEOWNERS CHANGED
@@ -9,4 +9,4 @@
9
9
  * @fmvilas @derberg @magicmatatjahu @jonaslagoni @asyncapi-bot-eve
10
10
 
11
11
  # All .md files
12
- *.md @Florence-Njeri @pratik2315
12
+ *.md @Florence-Njeri @pratik2315 @asyncapi-bot-eve
package/README.md CHANGED
@@ -10,12 +10,6 @@
10
10
 
11
11
  - [Overview](#overview)
12
12
  - [List of official generator templates](#list-of-official-generator-templates)
13
- - [Requirements](#requirements)
14
- - [Using from the command-line interface (CLI)](#using-from-the-command-line-interface-cli)
15
- * [CLI installation](#cli-installation)
16
- * [CLI usage](#cli-usage)
17
- - [Generator version vs Template version](#generator-version-vs-template-version)
18
- - [How to create a template](#how-to-create-a-template)
19
13
  - [Contributing](#contributing)
20
14
  - [Contributors ✨](#contributors-%E2%9C%A8)
21
15
 
@@ -23,9 +17,7 @@
23
17
 
24
18
  ## Overview
25
19
 
26
- Generator is a tool that you can use to generate whatever you want basing on the AsyncAPI specification file as an input.
27
-
28
- To specify what exactly must be generated you create so called **template**. To create your own template, go to section that explains [How to create a template](#how-to-create-a-template).
20
+ Generator is a tool that you can use to generate whatever you want basing on the AsyncAPI specification file as an input. For more information [read the docs](https://www.asyncapi.com/docs/tools/generator).
29
21
 
30
22
  There is a large number of templates that are ready to use and are officially supported by the AsyncAPI Initiative.
31
23
 
@@ -47,39 +39,13 @@ Template Name | Description | Source code
47
39
  `@asyncapi/ts-nats-template` | Generates TypeScript NATS client | [click here](https://github.com/asyncapi/ts-nats-template/)
48
40
  `@asyncapi/go-watermill-template` | Generates Go client using Watermill | [click here](https://github.com/asyncapi/go-watermill-template)
49
41
  `@asyncapi/dotnet-nats-template` | Generates .NET C# client using NATS | [click here](https://github.com/asyncapi/dotnet-nats-template)
42
+ `@asyncapi/php-template` | Generates PHP client using RabbitMQ | [click here](https://github.com/asyncapi/php-template)
43
+ `@asyncapi/dotnet-rabbitmq-template` | Generates .NET C# client using RabbitMQ | [click here](https://github.com/asyncapi/dotnet-rabbitmq-template)
50
44
 
51
45
  <!-- TEMPLATES-LIST:END -->
52
46
 
53
47
  You can find above templates and the ones provided by the community in **[this list](https://github.com/search?q=topic%3Aasyncapi+topic%3Agenerator+topic%3Atemplate)**
54
48
 
55
- ## Requirements
56
-
57
- * Node.js v12.16 and higher
58
- * npm v6.13.7 and higher
59
-
60
- Install both packages using [official installer](https://nodejs.org/en/download/). After installation make sure both packages have proper version by running `node -v` and `npm -v`. To upgrade invalid npm version run `npm install npm@latest -g`
61
-
62
- > The generator is tested at the moment against Node 14 and NPM 6. Using newer versions is enabled but we don't guarantee they work well. Please provide feedback via issues.
63
-
64
- ## Using from the command-line interface (CLI)
65
-
66
- ### CLI installation
67
- Learn to install the AsyncAPI CLI from the [installation guide](docs/installation-guide.md).
68
-
69
- ### CLI usage
70
-
71
- Learn more about different ways of using the CLI from the [usage document](docs/usage.md).
72
-
73
- ## Generator version vs Template version
74
-
75
- Learn more about versioning from the [versioning document](docs/versioning.md).
76
-
77
- ## How to create a template
78
-
79
- To create your own template, for example code generator for some specific language and technology, learn from the following resources:
80
- - Read the [documentation](docs/README.md)
81
- - Use [Template for Generator Templates](https://github.com/asyncapi/template-for-generator-templates) that showcases Generator features
82
-
83
49
  ## Contributing
84
50
 
85
51
  Read [CONTRIBUTING](CONTRIBUTING.md) guide.
package/cli.js CHANGED
@@ -4,6 +4,7 @@ const path = require('path');
4
4
  const os = require('os');
5
5
  const program = require('commander');
6
6
  const xfs = require('fs.extra');
7
+ const { DiagnosticSeverity } = require('@asyncapi/parser/cjs');
7
8
  const packageInfo = require('./package.json');
8
9
  const Generator = require('./lib/generator');
9
10
  const Watcher = require('./lib/watcher');
@@ -64,8 +65,10 @@ const mapBaseUrlParser = v => {
64
65
  const showError = err => {
65
66
  console.error(red('Something went wrong:'));
66
67
  console.error(red(err.stack || err.message));
67
- if (err.errors) console.error(red(JSON.stringify(err.errors)));
68
- if (err.validationErrors) console.error(red(JSON.stringify(err.validationErrors, null, 4)));
68
+ if (err.diagnostics) {
69
+ const errorDiagnostics = err.diagnostics.filter(diagnostic => diagnostic.severity === DiagnosticSeverity.Error);
70
+ console.error(red(`Errors:\n${JSON.stringify(errorDiagnostics, undefined, 2)}`));
71
+ }
69
72
  };
70
73
  const showErrorAndExit = err => {
71
74
  showError(err);
@@ -127,7 +130,7 @@ xfs.mkdirp(program.output, async err => {
127
130
  console.warn(`WARNING: ${template} is a remote template. Changes may be lost on subsequent installations.`);
128
131
  }
129
132
 
130
- watcher.watch(watcherHandler, (paths) => {
133
+ await watcher.watch(watcherHandler, (paths) => {
131
134
  showErrorAndExit({ message: `[WATCHER] Could not find the file path ${paths}, are you sure it still exists? If it has been deleted or moved please rerun the generator.` });
132
135
  });
133
136
  }
package/docs/README.md CHANGED
@@ -1,18 +1,3 @@
1
- Table of Contents
1
+ ## Documentation
2
2
 
3
- - [Introduction](index.md)
4
- - [Installation Guide](installation-guide.md)
5
- - [Usage](usage.md)
6
- - [AsyncAPI document](asyncapi-document.md)
7
- - [Template](template.md)
8
- - [Parser](parser.md)
9
- - [Generator version vs template version](versioning.md)
10
- - [Template Development](template-development.md)
11
- - [Configuration File](configuration-file.md)
12
- - [Template context](template-context.md)
13
- - [React render engine](react-render-engine.md)
14
- - [Nunjucks render engine](nunjucks-render-engine.md)
15
- - [Hooks](hooks.md)
16
- - [File templates](file-templates.md)
17
- - [TypeScript support](typescript-support.md)
18
- - [Special file names](special-file-names.md)
3
+ Read the docs on https://www.asyncapi.com/docs/tools/generator. You can still read the docs from here, but some of the links may be broken.
@@ -8,6 +8,7 @@ The `generator` property from `package.json` file must contain a JSON object tha
8
8
  |Name|Type|Description|
9
9
  |---|---|---|
10
10
  |`renderer`| String | Its value can be either `react` or `nunjucks` (default).
11
+ |`apiVersion`| String | Determines which **major** version of the [Parser-API](https://github.com/asyncapi/parser-api) the template uses. For example, `v1` for `v1.x.x`. If not specified, the Generator assumes the template is not compatible with the Parser-API so it will use the [Parser-JS v1 API](https://github.com/asyncapi/parser-js/tree/v1.18.1#api-documentation). If the template uses a version of the Parser-API that is not supported by the Generator, the Generator will throw an error.
11
12
  |`supportedProtocols`| [String] | A list with all the protocols this template supports.
12
13
  |`parameters`| Object[String, Object] | An object with all the parameters that can be passed when generating the template. When using the command line, it's done by indicating `--param name=value` or `-p name=value`.
13
14
  |`parameters[param].description`| String | A user-friendly description about the parameter.
@@ -27,6 +28,7 @@ The `generator` property from `package.json` file must contain a JSON object tha
27
28
  "generator":
28
29
  {
29
30
  "renderer": "react",
31
+ "apiVersion": "v1",
30
32
  "supportedProtocols": ["amqp", "mqtt"],
31
33
  "parameters": {
32
34
  "server": {
package/docs/hooks.md CHANGED
@@ -14,11 +14,11 @@ The following types of hooks are currently supported:
14
14
 
15
15
  The generator parses:
16
16
  - All the files in the `.hooks` directory inside the template.
17
- - All modules listed in the template configuration and triggers only hooks that names were added to the config. You can use the official AsyncAPI [hooks library](https://github.com/asyncapi/generator-hooks). To learn how to add hooks to configuration [read more about the configuration file](#configuration-file).
17
+ - All modules listed in the template configuration and triggers only hooks that names were added to the config. You can use the official AsyncAPI [hooks library](https://github.com/asyncapi/generator-hooks). To learn how to add hooks to configuration [read more about the configuration file](https://www.asyncapi.com/docs/tools/generator/configuration-file).
18
18
 
19
19
  ### Examples
20
20
 
21
- > Some of the examples have names of hook functions provided and some not. Keep in mind that hook functions kept in template in default location do not require a name. Name is required only if you keep hooks in non default location or in a separate library, because such hooks need to be explicitly configured in the configuration file. For more details on hooks configuration [read more about the configuration file](#configuration-file).
21
+ > Some of the examples have names of hook functions provided and some not. Keep in mind that hook functions kept in template in default location do not require a name. Name is required only if you keep hooks in non default location or in a separate library, because such hooks need to be explicitly configured in the configuration file. For more details on hooks configuration [read more about the configuration file](https://www.asyncapi.com/docs/tools/generator/configuration-file).
22
22
 
23
23
  Most basic modules with hooks look like this:
24
24
  ```js
@@ -78,4 +78,4 @@ module.exports = {
78
78
  return currentFilename.replace('-', '_')
79
79
  };
80
80
  };
81
- ```
81
+ ```
package/lib/generator.js CHANGED
@@ -2,18 +2,15 @@ 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 parser = require('@asyncapi/parser');
6
- const { configureReact, renderReact, saveRenderedReactContent } = require('./renderer/react');
7
- const { configureNunjucks, renderNunjucks } = require('./renderer/nunjucks');
8
- const { parse, AsyncAPIDocument } = parser;
9
- const ramlDtParser = require('@asyncapi/raml-dt-schema-parser');
10
- const openapiSchemaParser = require('@asyncapi/openapi-schema-parser');
11
- const avroSchemaParser = require('@asyncapi/avro-schema-parser');
12
5
  const jmespath = require('jmespath');
13
6
  const filenamify = require('filenamify');
14
7
  const git = require('simple-git');
15
8
  const log = require('loglevel');
16
9
  const Arborist = require('@npmcli/arborist');
10
+ const { isAsyncAPIDocument } = require('@asyncapi/parser/cjs/document');
11
+
12
+ const { configureReact, renderReact, saveRenderedReactContent } = require('./renderer/react');
13
+ const { configureNunjucks, renderNunjucks } = require('./renderer/nunjucks');
17
14
  const { validateTemplateConfig } = require('./templateConfigValidator');
18
15
  const {
19
16
  convertMapToObject,
@@ -30,8 +27,9 @@ const {
30
27
  registerSourceMap,
31
28
  registerTypeScript,
32
29
  getTemplateDetails,
33
- getMapBaseUrlToFolderResolver
30
+ convertCollectionToObject,
34
31
  } = require('./utils');
32
+ const { parse, usesNewAPI, getProperApiDocument } = require('./parser');
35
33
  const { registerFilters } = require('./filtersRegistry');
36
34
  const { registerHooks } = require('./hooksRegistry');
37
35
 
@@ -57,9 +55,6 @@ const shouldIgnoreDir = dirPath =>
57
55
  dirPath === '.git'
58
56
  || dirPath.startsWith(`.git${path.sep}`);
59
57
 
60
- parser.registerSchemaParser(openapiSchemaParser);
61
- parser.registerSchemaParser(ramlDtParser);
62
- parser.registerSchemaParser(avroSchemaParser);
63
58
  registerSourceMap();
64
59
  registerTypeScript();
65
60
 
@@ -164,10 +159,9 @@ class Generator {
164
159
  * @return {Promise}
165
160
  */
166
161
  async generate(asyncapiDocument) {
167
- if (!(asyncapiDocument instanceof AsyncAPIDocument)) throw new Error('Parameter "asyncapiDocument" must be an AsyncAPIDocument object.');
162
+ if (!isAsyncAPIDocument(asyncapiDocument)) throw new Error('Parameter "asyncapiDocument" must be an AsyncAPIDocument object.');
168
163
 
169
164
  this.asyncapi = asyncapiDocument;
170
-
171
165
  if (this.output === 'fs') {
172
166
  xfs.mkdirpSync(this.targetDir);
173
167
  if (!this.forceWrite) await this.verifyTargetDir(this.targetDir);
@@ -185,6 +179,9 @@ class Generator {
185
179
  validateTemplateConfig(this.templateConfig, this.templateParams, asyncapiDocument);
186
180
  await this.configureTemplate();
187
181
 
182
+ // use new or old document API based on `templateConfig.apiVersion` value
183
+ this.asyncapi = asyncapiDocument = getProperApiDocument(asyncapiDocument, this.templateConfig);
184
+
188
185
  if (!isReactTemplate(this.templateConfig)) {
189
186
  await registerFilters(this.nunjucks, this.templateConfig, this.templateDir, FILTERS_DIRNAME);
190
187
  }
@@ -252,18 +249,23 @@ class Generator {
252
249
  * }
253
250
  *
254
251
  * @param {String} asyncapiString AsyncAPI string to use as source.
255
- * @param {Object} [parserOptions={}] AsyncAPI parser options. Check out {@link https://www.github.com/asyncapi/parser-js|@asyncapi/parser} for more information.
252
+ * @param {Object} [parseOptions={}] AsyncAPI Parser parse options. Check out {@link https://www.github.com/asyncapi/parser-js|@asyncapi/parser} for more information.
256
253
  * @return {Promise}
257
254
  */
258
- async generateFromString(asyncapiString, parserOptions = {}) {
255
+ async generateFromString(asyncapiString, parseOptions = {}) {
259
256
  if (!asyncapiString || typeof asyncapiString !== 'string') throw new Error('Parameter "asyncapiString" must be a non-empty string.');
260
257
 
261
258
  /** @type {String} AsyncAPI string to use as a source. */
262
259
  this.originalAsyncAPI = asyncapiString;
263
260
 
264
261
  /** @type {AsyncAPIDocument} Parsed AsyncAPI schema. See {@link https://github.com/asyncapi/parser-js/blob/master/API.md#module_@asyncapi/parser+AsyncAPIDocument|AsyncAPIDocument} for details on object structure. */
265
- const asyncapi = await parse(asyncapiString, parserOptions);
266
- return this.generate(asyncapi);
262
+ const { document, diagnostics } = await parse(asyncapiString, parseOptions, this);
263
+ if (!document) {
264
+ const err = new Error('Input is not a corrent AsyncAPI document so it cannot be processed.');
265
+ err.diagnostics = diagnostics;
266
+ throw err;
267
+ }
268
+ return this.generate(document);
267
269
  }
268
270
 
269
271
  /**
@@ -290,12 +292,7 @@ class Generator {
290
292
  */
291
293
  async generateFromURL(asyncapiURL) {
292
294
  const doc = await fetchSpec(asyncapiURL);
293
- const parserOptions = {};
294
- if (this.mapBaseUrlToFolder.url) {
295
- parserOptions.resolve = {resolver: getMapBaseUrlToFolderResolver(this.mapBaseUrlToFolder)};
296
- }
297
-
298
- return this.generateFromString(doc, parserOptions);
295
+ return this.generateFromString(doc, { path: asyncapiURL });
299
296
  }
300
297
 
301
298
  /**
@@ -322,12 +319,7 @@ class Generator {
322
319
  */
323
320
  async generateFromFile(asyncapiFile) {
324
321
  const doc = await readFile(asyncapiFile, { encoding: 'utf8' });
325
- const parserOptions = { path: asyncapiFile };
326
- if (this.mapBaseUrlToFolder.url) {
327
- parserOptions.resolve = {resolver: getMapBaseUrlToFolderResolver(this.mapBaseUrlToFolder)};
328
- }
329
-
330
- return this.generateFromString(doc, parserOptions);
322
+ return this.generateFromString(doc, { path: asyncapiFile });
331
323
  }
332
324
 
333
325
  /**
@@ -425,18 +417,30 @@ class Generator {
425
417
  getAllParameters(asyncapiDocument) {
426
418
  const parameters = new Map();
427
419
 
428
- if (asyncapiDocument.hasChannels()) {
429
- asyncapiDocument.channelNames().forEach(channelName => {
430
- const channel = asyncapiDocument.channel(channelName);
431
- for (const [key, value] of Object.entries(channel.parameters())) {
432
- parameters.set(key, value);
433
- }
420
+ if (usesNewAPI(this.templateConfig)) {
421
+ asyncapiDocument.channels().all().forEach(channel => {
422
+ channel.parameters().all().forEach(parameter => {
423
+ parameters.set(parameter.id(), parameter);
424
+ });
434
425
  });
435
- }
436
426
 
437
- if (asyncapiDocument.hasComponents()) {
438
- for (const [key, value] of Object.entries(asyncapiDocument.components().parameters())) {
439
- parameters.set(key, value);
427
+ asyncapiDocument.components().channelParameters().all().forEach(parameter => {
428
+ parameters.set(parameter.id(), parameter);
429
+ });
430
+ } else {
431
+ if (asyncapiDocument.hasChannels()) {
432
+ asyncapiDocument.channelNames().forEach(channelName => {
433
+ const channel = asyncapiDocument.channel(channelName);
434
+ for (const [key, value] of Object.entries(channel.parameters())) {
435
+ parameters.set(key, value);
436
+ }
437
+ });
438
+ }
439
+
440
+ if (asyncapiDocument.hasComponents()) {
441
+ for (const [key, value] of Object.entries(asyncapiDocument.components().parameters())) {
442
+ parameters.set(key, value);
443
+ }
440
444
  }
441
445
  }
442
446
 
@@ -451,9 +455,6 @@ class Generator {
451
455
  * @return {Promise}
452
456
  */
453
457
  generateDirectoryStructure(asyncapiDocument) {
454
- const objectMap = {};
455
- asyncapiDocument.allSchemas().forEach((schema, schemaId) => { if (schema.type() === 'object') objectMap[schemaId] = schema; });
456
-
457
458
  return new Promise((resolve, reject) => {
458
459
  xfs.mkdirpSync(this.targetDir);
459
460
 
@@ -463,7 +464,7 @@ class Generator {
463
464
 
464
465
  walker.on('file', async (root, stats, next) => {
465
466
  try {
466
- await this.filesGenerationHandler(asyncapiDocument, objectMap, root, stats, next);
467
+ await this.filesGenerationHandler(asyncapiDocument, root, stats, next);
467
468
  } catch (e) {
468
469
  reject(e);
469
470
  }
@@ -514,16 +515,31 @@ class Generator {
514
515
  * @param {String} stats Information about the file.
515
516
  * @param {Function} next Callback function
516
517
  */
517
- async filesGenerationHandler(asyncapiDocument, objectMap, root, stats, next) {
518
- const fileNamesForSeparation = {
519
- channel: asyncapiDocument.channels(),
520
- message: convertMapToObject(asyncapiDocument.allMessages()),
521
- securityScheme: asyncapiDocument.components() ? asyncapiDocument.components().securitySchemes() : {},
522
- schema: asyncapiDocument.components() ? asyncapiDocument.components().schemas() : {},
523
- objectSchema: objectMap,
524
- parameter: convertMapToObject(this.getAllParameters(asyncapiDocument)),
525
- everySchema: convertMapToObject(asyncapiDocument.allSchemas()),
526
- };
518
+ async filesGenerationHandler(asyncapiDocument, root, stats, next) {
519
+ let fileNamesForSeparation = {};
520
+ if (usesNewAPI(this.templateConfig)) {
521
+ fileNamesForSeparation = {
522
+ channel: convertCollectionToObject(asyncapiDocument.channels().all(), 'address'),
523
+ message: convertCollectionToObject(asyncapiDocument.messages().all(), 'id'),
524
+ securityScheme: convertCollectionToObject(asyncapiDocument.components().securitySchemes().all(), 'id'),
525
+ schema: convertCollectionToObject(asyncapiDocument.components().schemas().all(), 'id'),
526
+ objectSchema: convertCollectionToObject(asyncapiDocument.schemas().all().filter(schema => schema.type() === 'object'), 'id'),
527
+ parameter: convertMapToObject(this.getAllParameters(asyncapiDocument)),
528
+ everySchema: convertCollectionToObject(asyncapiDocument.schemas().all(), 'id'),
529
+ };
530
+ } else {
531
+ const objectSchema = {};
532
+ asyncapiDocument.allSchemas().forEach((schema, schemaId) => { if (schema.type() === 'object') objectSchema[schemaId] = schema; });
533
+ fileNamesForSeparation = {
534
+ channel: asyncapiDocument.channels(),
535
+ message: convertMapToObject(asyncapiDocument.allMessages()),
536
+ securityScheme: asyncapiDocument.components() ? asyncapiDocument.components().securitySchemes() : {},
537
+ schema: asyncapiDocument.components() ? asyncapiDocument.components().schemas() : {},
538
+ objectSchema,
539
+ parameter: convertMapToObject(this.getAllParameters(asyncapiDocument)),
540
+ everySchema: convertMapToObject(asyncapiDocument.allSchemas()),
541
+ };
542
+ }
527
543
 
528
544
  // Check if the filename dictates it should be separated
529
545
  let wasSeparated = false;
@@ -663,7 +679,6 @@ class Generator {
663
679
  }
664
680
 
665
681
  if (this.isNonRenderableFile(relativeSourceFile)) return await copyFile(sourceFile, targetFile);
666
-
667
682
  await this.renderAndWriteToFile(asyncapiDocument, sourceFile, targetFile);
668
683
  }
669
684
 
package/lib/parser.js ADDED
@@ -0,0 +1,123 @@
1
+ const fs = require('fs');
2
+
3
+ const { Parser, convertToOldAPI } = require('@asyncapi/parser/cjs');
4
+ const { OpenAPISchemaParser } = require('@asyncapi/openapi-schema-parser');
5
+ const { AvroSchemaParser } = require('@asyncapi/avro-schema-parser');
6
+ const { RamlDTSchemaParser } = require('@asyncapi/raml-dt-schema-parser');
7
+
8
+ const parser = module.exports;
9
+
10
+ const defaultParser = new Parser({
11
+ schemaParsers: [
12
+ OpenAPISchemaParser(),
13
+ AvroSchemaParser(),
14
+ RamlDTSchemaParser(),
15
+ ],
16
+ });
17
+
18
+ parser.parse = (asyncapi, oldOptions, generator) => {
19
+ const options = convertOldOptionsToNew(oldOptions, generator);
20
+ return defaultParser.parse(asyncapi, options);
21
+ };
22
+
23
+ parser.usesNewAPI = (templateConfig = {}) => {
24
+ return templateConfig.apiVersion === 'v1';
25
+ };
26
+
27
+ parser.getProperApiDocument = (asyncapiDocument, templateConfig) => {
28
+ return parser.usesNewAPI(templateConfig) ? asyncapiDocument : convertToOldAPI(asyncapiDocument);
29
+ };
30
+
31
+ // 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.
32
+ function convertOldOptionsToNew(oldOptions, generator) {
33
+ if (!oldOptions) return;
34
+ const newOptions = {};
35
+
36
+ if (typeof oldOptions.path === 'string') {
37
+ newOptions.source = oldOptions.path;
38
+ }
39
+ if (typeof oldOptions.applyTraits === 'boolean') {
40
+ newOptions.applyTraits = oldOptions.applyTraits;
41
+ }
42
+
43
+ const resolvers = [];
44
+ if (generator && generator.mapBaseUrlToFolder && generator.mapBaseUrlToFolder.url) {
45
+ resolvers.push(...getMapBaseUrlToFolderResolvers(generator.mapBaseUrlToFolder));
46
+ }
47
+ if (oldOptions.resolve) {
48
+ resolvers.push(...convertOldResolvers(oldOptions.resolve));
49
+ }
50
+
51
+ if (resolvers.length) {
52
+ newOptions.__unstable = {};
53
+ newOptions.__unstable.resolver = {
54
+ resolvers,
55
+ };
56
+ }
57
+
58
+ return newOptions;
59
+ }
60
+
61
+ /**
62
+ * Creates a custom resolver that maps urlToFolder.url to urlToFolder.folder
63
+ * Building your custom resolver is explained here: https://apitools.dev/json-schema-ref-parser/docs/plugins/resolvers.html
64
+ *
65
+ * @private
66
+ * @param {object} urlToFolder to resolve url e.g. https://schema.example.com/crm/ to a folder e.g. ./test/docs/.
67
+ * @return {{read(*, *, *): Promise<unknown>, canRead(*): boolean, order: number}}
68
+ */
69
+ function getMapBaseUrlToFolderResolvers({ url: baseUrl, folder: baseDir }) {
70
+ const resolver = {
71
+ order: 1,
72
+ canRead: true,
73
+ read(uri) {
74
+ return new Promise(((resolve, reject) => {
75
+ const path = uri.toString();
76
+ const localpath = path.replace(baseUrl, baseDir);
77
+ try {
78
+ fs.readFile(localpath, (err, data) => {
79
+ if (err) {
80
+ reject(`Error opening file "${localpath}"`);
81
+ } else {
82
+ resolve(data.toString());
83
+ }
84
+ });
85
+ } catch (err) {
86
+ reject(`Error opening file "${localpath}"`);
87
+ }
88
+ }));
89
+ }
90
+ };
91
+
92
+ return [
93
+ { schema: 'http', ...resolver, },
94
+ { schema: 'https', ...resolver, },
95
+ ];
96
+ };
97
+
98
+ function convertOldResolvers(resolvers = {}) { // NOSONAR
99
+ if (Object.keys(resolvers).length === 0) return [];
100
+
101
+ return Object.entries(resolvers).map(([protocol, resolver]) => {
102
+ return {
103
+ schema: protocol,
104
+ order: resolver.order || 1,
105
+ canRead: (uri) => canReadFn(uri, resolver.canRead),
106
+ read: (uri) => {
107
+ return resolver.read({ url: uri.valueOf(), extension: uri.suffix() });
108
+ }
109
+ };
110
+ });
111
+ }
112
+
113
+ function canReadFn(uri, canRead) {
114
+ const value = uri.valueOf();
115
+ if (typeof canRead === 'boolean') return canRead;
116
+ if (typeof canRead === 'string') return canRead === value;
117
+ if (Array.isArray(canRead)) return canRead.includes(value);
118
+ if (canRead instanceof RegExp) return canRead.test(value);
119
+ if (typeof canRead === 'function') {
120
+ return canRead({ url: value, extension: uri.suffix() });
121
+ }
122
+ return false;
123
+ }
@@ -5,6 +5,11 @@ const levenshtein = require('levenshtein-edit-distance');
5
5
 
6
6
  const ajv = new Ajv({ allErrors: true });
7
7
 
8
+ // See https://github.com/asyncapi/parser-api
9
+ const supportedParserAPIMajorVersions = [
10
+ 'v1',
11
+ ];
12
+
8
13
  /**
9
14
  * Validates the template configuration.
10
15
  *
@@ -14,20 +19,19 @@ const ajv = new Ajv({ allErrors: true });
14
19
  * @return {Boolean}
15
20
  */
16
21
  module.exports.validateTemplateConfig = (templateConfig, templateParams, asyncapiDocument) => {
17
- const { parameters, supportedProtocols, conditionalFiles, generator } = templateConfig;
18
-
22
+ const { parameters, supportedProtocols, conditionalFiles, generator, apiVersion } = templateConfig;
23
+
19
24
  validateConditionalFiles(conditionalFiles);
20
- isTemplateCompatible(generator);
25
+ isTemplateCompatible(generator, apiVersion);
21
26
  isRequiredParamProvided(parameters, templateParams);
22
27
  isProvidedTemplateRendererSupported(templateConfig);
23
28
  if (asyncapiDocument) {
24
- const server = asyncapiDocument.server(templateParams.server);
29
+ const server = asyncapiDocument.servers().get(templateParams.server);
25
30
  isServerProvidedInDocument(server, templateParams.server);
26
31
  isServerProtocolSupported(server, supportedProtocols, templateParams.server);
27
32
  }
28
33
 
29
34
  isProvidedParameterSupported(parameters, templateParams);
30
-
31
35
  return true;
32
36
  };
33
37
 
@@ -35,24 +39,29 @@ module.exports.validateTemplateConfig = (templateConfig, templateParams, asyncap
35
39
  * Checks if template is compatible with the version of the generator that is used
36
40
  * @private
37
41
  * @param {String} generator Information about supported generator version that is part of the template configuration
42
+ * @param {String} generator Information about supported Parser-API version that is part of the template configuration
38
43
  */
39
- function isTemplateCompatible(generator) {
44
+ function isTemplateCompatible(generator, apiVersion) {
40
45
  const generatorVersion = getGeneratorVersion();
41
46
  if (typeof generator === 'string' && !semver.satisfies(generatorVersion, generator, {includePrerelease: true})) {
42
47
  throw new Error(`This template is not compatible with the current version of the generator (${generatorVersion}). This template is compatible with the following version range: ${generator}.`);
43
- }
48
+ }
49
+
50
+ if (typeof apiVersion === 'string' && !supportedParserAPIMajorVersions.includes(apiVersion)) {
51
+ throw new Error(`The version specified in apiVersion is not supported by this Generator version. Supported versions are: ${supportedParserAPIMajorVersions.toString()}`);
52
+ }
44
53
  }
45
54
 
46
55
  /**
47
56
  * Checks if parameters described in template configuration as required are passed to the generator
48
57
  * @private
49
58
  * @param {Object} configParams Parameters specified in template configuration
50
- * @param {Object} templateParams All parameters provided to generator
59
+ * @param {Object} templateParams All parameters provided to generator
51
60
  */
52
61
  function isRequiredParamProvided(configParams, templateParams) {
53
62
  const missingParams = Object.keys(configParams || {})
54
63
  .filter(key => configParams[key].required && !templateParams[key]);
55
-
64
+
56
65
  if (missingParams.length) {
57
66
  throw new Error(`This template requires the following missing params: ${missingParams}.`);
58
67
  }
@@ -75,29 +84,29 @@ function getParamSuggestion(wrongParam, configParams) {
75
84
  * Checks if parameters provided to generator is supported by the template
76
85
  * @private
77
86
  * @param {Object} configParams Parameters specified in template configuration
78
- * @param {Object} templateParams All parameters provided to generator
87
+ * @param {Object} templateParams All parameters provided to generator
79
88
  */
80
89
  function isProvidedParameterSupported(configParams, templateParams) {
81
90
  const wrongParams = Object.keys(templateParams || {}).filter(key => !configParams || !configParams[key]);
82
-
91
+
83
92
  if (!wrongParams.length) return;
84
93
  if (!configParams) throw new Error('This template doesn\'t have any params.');
85
94
 
86
95
  let suggestionsString = '';
87
-
88
- wrongParams.forEach(wp => {
96
+
97
+ wrongParams.forEach(wp => {
89
98
  suggestionsString += `\nDid you mean "${getParamSuggestion(wp,configParams)}" instead of "${wp}"?`;
90
99
  });
91
100
 
92
101
  throw new Error(`This template doesn't have the following params: ${wrongParams}.${suggestionsString}`);
93
102
  }
94
-
103
+
95
104
  /**
96
105
  * Checks if given AsyncAPI document has servers with protocol that is supported by the template
97
106
  * @private
98
107
  * @param {Object} server Server object from AsyncAPI file
99
108
  * @param {String[]} supportedProtocols Supported protocols specified in template configuration
100
- * @param {String} paramsServerName Name of the server specified as a param for the generator
109
+ * @param {String} paramsServerName Name of the server specified as a param for the generator
101
110
  */
102
111
  function isServerProtocolSupported(server, supportedProtocols, paramsServerName) {
103
112
  if (server && Array.isArray(supportedProtocols) && !supportedProtocols.includes(server.protocol())) {
@@ -107,7 +116,7 @@ function isServerProtocolSupported(server, supportedProtocols, paramsServerName)
107
116
 
108
117
  /**
109
118
  * Checks if the the provided renderer are supported (no renderer are also supported, defaults to nunjucks)
110
- *
119
+ *
111
120
  * @param {Object} templateConfig Template configuration.
112
121
  */
113
122
  function isProvidedTemplateRendererSupported(templateConfig) {
@@ -115,7 +124,7 @@ function isProvidedTemplateRendererSupported(templateConfig) {
115
124
  if (supportedRenderers.includes(templateConfig.renderer)) {
116
125
  return;
117
126
  }
118
-
127
+
119
128
  throw new Error(`We do not support '${templateConfig.renderer}' as a renderer for a template. Only 'react' or 'nunjucks' are supported.`);
120
129
  }
121
130
 
@@ -137,7 +146,7 @@ function isServerProvidedInDocument(server, paramsServerName) {
137
146
  function validateConditionalFiles(conditionalFiles) {
138
147
  if (typeof conditionalFiles === 'object') {
139
148
  const fileNames = Object.keys(conditionalFiles);
140
-
149
+
141
150
  fileNames.forEach(fileName => {
142
151
  const def = conditionalFiles[fileName];
143
152
  if (typeof def.subject !== 'string') throw new Error(`Invalid conditional file subject for ${fileName}: ${def.subject}.`);
@@ -145,4 +154,4 @@ function validateConditionalFiles(conditionalFiles) {
145
154
  conditionalFiles[fileName].validate = ajv.compile(conditionalFiles[fileName].validation);
146
155
  });
147
156
  }
148
- }
157
+ }
package/lib/utils.js CHANGED
@@ -7,6 +7,7 @@ const resolvePkg = require('resolve-pkg');
7
7
  const resolveFrom = require('resolve-from');
8
8
  const globalDirs = require('global-dirs');
9
9
  const log = require('loglevel');
10
+
10
11
  const packageJson = require('../package.json');
11
12
 
12
13
  const logMessage = require('./logMessages.js');
@@ -223,39 +224,11 @@ utils.getTemplateDetails = (name, PACKAGE_JSON_FILENAME) => {
223
224
  return installedPkg;
224
225
  };
225
226
 
226
- /**
227
- * Creates a custom resolver that maps urlToFolder.url to urlToFolder.folder
228
- * Building your custom resolver is explained here: https://apitools.dev/json-schema-ref-parser/docs/plugins/resolvers.html
229
- *
230
- * @private
231
- * @param {object} urlToFolder to resolve url e.g. https://schema.example.com/crm/ to a folder e.g. ./test/docs/.
232
- * @return {{read(*, *, *): Promise<unknown>, canRead(*): boolean, order: number}}
233
- */
234
- utils.getMapBaseUrlToFolderResolver = (urlToFolder) => {
235
- return {
236
- order: 1,
237
- canRead (file) {
238
- return true;
239
- },
240
- read(file, callback, $refs) {
241
- const baseUrl = urlToFolder.url;
242
- const baseDir = urlToFolder.folder;
243
-
244
- return new Promise(((resolve, reject) => {
245
- let localpath = file.url;
246
- localpath = localpath.replace(baseUrl,baseDir);
247
- try {
248
- fs.readFile(localpath, (err, data) => {
249
- if (err) {
250
- reject(`Error opening file "${localpath}"`);
251
- } else {
252
- resolve(data);
253
- }
254
- });
255
- } catch (err) {
256
- reject(`Error opening file "${localpath}"`);
257
- }
258
- }));
259
- }
260
- };
227
+ utils.convertCollectionToObject = (array, idFunction) => {
228
+ const tempObject = {};
229
+ for (const value of array) {
230
+ const id = value[idFunction]();
231
+ tempObject[id] = value;
232
+ }
233
+ return tempObject;
261
234
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asyncapi/generator",
3
- "version": "1.9.18",
3
+ "version": "1.10.0",
4
4
  "description": "The AsyncAPI generator. It can generate documentation, code, anything!",
5
5
  "main": "./lib/generator.js",
6
6
  "bin": {
@@ -47,11 +47,11 @@
47
47
  "license": "Apache-2.0",
48
48
  "homepage": "https://github.com/asyncapi/generator",
49
49
  "dependencies": {
50
- "@asyncapi/avro-schema-parser": "^1.1.0",
50
+ "@asyncapi/avro-schema-parser": "^3.0.0",
51
51
  "@asyncapi/generator-react-sdk": "^0.2.23",
52
- "@asyncapi/openapi-schema-parser": "^2.0.3",
53
- "@asyncapi/parser": "^1.18.0",
54
- "@asyncapi/raml-dt-schema-parser": "^2.0.1",
52
+ "@asyncapi/openapi-schema-parser": "^3.0.0",
53
+ "@asyncapi/parser": "^2.0.0",
54
+ "@asyncapi/raml-dt-schema-parser": "^4.0.0",
55
55
  "@npmcli/arborist": "^2.2.4",
56
56
  "ajv": "^6.10.2",
57
57
  "chokidar": "^3.4.0",