@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 +22 -0
- package/CODEOWNERS +1 -1
- package/README.md +3 -37
- package/cli.js +6 -3
- package/docs/README.md +2 -17
- package/docs/configuration-file.md +2 -0
- package/docs/hooks.md +3 -3
- package/lib/generator.js +69 -54
- package/lib/parser.js +123 -0
- package/lib/templateConfigValidator.js +28 -19
- package/lib/utils.js +8 -35
- package/package.json +5 -5
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
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.
|
|
68
|
-
|
|
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
|
-
|
|
1
|
+
## Documentation
|
|
2
2
|
|
|
3
|
-
|
|
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](
|
|
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](
|
|
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
|
-
|
|
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
|
|
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} [
|
|
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,
|
|
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
|
|
266
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
429
|
-
asyncapiDocument.
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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,
|
|
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,
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
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.
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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.
|
|
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": "^
|
|
50
|
+
"@asyncapi/avro-schema-parser": "^3.0.0",
|
|
51
51
|
"@asyncapi/generator-react-sdk": "^0.2.23",
|
|
52
|
-
"@asyncapi/openapi-schema-parser": "^
|
|
53
|
-
"@asyncapi/parser": "^
|
|
54
|
-
"@asyncapi/raml-dt-schema-parser": "^
|
|
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",
|