@asyncapi/generator 1.15.9 → 1.17.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/docs/template.md CHANGED
@@ -16,7 +16,7 @@ Examples outputs:
16
16
 
17
17
  A template is an independent Node.js project unrelated to the `generator` repository. AsyncAPI templates are managed, released, and published separately. You can also create templates and manage templates on your own.
18
18
 
19
- The generator uses the official [Arborist](https://www.npmjs.com/package/@npmcli/arborist) NPM library. (This means templates do not have to be published to package managers to use them.) Arborist helps the generator fetch the template's source code and use it for the generation process.
19
+ The generator uses the official [Arborist](https://www.npmjs.com/package/@npmcli/arborist) NPM library. (This means templates do not have to be published to package managers to use them.) Arborist helps the generator fetch the template's source code and use it for the generation process. By default, this library pulls data from the default NPM registry, which is https://registry.npmjs.org. You can also configure the generator to fetch templates that are private or hosted in different NPM registry
20
20
 
21
21
  You can store template projects on a local drive or as a `git` repository during the development process.
22
22
 
@@ -0,0 +1,29 @@
1
+ ---
2
+ title: "Using private templates"
3
+ weight: 180
4
+ ---
5
+ Generator allows fetching the template from private repositories like Verdaccio, Nexus, npm, etc.
6
+
7
+
8
+ ## Private registry options:
9
+
10
+ * **registry.url**: The URL of the registry where the private template is located. Defaults to `registry.npmjs.org`.
11
+ * **registry.auth**: An optional parameter to pass the npm registry username and password encoded with base64, formatted as `username:password`. For example, if the username and password are `admin` and `nimda`, you need to encode them with the base64 value like `admin:nimda` which results in `YWRtaW46bmltZGE=`.
12
+ **registry.token**: An optional parameter to pass to the npm registry authentication token. To get the token, you can first authenticate with the registry using `npm login` and then grab the generated token from the `.npmrc` file.
13
+
14
+ ## Pulling private template using library:
15
+
16
+ ```javascript
17
+ const generator = new Generator('@asyncapi/html-template', 'output',
18
+ {
19
+ debug: true,
20
+ registry: {
21
+ url: 'http://verdaccio:4873',
22
+ auth: 'YWRtaW46bmltZGE='
23
+ // base64 encoded username and password
24
+ // represented as admin:nimda
25
+
26
+ }
27
+ });
28
+ ```
29
+ Assuming you host `@asyncapi/html-template` in a private package registry like Verdaccio. To pull this template, you need to provide `registry.url` option that points to the registry URL and `registry.auth` as a base64 encoded value that represents the username and password. Instead of username and password, you can also pass `registry.token`.
@@ -12,7 +12,7 @@ const { isAsyncFunction } = require('./utils');
12
12
  */
13
13
  module.exports.registerFilters = async (nunjucks, templateConfig, templateDir, filtersDir) => {
14
14
  await registerLocalFilters(nunjucks, templateDir, filtersDir);
15
- await registerConfigFilters(nunjucks, templateDir, templateConfig);
15
+ registerConfigFilters(nunjucks, templateDir, templateConfig);
16
16
  };
17
17
 
18
18
  /**
@@ -71,16 +71,31 @@ async function registerConfigFilters(nunjucks, templateDir, templateConfig) {
71
71
 
72
72
  const promises = confFilters.map(async filtersModule => {
73
73
  let mod;
74
+ let filterName = filtersModule;
74
75
  try {
75
76
  //first we try to grab module with filters by the module name
76
77
  //this is when generation is used on production using remote templates
77
- mod = require(filtersModule);
78
+ mod = require(filterName);
78
79
  } catch (error) {
79
80
  //in case template is local but was not installed in node_modules of the generator then we need to explicitly provide modules location
80
- mod = require(path.resolve(templateDir, DEFAULT_MODULES_DIR, filtersModule));
81
+ try {
82
+ filterName = path.resolve(templateDir, DEFAULT_MODULES_DIR, filtersModule);
83
+ mod = require(filterName);
84
+ } catch (e) {
85
+ //sometimes it may happen that template is located in node_modules with other templates and its filter package is on the same level as template, as it is shared with other templates
86
+ try {
87
+ filterName = path.resolve(templateDir, '../..', filtersModule);
88
+ mod = require(filterName);
89
+ } catch (error) {
90
+ //in rare cases, especially in isolated tests, it may happen that installation
91
+ //ends but is not yet fully completed, so initial require of the same path do not work
92
+ //but in next attempt it works
93
+ //we need to keep this workaround until we find a solution
94
+ mod = require(filterName);
95
+ }
96
+ }
81
97
  }
82
-
83
- addFilters(nunjucks, mod);
98
+ return addFilters(nunjucks, mod);
84
99
  });
85
100
 
86
101
  await Promise.all(promises);
package/lib/generator.js CHANGED
@@ -21,7 +21,6 @@ const {
21
21
  copyFile,
22
22
  exists,
23
23
  fetchSpec,
24
- getInvalidOptions,
25
24
  isReactTemplate,
26
25
  isJsFile,
27
26
  registerSourceMap,
@@ -44,8 +43,7 @@ const DEFAULT_TEMPLATES_DIR = path.resolve(ROOT_DIR, 'node_modules');
44
43
 
45
44
  const TRANSPILED_TEMPLATE_LOCATION = '__transpiled';
46
45
  const TEMPLATE_CONTENT_DIRNAME = 'template';
47
- const GENERATOR_OPTIONS = ['debug', 'disabledHooks', 'entrypoint', 'forceWrite', 'install', 'noOverwriteGlobs', 'output', 'templateParams', 'mapBaseUrlToFolder'];
48
-
46
+ const GENERATOR_OPTIONS = ['debug', 'disabledHooks', 'entrypoint', 'forceWrite', 'install', 'noOverwriteGlobs', 'output', 'templateParams', 'mapBaseUrlToFolder', 'url', 'auth', 'token', 'registry'];
49
47
  const logMessage = require('./logMessages');
50
48
 
51
49
  const shouldIgnoreFile = filePath =>
@@ -86,14 +84,21 @@ class Generator {
86
84
  * @param {Boolean} [options.install=false] Install the template and its dependencies, even when the template has already been installed.
87
85
  * @param {Boolean} [options.debug=false] Enable more specific errors in the console. At the moment it only shows specific errors about filters. Keep in mind that as a result errors about template are less descriptive.
88
86
  * @param {Object<String, String>} [options.mapBaseUrlToFolder] Optional parameter to map schema references from a base url to a local base folder e.g. url=https://schema.example.com/crm/ folder=./test/docs/ .
87
+ * @param {Object} [options.registry] Optional parameter with private registry configuration
88
+ * @param {String} [options.registry.url] Parameter to pass npm registry url
89
+ * @param {String} [options.registry.auth] Optional parameter to pass npm registry username and password encoded with base64, formatted like username:password value should be encoded
90
+ * @param {String} [options.registry.token] Optional parameter to pass npm registry auth token that you can grab from .npmrc file
89
91
  */
90
- constructor(templateName, targetDir, { templateParams = {}, entrypoint, noOverwriteGlobs, disabledHooks, output = 'fs', forceWrite = false, install = false, debug = false, mapBaseUrlToFolder = {} } = {}) {
91
- const invalidOptions = getInvalidOptions(GENERATOR_OPTIONS, arguments[arguments.length - 1] || []);
92
- if (invalidOptions.length) throw new Error(`These options are not supported by the generator: ${invalidOptions.join(', ')}`);
92
+
93
+ constructor(templateName, targetDir, { templateParams = {}, entrypoint, noOverwriteGlobs, disabledHooks, output = 'fs', forceWrite = false, install = false, debug = false, mapBaseUrlToFolder = {}, registry = {}} = {}) {
94
+ const options = arguments[arguments.length - 1];
95
+ this.verifyoptions(options);
93
96
  if (!templateName) throw new Error('No template name has been specified.');
94
97
  if (!entrypoint && !targetDir) throw new Error('No target directory has been specified.');
95
98
  if (!['fs', 'string'].includes(output)) throw new Error(`Invalid output type ${output}. Valid values are 'fs' and 'string'.`);
96
99
 
100
+ /** @type {Object} Npm registry information. */
101
+ this.registry = registry;
97
102
  /** @type {String} Name of the template to generate. */
98
103
  this.templateName = templateName;
99
104
  /** @type {String} Path to the directory where the files will be generated. */
@@ -136,6 +141,23 @@ class Generator {
136
141
  });
137
142
  }
138
143
 
144
+ /**
145
+ * Check if the Registry Options are valid or not.
146
+ *
147
+ * @private
148
+ * @param {Object} invalidRegOptions Invalid Registry Options.
149
+ *
150
+ */
151
+
152
+ verifyoptions(Options) {
153
+ if (typeof Options !== 'object') return [];
154
+ const invalidOptions = Object.keys(Options).filter(param => !GENERATOR_OPTIONS.includes(param));
155
+
156
+ if (invalidOptions.length > 0) {
157
+ throw new Error(`These options are not supported by the generator: ${invalidOptions.join(', ')}`);
158
+ }
159
+ }
160
+
139
161
  /**
140
162
  * Generates files from a given template and an AsyncAPIDocument object.
141
163
  *
@@ -143,7 +165,7 @@ class Generator {
143
165
  * @example
144
166
  * await generator.generate(myAsyncAPIdocument);
145
167
  * console.log('Done!');
146
- *
168
+ *
147
169
  * @example
148
170
  * generator
149
171
  * .generate(myAsyncAPIdocument)
@@ -201,9 +223,9 @@ class Generator {
201
223
  * @example
202
224
  * const generator = new Generator();
203
225
  * await generator.setupOutput();
204
- *
226
+ *
205
227
  * @async
206
- *
228
+ *
207
229
  * @throws {Error} If 'output' is set to 'string' without providing 'entrypoint'.
208
230
  */
209
231
  async setupOutput() {
@@ -259,7 +281,6 @@ class Generator {
259
281
  */
260
282
  async installAndSetupTemplate() {
261
283
  const { name: templatePkgName, path: templatePkgPath } = await this.installTemplate(this.install);
262
-
263
284
  this.templateDir = templatePkgPath;
264
285
  this.templateName = templatePkgName;
265
286
  this.templateContentDir = path.resolve(this.templateDir, TEMPLATE_CONTENT_DIRNAME);
@@ -287,11 +308,9 @@ class Generator {
287
308
  await this.parseInput(this.asyncapi, parseOptions);
288
309
  validateTemplateConfig(this.templateConfig, this.templateParams, this.asyncapi);
289
310
  await this.configureTemplate();
290
-
291
311
  if (!isReactTemplate(this.templateConfig)) {
292
312
  await registerFilters(this.nunjucks, this.templateConfig, this.templateDir, FILTERS_DIRNAME);
293
313
  }
294
-
295
314
  await registerHooks(this.hooks, this.templateConfig, this.templateDir, HOOKS_DIRNAME);
296
315
  await this.launchHook('generate:before');
297
316
  }
@@ -313,16 +332,14 @@ class Generator {
313
332
  async handleEntrypoint() {
314
333
  if (this.entrypoint) {
315
334
  const entrypointPath = path.resolve(this.templateContentDir, this.entrypoint);
316
-
317
335
  if (!(await exists(entrypointPath))) {
318
336
  throw new Error(`Template entrypoint "${entrypointPath}" couldn't be found.`);
319
337
  }
320
-
321
338
  if (this.output === 'fs') {
322
339
  await this.generateFile(this.asyncapi, path.basename(entrypointPath), path.dirname(entrypointPath));
323
340
  await this.launchHook('generate:after');
324
341
  } else if (this.output === 'string') {
325
- return this.renderFile(this.asyncapi, entrypointPath);
342
+ return await this.renderFile(this.asyncapi, entrypointPath);
326
343
  }
327
344
  } else {
328
345
  await this.generateDirectoryStructure(this.asyncapi);
@@ -355,6 +372,13 @@ class Generator {
355
372
  if (!document) {
356
373
  const err = new Error('Input is not a correct AsyncAPI document so it cannot be processed.');
357
374
  err.diagnostics = diagnostics;
375
+ for (const diag of diagnostics) {
376
+ console.error(
377
+ `Diagnostic err: ${diag['message']} in path ${JSON.stringify(diag['path'])} starting `+
378
+ `L${diag['range']['start']['line'] + 1} C${diag['range']['start']['character']}, ending `+
379
+ `L${diag['range']['end']['line'] + 1} C${diag['range']['end']['character']}`
380
+ );
381
+ }
358
382
  throw err;
359
383
  } else {
360
384
  this.asyncapi = document;
@@ -417,7 +441,7 @@ class Generator {
417
441
  if (!isParsableCompatible) {
418
442
  throw new Error('Parameter "asyncapiString" must be a non-empty string.');
419
443
  }
420
- return this.generate(asyncapiString, parseOptions);
444
+ return await this.generate(asyncapiString, parseOptions);
421
445
  }
422
446
 
423
447
  /**
@@ -444,7 +468,7 @@ class Generator {
444
468
  */
445
469
  async generateFromURL(asyncapiURL) {
446
470
  const doc = await fetchSpec(asyncapiURL);
447
- return this.generate(doc, { path: asyncapiURL });
471
+ return await this.generate(doc, { path: asyncapiURL });
448
472
  }
449
473
 
450
474
  /**
@@ -471,7 +495,7 @@ class Generator {
471
495
  */
472
496
  async generateFromFile(asyncapiFile) {
473
497
  const doc = await readFile(asyncapiFile, { encoding: 'utf8' });
474
- return this.generate(doc, { path: asyncapiFile });
498
+ return await this.generate(doc, { path: asyncapiFile });
475
499
  }
476
500
 
477
501
  /**
@@ -495,69 +519,99 @@ class Generator {
495
519
  return await readFile(path.resolve(templatesDir, templateName, filePath), 'utf8');
496
520
  }
497
521
 
522
+ /**
523
+ * @private
524
+ * @param {Object} arbOptions ArbOptions to intialise the Registry details.
525
+ */
526
+ initialiseArbOptions(arbOptions) {
527
+ let registryUrl = 'registry.npmjs.org';
528
+ let authorizationName = 'anonymous';
529
+ const providedRegistry = this.registry.url;
530
+
531
+ if (providedRegistry) {
532
+ arbOptions.registry = providedRegistry;
533
+ registryUrl = providedRegistry;
534
+ }
535
+
536
+ const domainName = registryUrl.replace(/^https?:\/\//, '');
537
+ //doing basic if/else so basically only one auth type is used and token as more secure is primary
538
+ if (this.registry.token) {
539
+ authorizationName = `//${domainName}:_authToken`;
540
+ arbOptions[authorizationName] = this.registry.token;
541
+ } else if (this.registry.auth) {
542
+ authorizationName = `//${domainName}:_auth`;
543
+ arbOptions[authorizationName] = this.registry.auth;
544
+ }
545
+
546
+ //not sharing in logs neither token nor auth for security reasons
547
+ log.debug(`Using npm registry ${registryUrl} and authorization type ${authorizationName} to handle template installation.`);
548
+ }
498
549
  /**
499
550
  * Downloads and installs a template and its dependencies
500
551
  *
501
552
  * @param {Boolean} [force=false] Whether to force installation (and skip cache) or not.
502
553
  */
503
- installTemplate(force = false) {
504
- return new Promise(async (resolve, reject) => {
505
- if (!force) {
506
- let pkgPath;
507
- let installedPkg;
508
- let packageVersion;
509
-
510
- try {
511
- installedPkg = getTemplateDetails(this.templateName, PACKAGE_JSON_FILENAME);
512
- pkgPath = installedPkg && installedPkg.pkgPath;
513
- packageVersion = installedPkg && installedPkg.version;
514
- log.debug(logMessage.templateSource(pkgPath));
515
- if (packageVersion) log.debug(logMessage.templateVersion(packageVersion));
516
-
517
- return resolve({
518
- name: installedPkg.name,
519
- path: pkgPath
520
- });
521
- } catch (e) {
522
- log.debug(logMessage.packageNotAvailable(pkgPath), e);
523
- // We did our best. Proceed with installation...
524
- }
525
- }
526
-
527
- const debugMessage = force ? logMessage.TEMPLATE_INSTALL_FLAG_MSG : logMessage.TEMPLATE_INSTALL_DISK_MSG;
528
- log.debug(logMessage.installationDebugMessage(debugMessage));
529
-
530
- if (isFileSystemPath(this.templateName)) log.debug(logMessage.NPM_INSTALL_TRIGGER);
531
-
532
- const arb = new Arborist({
533
- path: ROOT_DIR
534
- });
535
-
554
+ async installTemplate(force = false) {
555
+ if (!force) {
556
+ let pkgPath;
557
+ let installedPkg;
558
+ let packageVersion;
559
+
536
560
  try {
537
- const installResult = await arb.reify({
538
- add: [this.templateName],
539
- saveType: 'prod',
540
- save: false
541
- });
542
-
543
- const addResult = arb[Symbol.for('resolvedAdd')];
544
- if (!addResult) return reject('Unable to resolve the name of the added package. It was most probably not added to node_modules successfully');
545
-
546
- const packageName = addResult[0].name;
547
- const packageVersion = installResult.children.get(packageName).version;
548
- const packagePath = installResult.children.get(packageName).path;
549
-
550
- if (!isFileSystemPath(this.templateName)) log.debug(logMessage.templateSuccessfullyInstalled(packageName, packagePath));
561
+ installedPkg = getTemplateDetails(this.templateName, PACKAGE_JSON_FILENAME);
562
+ pkgPath = installedPkg && installedPkg.pkgPath;
563
+ packageVersion = installedPkg && installedPkg.version;
564
+ log.debug(logMessage.templateSource(pkgPath));
551
565
  if (packageVersion) log.debug(logMessage.templateVersion(packageVersion));
552
-
553
- return resolve({
554
- name: packageName,
555
- path: packagePath,
556
- });
557
- } catch (err) {
558
- reject(err);
566
+
567
+ return {
568
+ name: installedPkg.name,
569
+ path: pkgPath
570
+ };
571
+ } catch (e) {
572
+ log.debug(logMessage.packageNotAvailable(installedPkg), e);
573
+ // We did our best. Proceed with installation...
559
574
  }
560
- });
575
+ }
576
+
577
+ const debugMessage = force ? logMessage.TEMPLATE_INSTALL_FLAG_MSG : logMessage.TEMPLATE_INSTALL_DISK_MSG;
578
+ log.debug(logMessage.installationDebugMessage(debugMessage));
579
+
580
+ if (isFileSystemPath(this.templateName)) log.debug(logMessage.NPM_INSTALL_TRIGGER);
581
+
582
+ const arbOptions = {
583
+ path: ROOT_DIR,
584
+ };
585
+ if (this.registry) {
586
+ this.initialiseArbOptions(arbOptions);
587
+ }
588
+
589
+ const arb = new Arborist(arbOptions);
590
+
591
+ try {
592
+ const installResult = await arb.reify({
593
+ add: [this.templateName],
594
+ saveType: 'prod',
595
+ save: false
596
+ });
597
+
598
+ const addResult = arb[Symbol.for('resolvedAdd')];
599
+ if (!addResult) throw new Error('Unable to resolve the name of the added package. It was most probably not added to node_modules successfully');
600
+
601
+ const packageName = addResult[0].name;
602
+ const packageVersion = installResult.children.get(packageName).version;
603
+ const packagePath = installResult.children.get(packageName).path;
604
+
605
+ if (!isFileSystemPath(this.templateName)) log.debug(logMessage.templateSuccessfullyInstalled(packageName, packagePath));
606
+ if (packageVersion) log.debug(logMessage.templateVersion(packageVersion));
607
+
608
+ return {
609
+ name: packageName,
610
+ path: packagePath,
611
+ };
612
+ } catch (err) {
613
+ throw new Error('Installation failed', err);
614
+ }
561
615
  }
562
616
 
563
617
  /**
@@ -722,7 +776,7 @@ class Generator {
722
776
  * @param {String} baseDir Base directory of the given file name.
723
777
  * @returns {Promise}
724
778
  */
725
- generateSeparateFiles(asyncapiDocument, array, template, fileName, baseDir) {
779
+ async generateSeparateFiles(asyncapiDocument, array, template, fileName, baseDir) {
726
780
  const promises = [];
727
781
 
728
782
  Object.keys(array).forEach((name) => {
@@ -761,7 +815,7 @@ class Generator {
761
815
  const newFileName = fileName.replace(`\$\$${template}\$\$`, filename);
762
816
  const targetFile = path.resolve(this.targetDir, relativeBaseDir, newFileName);
763
817
  const relativeTargetFile = path.relative(this.targetDir, targetFile);
764
- const shouldOverwriteFile = this.shouldOverwriteFile(relativeTargetFile);
818
+ const shouldOverwriteFile = await this.shouldOverwriteFile(relativeTargetFile);
765
819
  if (!shouldOverwriteFile) return;
766
820
  //Ensure the same object are parsed to the renderFile method as before.
767
821
  const temp = {};
@@ -808,7 +862,7 @@ class Generator {
808
862
 
809
863
  if (shouldIgnoreFile(relativeSourceFile)) return;
810
864
 
811
- const shouldOverwriteFile = this.shouldOverwriteFile(relativeTargetFile);
865
+ const shouldOverwriteFile = await this.shouldOverwriteFile(relativeTargetFile);
812
866
  if (!shouldOverwriteFile) return;
813
867
 
814
868
  if (this.templateConfig.conditionalFiles && this.templateConfig.conditionalFiles[relativeSourceFile]) {
@@ -889,7 +943,7 @@ class Generator {
889
943
  *
890
944
  * @private
891
945
  * @param {string} filePath Path to the file to check against a list of glob patterns.
892
- * @return {boolean}
946
+ * @return {Promise<boolean>}
893
947
  */
894
948
  async shouldOverwriteFile(filePath) {
895
949
  if (!Array.isArray(this.noOverwriteGlobs)) return true;
@@ -18,8 +18,12 @@ function templateNotFound(templateName) {
18
18
  return `${templateName} not found in local dependencies but found it installed as a global package.`;
19
19
  }
20
20
 
21
- function packageNotAvailable(pkgPath) {
22
- return `Unable to resolve template location at ${pkgPath}. Package is not available locally.`;
21
+ function packageNotAvailable(packageDetails) {
22
+ if (packageDetails && packageDetails.pkgPath) {
23
+ return `Unable to resolve template location at ${packageDetails.pkgPath}. Package is not available locally.`;
24
+ }
25
+
26
+ return `Template is not available locally and expected location is undefined. Known details are: ${JSON.stringify(packageDetails, null, 2)}`;
23
27
  }
24
28
 
25
29
  function installationDebugMessage(debugMessage) {
package/lib/utils.js CHANGED
@@ -124,17 +124,6 @@ utils.getGeneratorVersion = () => {
124
124
  return packageJson.version;
125
125
  };
126
126
 
127
- /**
128
- * Filters out the Generator invalid options given
129
- *
130
- * @param {Array}
131
- * @returns {Array}
132
- */
133
- utils.getInvalidOptions = (generatorOptions, options) => {
134
- if (typeof options !== 'object') return [];
135
- return Object.keys(options).filter(param => !generatorOptions.includes(param));
136
- };
137
-
138
127
  /**
139
128
  * Determine whether the given function is asynchronous.
140
129
  * @private
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@asyncapi/generator",
3
- "version": "1.15.9",
3
+ "version": "1.17.0",
4
4
  "description": "The AsyncAPI generator. It can generate documentation, code, anything!",
5
5
  "main": "./lib/generator.js",
6
6
  "bin": {
@@ -50,7 +50,7 @@
50
50
  "dependencies": {
51
51
  "@asyncapi/generator-react-sdk": "^1.0.6",
52
52
  "@asyncapi/parser": "^3.0.2",
53
- "@npmcli/arborist": "^2.2.4",
53
+ "@npmcli/arborist": "5.6.3",
54
54
  "@smoya/multi-parser": "^5.0.0",
55
55
  "ajv": "^8.12.0",
56
56
  "chokidar": "^3.4.0",