@asyncapi/cli 0.23.1 → 0.24.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  [![AsyncAPI CLI](./assets/logo.png)](https://www.asyncapi.com)
2
2
 
3
- CLI to work with your AsyncAPI files. Currently supports validation, but it is under development for more features.
3
+ CLI to work with your AsyncAPI files. Currently under development, we are working to bring more features.
4
4
 
5
5
  [![GitHub license](https://img.shields.io/github/license/asyncapi/cli)](https://github.com/asyncapi/cli/blob/master/LICENSE)
6
6
  [![PR testing - if Node project](https://github.com/asyncapi/cli/actions/workflows/if-nodejs-pr-testing.yml/badge.svg)](https://github.com/asyncapi/cli/actions/workflows/if-nodejs-pr-testing.yml)
@@ -114,7 +114,7 @@ asyncapi
114
114
 
115
115
  ## Usage
116
116
 
117
- As of now, the `@asyncapi/cli` only supports validation of the specification file. (This is still under development for more features.)
117
+ `@asyncapi/cli` makes it easier to work with asyncpi files.
118
118
 
119
119
  We have well-documented help commands so just run:
120
120
 
@@ -144,6 +144,7 @@ COMMANDS
144
144
  java generate the models for Java
145
145
  javascript generate the models for JavaScript
146
146
  dart generate the models for Dart
147
+ fromTemplate generate whatever you want using templates compatible with AsyncAPI Generator
147
148
  ```
148
149
 
149
150
  ## Contributing
@@ -1,4 +1,5 @@
1
1
  import Command from '../../base';
2
2
  export default class Config extends Command {
3
+ static description: string;
3
4
  run(): Promise<void>;
4
5
  }
@@ -11,3 +11,4 @@ class Config extends base_1.default {
11
11
  }
12
12
  }
13
13
  exports.default = Config;
14
+ Config.description = 'CLI config settings';
@@ -52,7 +52,7 @@ class Convert extends base_1.default {
52
52
  }
53
53
  }
54
54
  exports.default = Convert;
55
- Convert.description = 'convert asyncapi documents older to newer versions';
55
+ Convert.description = 'Convert asyncapi documents older to newer versions';
56
56
  Convert.flags = {
57
57
  help: core_1.Flags.help({ char: 'h' }),
58
58
  output: core_1.Flags.string({ char: 'o', description: 'path to the file where the result is saved' }),
@@ -131,7 +131,7 @@ class Diff extends base_1.default {
131
131
  }
132
132
  }
133
133
  exports.default = Diff;
134
- Diff.description = 'find diff between two asyncapi files';
134
+ Diff.description = 'Find diff between two asyncapi files';
135
135
  Diff.flags = {
136
136
  help: core_1.Flags.help({ char: 'h' }),
137
137
  format: core_1.Flags.string({
@@ -150,7 +150,7 @@ Diff.flags = {
150
150
  char: 'o',
151
151
  description: 'path to JSON file containing the override properties',
152
152
  }),
153
- watch: flags_1.watchFlag,
153
+ watch: (0, flags_1.watchFlag)(),
154
154
  };
155
155
  Diff.args = [
156
156
  {
@@ -0,0 +1,32 @@
1
+ import Command from '../../base';
2
+ import type { Example } from '@oclif/core/lib/interfaces';
3
+ export default class Template extends Command {
4
+ static description: string;
5
+ static examples: Example[];
6
+ static flags: {
7
+ help: import("@oclif/core/lib/interfaces").BooleanFlag<void>;
8
+ 'disable-hook': import("@oclif/core/lib/interfaces").OptionFlag<string[]>;
9
+ install: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ debug: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
+ 'no-overwrite': import("@oclif/core/lib/interfaces").OptionFlag<string[]>;
12
+ output: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined>;
13
+ 'force-write': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
14
+ watch: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
15
+ param: import("@oclif/core/lib/interfaces").OptionFlag<string[]>;
16
+ 'map-base-url': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined>;
17
+ };
18
+ static args: {
19
+ name: string;
20
+ description: string;
21
+ required: boolean;
22
+ }[];
23
+ run(): Promise<void>;
24
+ private parseFlags;
25
+ private paramParser;
26
+ private disableHooksParser;
27
+ private mapBaseURLParser;
28
+ private generate;
29
+ private runWatchMode;
30
+ private watcherHandler;
31
+ private getMapBaseUrlToFolderResolver;
32
+ }
@@ -0,0 +1,273 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const core_1 = require("@oclif/core");
5
+ const base_1 = tslib_1.__importDefault(require("../../base"));
6
+ // eslint-disable-next-line
7
+ // @ts-ignore
8
+ const generator_1 = tslib_1.__importDefault(require("@asyncapi/generator"));
9
+ const path_1 = tslib_1.__importDefault(require("path"));
10
+ const os_1 = tslib_1.__importDefault(require("os"));
11
+ const fs_1 = tslib_1.__importDefault(require("fs"));
12
+ const SpecificationFile_1 = require("../../models/SpecificationFile");
13
+ const flags_1 = require("../../flags");
14
+ const generator_2 = require("../../utils/generator");
15
+ const validation_error_1 = require("../../errors/validation-error");
16
+ const generator_error_1 = require("../../errors/generator-error");
17
+ const red = (text) => `\x1b[31m${text}\x1b[0m`;
18
+ const magenta = (text) => `\x1b[35m${text}\x1b[0m`;
19
+ const yellow = (text) => `\x1b[33m${text}\x1b[0m`;
20
+ const green = (text) => `\x1b[32m${text}\x1b[0m`;
21
+ class Template extends base_1.default {
22
+ constructor() {
23
+ super(...arguments);
24
+ this.getMapBaseUrlToFolderResolver = (urlToFolder) => {
25
+ return {
26
+ order: 1,
27
+ canRead() {
28
+ return true;
29
+ },
30
+ read(file) {
31
+ const baseUrl = urlToFolder.url;
32
+ const baseDir = urlToFolder.folder;
33
+ return new Promise(((resolve, reject) => {
34
+ let localpath = file.url;
35
+ localpath = localpath.replace(baseUrl, baseDir);
36
+ try {
37
+ fs_1.default.readFile(localpath, (err, data) => {
38
+ if (err) {
39
+ reject(`Error opening file "${localpath}"`);
40
+ }
41
+ else {
42
+ resolve(data);
43
+ }
44
+ });
45
+ }
46
+ catch (err) {
47
+ reject(`Error opening file "${localpath}"`);
48
+ }
49
+ }));
50
+ }
51
+ };
52
+ };
53
+ }
54
+ async run() {
55
+ const { args, flags } = await this.parse(Template); // NOSONAR
56
+ const asyncapi = args['asyncapi'];
57
+ const template = args['template'];
58
+ const output = flags.output || process.cwd();
59
+ const parsedFlags = this.parseFlags(flags['disable-hook'], flags['param'], flags['map-base-url']);
60
+ const options = {
61
+ forceWrite: flags['force-write'],
62
+ install: flags.install,
63
+ debug: flags.debug,
64
+ templateParams: parsedFlags.params,
65
+ noOverwriteGlobs: flags['no-overwrite'],
66
+ mapBaseUrlToFolder: parsedFlags.mapBaseUrlToFolder,
67
+ disabledHooks: parsedFlags.disableHooks,
68
+ };
69
+ const watchTemplate = flags['watch'];
70
+ const genOption = {};
71
+ if (flags['map-base-url']) {
72
+ genOption.resolve = { resolve: this.getMapBaseUrlToFolderResolver(parsedFlags.mapBaseUrlToFolder) };
73
+ }
74
+ await this.generate(asyncapi, template, output, options, genOption);
75
+ if (watchTemplate) {
76
+ const watcherHandler = this.watcherHandler(asyncapi, template, output, options, genOption);
77
+ await this.runWatchMode(asyncapi, template, output, watcherHandler);
78
+ }
79
+ }
80
+ parseFlags(disableHooks, params, mapBaseUrl) {
81
+ return {
82
+ params: this.paramParser(params),
83
+ disableHooks: this.disableHooksParser(disableHooks),
84
+ mapBaseUrlToFolder: this.mapBaseURLParser(mapBaseUrl),
85
+ };
86
+ }
87
+ paramParser(inputs) {
88
+ if (!inputs) {
89
+ return {};
90
+ }
91
+ const params = {};
92
+ for (const input of inputs) {
93
+ if (!input.includes('=')) {
94
+ throw new Error(`Invalid param ${input}. It must be in the format of --param name1=value1 name2=value2 `);
95
+ }
96
+ const [paramName, paramValue] = input.split(/=(.+)/, 2);
97
+ params[String(paramName)] = paramValue;
98
+ }
99
+ return params;
100
+ }
101
+ disableHooksParser(inputs) {
102
+ if (!inputs) {
103
+ return {};
104
+ }
105
+ const disableHooks = {};
106
+ for (const input of inputs) {
107
+ const [hookType, hookNames] = input.split(/=/);
108
+ if (!hookType) {
109
+ throw new Error('Invalid --disable-hook flag. It must be in the format of: --disable-hook <hookType> or --disable-hook <hookType>=<hookName1>,<hookName2>,...');
110
+ }
111
+ if (hookNames) {
112
+ disableHooks[String(hookType)] = hookNames.split(',');
113
+ }
114
+ else {
115
+ disableHooks[String(hookType)] = true;
116
+ }
117
+ }
118
+ return disableHooks;
119
+ }
120
+ mapBaseURLParser(input) {
121
+ if (!input) {
122
+ return;
123
+ }
124
+ const mapBaseURLToFolder = {};
125
+ const re = /(.*):(.*)/g; // NOSONAR
126
+ let mapping = [];
127
+ if ((mapping = re.exec(input)) === null || mapping.length !== 3) {
128
+ throw new Error('Invalid --map-base-url flag. A mapping <url>:<folder> with delimiter : expected.');
129
+ }
130
+ mapBaseURLToFolder.url = mapping[1].replace(/\/$/, '');
131
+ mapBaseURLToFolder.folder = path_1.default.resolve(mapping[2]);
132
+ const isURL = /^https?:/;
133
+ if (!isURL.test(mapBaseURLToFolder.url.toLowerCase())) {
134
+ throw new Error('Invalid --map-base-url flag. The mapping <url>:<folder> requires a valid http/https url and valid folder with delimiter `:`.');
135
+ }
136
+ return mapBaseURLToFolder;
137
+ }
138
+ async generate(asyncapi, template, output, options, genOption) {
139
+ let specification;
140
+ try {
141
+ specification = await (0, SpecificationFile_1.load)(asyncapi);
142
+ }
143
+ catch (err) {
144
+ return this.error(new validation_error_1.ValidationError({
145
+ type: 'invalid-file',
146
+ filepath: asyncapi,
147
+ }), { exit: 1 });
148
+ }
149
+ const generator = new generator_1.default(template, output || path_1.default.resolve(os_1.default.tmpdir(), 'asyncapi-generator'), options);
150
+ core_1.CliUx.ux.action.start('Generation in progress. Keep calm and wait a bit');
151
+ try {
152
+ await generator.generateFromString(specification.text(), genOption);
153
+ core_1.CliUx.ux.action.stop();
154
+ }
155
+ catch (err) {
156
+ core_1.CliUx.ux.action.stop('done\n');
157
+ throw new generator_error_1.GeneratorError(err);
158
+ }
159
+ console.log(`${yellow('Check out your shiny new generated files at ') + magenta(output) + yellow('.')}\n`);
160
+ }
161
+ async runWatchMode(asyncapi, template, output, watchHandler) {
162
+ const specification = await (0, SpecificationFile_1.load)(asyncapi);
163
+ const watchDir = path_1.default.resolve(template);
164
+ const outputPath = path_1.default.resolve(watchDir, output);
165
+ const transpiledTemplatePath = path_1.default.resolve(watchDir, generator_1.default.TRANSPILED_TEMPLATE_LOCATION);
166
+ const ignorePaths = [outputPath, transpiledTemplatePath];
167
+ const specificationFile = specification.getFilePath();
168
+ // Template name is needed as it is not always a part of the cli commad
169
+ // There is a use case that you run generator from a root of the template with `./` path
170
+ let templateName = '';
171
+ try {
172
+ // eslint-disable-next-line
173
+ templateName = require(path_1.default.resolve(watchDir, 'package.json')).name;
174
+ }
175
+ catch (err) {
176
+ // intentional
177
+ }
178
+ let watcher;
179
+ if (specificationFile) { // is local AsyncAPI file
180
+ this.log(`[WATCHER] Watching for changes in the template directory ${magenta(watchDir)} and in the AsyncAPI file ${magenta(specificationFile)}`);
181
+ watcher = new generator_2.Watcher([specificationFile, watchDir], ignorePaths);
182
+ }
183
+ else {
184
+ this.log(`[WATCHER] Watching for changes in the template directory ${magenta(watchDir)}`);
185
+ watcher = new generator_2.Watcher(watchDir, ignorePaths);
186
+ }
187
+ // Must check template in its installation path in generator to use isLocalTemplate function
188
+ if (!await (0, generator_2.isLocalTemplate)(path_1.default.resolve(generator_1.default.DEFAULT_TEMPLATES_DIR, templateName))) {
189
+ this.warn(`WARNING: ${template} is a remote template. Changes may be lost on subsequent installations.`);
190
+ }
191
+ watcher.watch(watchHandler, (paths) => {
192
+ this.error(`[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.`, {
193
+ exit: 1,
194
+ });
195
+ });
196
+ }
197
+ watcherHandler(asyncapi, template, output, options, genOption) {
198
+ return async (changedFiles) => {
199
+ console.clear();
200
+ console.log('[WATCHER] Change detected');
201
+ for (const [, value] of Object.entries(changedFiles)) {
202
+ let eventText;
203
+ switch (value.eventType) {
204
+ case 'changed':
205
+ eventText = green(value.eventType);
206
+ break;
207
+ case 'removed':
208
+ eventText = red(value.eventType);
209
+ break;
210
+ case 'renamed':
211
+ eventText = yellow(value.eventType);
212
+ break;
213
+ default:
214
+ eventText = yellow(value.eventType);
215
+ }
216
+ this.log(`\t${magenta(value.path)} was ${eventText}`);
217
+ }
218
+ try {
219
+ await this.generate(asyncapi, template, output, options, genOption);
220
+ }
221
+ catch (err) {
222
+ throw new generator_error_1.GeneratorError(err);
223
+ }
224
+ };
225
+ }
226
+ }
227
+ exports.default = Template;
228
+ Template.description = 'Generates whatever you want using templates compatible with AsyncAPI Generator.';
229
+ Template.examples = [
230
+ 'asyncapi generate fromTemplate asyncapi.yaml @asyncapi/html-template --param version=1.0.0 singleFile=true --output ./docs --force-write'
231
+ ];
232
+ Template.flags = {
233
+ help: core_1.Flags.help({ char: 'h' }),
234
+ 'disable-hook': core_1.Flags.string({
235
+ char: 'd',
236
+ description: 'Disable a specific hook type or hooks from a given hook type',
237
+ multiple: true
238
+ }),
239
+ install: core_1.Flags.boolean({
240
+ char: 'i',
241
+ default: false,
242
+ description: 'Installs the template and its dependencies (defaults to false)'
243
+ }),
244
+ debug: core_1.Flags.boolean({
245
+ description: 'Enable more specific errors in the console'
246
+ }),
247
+ 'no-overwrite': core_1.Flags.string({
248
+ char: 'n',
249
+ multiple: true,
250
+ description: 'Glob or path of the file(s) to skip when regenerating'
251
+ }),
252
+ output: core_1.Flags.string({
253
+ char: 'o',
254
+ description: 'Directory where to put the generated files (defaults to current directory)',
255
+ }),
256
+ 'force-write': core_1.Flags.boolean({
257
+ default: false,
258
+ description: 'Force writing of the generated files to given directory even if it is a git repo with unstaged files or not empty dir (defaults to false)'
259
+ }),
260
+ watch: (0, flags_1.watchFlag)('Watches the template directory and the AsyncAPI document, and re-generate the files when changes occur. Ignores the output directory.'),
261
+ param: core_1.Flags.string({
262
+ char: 'p',
263
+ description: 'Additional param to pass to templates',
264
+ multiple: true
265
+ }),
266
+ 'map-base-url': core_1.Flags.string({
267
+ description: 'Maps all schema references from base url to local folder'
268
+ })
269
+ };
270
+ Template.args = [
271
+ { name: 'asyncapi', description: '- Local path, url or context-name pointing to AsyncAPI file', required: true },
272
+ { name: 'template', description: '- Name of the generator template like for example @asyncapi/html-template or https://github.com/asyncapi/html-template', required: true }
273
+ ];
@@ -0,0 +1,5 @@
1
+ import Command from '../../base';
2
+ export default class Generate extends Command {
3
+ static description: string;
4
+ run(): Promise<void>;
5
+ }
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const base_1 = tslib_1.__importDefault(require("../../base"));
5
+ const core_1 = require("@oclif/core");
6
+ class Generate extends base_1.default {
7
+ async run() {
8
+ const help = new core_1.Help(this.config);
9
+ help.showHelp(['generate', '--help']);
10
+ }
11
+ }
12
+ exports.default = Generate;
13
+ Generate.description = 'Generate typed models or other things like clients, applications or docs using AsyncAPI Generator templates.';
@@ -115,7 +115,7 @@ class New extends base_1.default {
115
115
  }
116
116
  }
117
117
  exports.default = New;
118
- New.description = 'creates a new asyncapi file';
118
+ New.description = 'Creates a new asyncapi file';
119
119
  New.flags = {
120
120
  help: core_1.Flags.help({ char: 'h' }),
121
121
  'file-name': core_1.Flags.string({ char: 'n', description: 'name of the file' }),
@@ -1,4 +1,5 @@
1
1
  import Command from '../../base';
2
2
  export default class Start extends Command {
3
+ static description: string;
3
4
  run(): Promise<void>;
4
5
  }
@@ -5,9 +5,9 @@ const base_1 = tslib_1.__importDefault(require("../../base"));
5
5
  const core_1 = require("@oclif/core");
6
6
  class Start extends base_1.default {
7
7
  async run() {
8
- const Help = await (0, core_1.loadHelpClass)(this.config);
9
- const help = new Help(this.config);
8
+ const help = new core_1.Help(this.config);
10
9
  help.showHelp(['start', '--help']);
11
10
  }
12
11
  }
13
12
  exports.default = Start;
13
+ Start.description = 'Start asyncapi studio';
@@ -39,7 +39,7 @@ exports.default = Validate;
39
39
  Validate.description = 'validate asyncapi file';
40
40
  Validate.flags = {
41
41
  help: core_1.Flags.help({ char: 'h' }),
42
- watch: flags_1.watchFlag
42
+ watch: (0, flags_1.watchFlag)()
43
43
  };
44
44
  Validate.args = [
45
45
  { name: 'spec-file', description: 'spec path, url, or context-name', required: false },
@@ -0,0 +1,3 @@
1
+ export declare class GeneratorError extends Error {
2
+ constructor(err: Error);
3
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GeneratorError = void 0;
4
+ class GeneratorError extends Error {
5
+ constructor(err) {
6
+ super();
7
+ this.name = 'Generator Error';
8
+ this.message = err.message;
9
+ }
10
+ }
11
+ exports.GeneratorError = GeneratorError;
package/lib/flags.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const watchFlag: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
1
+ export declare const watchFlag: (description?: string) => import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
package/lib/flags.js CHANGED
@@ -2,7 +2,11 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.watchFlag = void 0;
4
4
  const core_1 = require("@oclif/core");
5
- exports.watchFlag = core_1.Flags.boolean({
6
- char: 'w',
7
- description: 'Enable watch mode'
8
- });
5
+ const watchFlag = (description) => {
6
+ return core_1.Flags.boolean({
7
+ default: false,
8
+ char: 'w',
9
+ description: description || 'Enable watch mode',
10
+ });
11
+ };
12
+ exports.watchFlag = watchFlag;
@@ -0,0 +1,48 @@
1
+ export declare function isLocalTemplate(templatePath: string): Promise<boolean>;
2
+ export declare class Watcher {
3
+ private watchers;
4
+ private fsWait;
5
+ private filesChanged;
6
+ private ignorePaths;
7
+ private paths;
8
+ constructor(paths: string | string[], ignorePaths: string[]);
9
+ /**
10
+ * Initiates watch on a path.
11
+ * @param {*} path The path the watcher is listening on.
12
+ * @param {*} changeCallback Callback to call when changed occur.
13
+ * @param {*} errorCallback Calback to call when it is no longer possible to watch a file.
14
+ */
15
+ initiateWatchOnPath(path: string, changeCallback: any, errorCallback: any): void;
16
+ /**
17
+ * This method initiate the watch for change in all files
18
+ * @param {*} callback called when the file(s) change
19
+ */
20
+ watch(changeCallback: any, errorCallback: any): Promise<void>;
21
+ /**
22
+ * Should be called when a file has changed one way or another.
23
+ * @param {*} listenerPath The path the watcher is listening on.
24
+ * @param {*} changedPath The file/dir that was changed
25
+ * @param {*} eventType What kind of change
26
+ * @param {*} changeCallback Callback to call when changed occur.
27
+ * @param {*} errorCallback Calback to call when it is no longer possible to watch a file.
28
+ */
29
+ fileChanged(listenerPath: string, changedPath: string, eventType: string, changeCallback: any, errorCallback: any): void;
30
+ /**
31
+ * Convert the event type to a more usefull one.
32
+ * @param {*} currentEventType The current event type (from chokidar)
33
+ */
34
+ convertEventType(currentEventType: string): string;
35
+ /**
36
+ * Get all paths which no longer exists
37
+ */
38
+ getAllNonExistingPaths(): any[];
39
+ /**
40
+ * Closes all active watchers down.
41
+ */
42
+ closeWatchers(): void;
43
+ /**
44
+ * Closes an active watcher down.
45
+ * @param {*} path The path to close the watcher for.
46
+ */
47
+ closeWatcher(path: string): void;
48
+ }
@@ -0,0 +1,153 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Watcher = exports.isLocalTemplate = void 0;
4
+ const tslib_1 = require("tslib");
5
+ // eslint-disable security/detect-object-injection
6
+ const fs = tslib_1.__importStar(require("fs"));
7
+ const util_1 = require("util");
8
+ const chokidar_1 = tslib_1.__importDefault(require("chokidar"));
9
+ const lstat = (0, util_1.promisify)(fs.lstat);
10
+ async function isLocalTemplate(templatePath) {
11
+ const stats = await lstat(templatePath);
12
+ return stats.isSymbolicLink();
13
+ }
14
+ exports.isLocalTemplate = isLocalTemplate;
15
+ class Watcher {
16
+ constructor(paths, ignorePaths) {
17
+ if (Array.isArray(paths)) {
18
+ this.paths = paths;
19
+ }
20
+ else {
21
+ this.paths = [paths];
22
+ }
23
+ //Ensure all backwards slashes are replaced with forward slash based on the requirement from chokidar
24
+ for (const pathIndex in this.paths) {
25
+ const path = this.paths[String(pathIndex)];
26
+ this.paths[String(pathIndex)] = path.replace(/[\\]/g, '/');
27
+ }
28
+ this.fsWait = false;
29
+ this.watchers = {};
30
+ this.filesChanged = {};
31
+ this.ignorePaths = ignorePaths;
32
+ }
33
+ /**
34
+ * Initiates watch on a path.
35
+ * @param {*} path The path the watcher is listening on.
36
+ * @param {*} changeCallback Callback to call when changed occur.
37
+ * @param {*} errorCallback Calback to call when it is no longer possible to watch a file.
38
+ */
39
+ initiateWatchOnPath(path, changeCallback, errorCallback) {
40
+ const watcher = chokidar_1.default.watch(path, { ignoreInitial: true, ignored: this.ignorePaths });
41
+ watcher.on('all', (eventType, changedPath) => this.fileChanged(path, changedPath, eventType, changeCallback, errorCallback));
42
+ this.watchers[String(path)] = watcher;
43
+ }
44
+ /**
45
+ * This method initiate the watch for change in all files
46
+ * @param {*} callback called when the file(s) change
47
+ */
48
+ async watch(changeCallback, errorCallback) {
49
+ for (const index in this.paths) {
50
+ const path = this.paths[String(index)];
51
+ this.initiateWatchOnPath(path, changeCallback, errorCallback);
52
+ }
53
+ }
54
+ /**
55
+ * Should be called when a file has changed one way or another.
56
+ * @param {*} listenerPath The path the watcher is listening on.
57
+ * @param {*} changedPath The file/dir that was changed
58
+ * @param {*} eventType What kind of change
59
+ * @param {*} changeCallback Callback to call when changed occur.
60
+ * @param {*} errorCallback Calback to call when it is no longer possible to watch a file.
61
+ */
62
+ fileChanged(listenerPath, changedPath, eventType, changeCallback, errorCallback) {
63
+ try {
64
+ if (fs.existsSync(listenerPath)) {
65
+ const newEventType = this.convertEventType(eventType);
66
+ this.filesChanged[String(changedPath)] = { eventType: newEventType, path: changedPath };
67
+ // Since multiple changes can occur at the same time, lets wait a bit before processing.
68
+ if (this.fsWait) {
69
+ return;
70
+ }
71
+ this.fsWait = setTimeout(async () => {
72
+ await changeCallback(this.filesChanged);
73
+ this.filesChanged = {};
74
+ this.fsWait = false;
75
+ }, 500);
76
+ }
77
+ }
78
+ catch (e) {
79
+ // File was not, find all files that are missing..
80
+ const unknownPaths = this.getAllNonExistingPaths();
81
+ this.closeWatchers();
82
+ errorCallback(unknownPaths);
83
+ }
84
+ }
85
+ /**
86
+ * Convert the event type to a more usefull one.
87
+ * @param {*} currentEventType The current event type (from chokidar)
88
+ */
89
+ convertEventType(currentEventType) {
90
+ let newEventType = currentEventType;
91
+ //Change the naming of the event type
92
+ switch (newEventType) {
93
+ case 'unlink':
94
+ case 'unlinkDir':
95
+ newEventType = 'removed';
96
+ break;
97
+ case 'addDir':
98
+ case 'add':
99
+ newEventType = 'added';
100
+ break;
101
+ case 'change':
102
+ newEventType = 'changed';
103
+ break;
104
+ case 'rename':
105
+ newEventType = 'renamed';
106
+ break;
107
+ default:
108
+ newEventType = `unknown (${currentEventType})`;
109
+ }
110
+ return newEventType;
111
+ }
112
+ /**
113
+ * Get all paths which no longer exists
114
+ */
115
+ getAllNonExistingPaths() {
116
+ const unknownPaths = [];
117
+ for (const index in this.paths) {
118
+ const path = this.paths[String(index)];
119
+ if (!fs.existsSync(path)) {
120
+ unknownPaths.push(path);
121
+ }
122
+ }
123
+ return unknownPaths;
124
+ }
125
+ /**
126
+ * Closes all active watchers down.
127
+ */
128
+ closeWatchers() {
129
+ this.filesChanged = {};
130
+ for (const index in this.paths) {
131
+ const path = this.paths[String(index)];
132
+ this.closeWatcher(path);
133
+ }
134
+ }
135
+ /**
136
+ * Closes an active watcher down.
137
+ * @param {*} path The path to close the watcher for.
138
+ */
139
+ closeWatcher(path) {
140
+ // Ensure if called before `watch` to do nothing
141
+ if (path !== null) {
142
+ const watcher = this.watchers[String(path)];
143
+ if (watcher !== null) {
144
+ watcher.close();
145
+ this.watchers[String(path)] = null;
146
+ }
147
+ else {
148
+ //Watcher not found for path
149
+ }
150
+ }
151
+ }
152
+ }
153
+ exports.Watcher = Watcher;
@@ -1 +1 @@
1
- {"version":"0.23.1","commands":{"convert":{"id":"convert","description":"convert asyncapi documents older to newer versions","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false},"output":{"name":"output","type":"option","char":"o","description":"path to the file where the result is saved","multiple":false},"target-version":{"name":"target-version","type":"option","char":"t","description":"asyncapi version to convert to","multiple":false,"default":"2.4.0"}},"args":[{"name":"spec-file","description":"spec path, url, or context-name","required":false}],"_globalFlags":{}},"diff":{"id":"diff","description":"find diff between two asyncapi files","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false},"format":{"name":"format","type":"option","char":"f","description":"format of the output","multiple":false,"options":["json","yaml","yml"],"default":"yaml"},"type":{"name":"type","type":"option","char":"t","description":"type of the output","multiple":false,"options":["breaking","non-breaking","unclassified","all"],"default":"all"},"overrides":{"name":"overrides","type":"option","char":"o","description":"path to JSON file containing the override properties","multiple":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Enable watch mode","allowNo":false}},"args":[{"name":"old","description":"old spec path, URL or context-name","required":true},{"name":"new","description":"new spec path, URL or context-name","required":true}],"_globalFlags":{}},"new":{"id":"new","description":"creates a new asyncapi file","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false},"file-name":{"name":"file-name","type":"option","char":"n","description":"name of the file","multiple":false},"example":{"name":"example","type":"option","char":"e","description":"name of the example to use","multiple":false},"studio":{"name":"studio","type":"boolean","char":"s","description":"open in Studio","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"port in which to start Studio","multiple":false},"no-tty":{"name":"no-tty","type":"boolean","description":"do not use an interactive terminal","allowNo":false}},"args":[],"_globalFlags":{}},"validate":{"id":"validate","description":"validate asyncapi file","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Enable watch mode","allowNo":false}},"args":[{"name":"spec-file","description":"spec path, url, or context-name","required":false}],"_globalFlags":{}},"config":{"id":"config","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"generate:models":{"id":"generate:models","description":"Generates typed models","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false},"output":{"name":"output","type":"option","char":"o","description":"The output directory where the models should be written to. Omitting this flag will write the models to `stdout`.","required":false,"multiple":false},"packageName":{"name":"packageName","type":"option","description":"Go and Java specific, define the package to use for the generated models. This is required when language is `go` or `java`.","required":false,"multiple":false},"namespace":{"name":"namespace","type":"option","description":"C# specific, define the namespace to use for the generated models. This is required when language is `csharp`.","required":false,"multiple":false}},"args":[{"name":"language","description":"The language you want the typed models generated for.","required":true,"options":["typescript","csharp","golang","java","javascript","dart"]},{"name":"file","description":"Path or URL to the AsyncAPI document, or context-name","required":true}],"_globalFlags":{}},"start":{"id":"start","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"start:studio":{"id":"start:studio","description":"starts a new local instance of Studio","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false},"file":{"name":"file","type":"option","char":"f","description":"path to the AsyncAPI file to link with Studio","multiple":false},"port":{"name":"port","type":"option","char":"p","description":"port in which to start Studio","multiple":false}},"args":[],"_globalFlags":{}},"config:context:add":{"id":"config:context:add","description":"Add or modify a context in the store","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false}},"args":[{"name":"context-name","description":"context name","required":true},{"name":"spec-file-path","description":"file path of the spec file","required":true}],"_globalFlags":{}},"config:context:current":{"id":"config:context:current","description":"Shows the current context that is being used","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false}},"args":[],"_globalFlags":{}},"config:context":{"id":"config:context","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"config:context:list":{"id":"config:context:list","description":"List all the stored context in the store","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false}},"args":[],"_globalFlags":{}},"config:context:remove":{"id":"config:context:remove","description":"Delete a context from the store","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false}},"args":[{"name":"context-name","description":"Name of the context to delete","required":true}],"_globalFlags":{}},"config:context:use":{"id":"config:context:use","description":"Set a context as current","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false}},"args":[{"name":"context-name","description":"name of the saved context","required":true}],"_globalFlags":{}}}}
1
+ {"version":"0.24.0","commands":{"convert":{"id":"convert","description":"Convert asyncapi documents older to newer versions","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false},"output":{"name":"output","type":"option","char":"o","description":"path to the file where the result is saved","multiple":false},"target-version":{"name":"target-version","type":"option","char":"t","description":"asyncapi version to convert to","multiple":false,"default":"2.4.0"}},"args":[{"name":"spec-file","description":"spec path, url, or context-name","required":false}],"_globalFlags":{}},"diff":{"id":"diff","description":"Find diff between two asyncapi files","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false},"format":{"name":"format","type":"option","char":"f","description":"format of the output","multiple":false,"options":["json","yaml","yml"],"default":"yaml"},"type":{"name":"type","type":"option","char":"t","description":"type of the output","multiple":false,"options":["breaking","non-breaking","unclassified","all"],"default":"all"},"overrides":{"name":"overrides","type":"option","char":"o","description":"path to JSON file containing the override properties","multiple":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Enable watch mode","allowNo":false}},"args":[{"name":"old","description":"old spec path, URL or context-name","required":true},{"name":"new","description":"new spec path, URL or context-name","required":true}],"_globalFlags":{}},"new":{"id":"new","description":"Creates a new asyncapi file","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false},"file-name":{"name":"file-name","type":"option","char":"n","description":"name of the file","multiple":false},"example":{"name":"example","type":"option","char":"e","description":"name of the example to use","multiple":false},"studio":{"name":"studio","type":"boolean","char":"s","description":"open in Studio","allowNo":false},"port":{"name":"port","type":"option","char":"p","description":"port in which to start Studio","multiple":false},"no-tty":{"name":"no-tty","type":"boolean","description":"do not use an interactive terminal","allowNo":false}},"args":[],"_globalFlags":{}},"validate":{"id":"validate","description":"validate asyncapi file","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Enable watch mode","allowNo":false}},"args":[{"name":"spec-file","description":"spec path, url, or context-name","required":false}],"_globalFlags":{}},"config":{"id":"config","description":"CLI config settings","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"generate:fromTemplate":{"id":"generate:fromTemplate","description":"Generates whatever you want using templates compatible with AsyncAPI Generator.","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"examples":["asyncapi generate fromTemplate asyncapi.yaml @asyncapi/html-template --param version=1.0.0 singleFile=true --output ./docs --force-write"],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false},"disable-hook":{"name":"disable-hook","type":"option","char":"d","description":"Disable a specific hook type or hooks from a given hook type","multiple":true},"install":{"name":"install","type":"boolean","char":"i","description":"Installs the template and its dependencies (defaults to false)","allowNo":false},"debug":{"name":"debug","type":"boolean","description":"Enable more specific errors in the console","allowNo":false},"no-overwrite":{"name":"no-overwrite","type":"option","char":"n","description":"Glob or path of the file(s) to skip when regenerating","multiple":true},"output":{"name":"output","type":"option","char":"o","description":"Directory where to put the generated files (defaults to current directory)","multiple":false},"force-write":{"name":"force-write","type":"boolean","description":"Force writing of the generated files to given directory even if it is a git repo with unstaged files or not empty dir (defaults to false)","allowNo":false},"watch":{"name":"watch","type":"boolean","char":"w","description":"Watches the template directory and the AsyncAPI document, and re-generate the files when changes occur. Ignores the output directory.","allowNo":false},"param":{"name":"param","type":"option","char":"p","description":"Additional param to pass to templates","multiple":true},"map-base-url":{"name":"map-base-url","type":"option","description":"Maps all schema references from base url to local folder","multiple":false}},"args":[{"name":"asyncapi","description":"- Local path, url or context-name pointing to AsyncAPI file","required":true},{"name":"template","description":"- Name of the generator template like for example @asyncapi/html-template or https://github.com/asyncapi/html-template","required":true}],"_globalFlags":{}},"generate":{"id":"generate","description":"Generate typed models or other things like clients, applications or docs using AsyncAPI Generator templates.","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"generate:models":{"id":"generate:models","description":"Generates typed models","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false},"output":{"name":"output","type":"option","char":"o","description":"The output directory where the models should be written to. Omitting this flag will write the models to `stdout`.","required":false,"multiple":false},"packageName":{"name":"packageName","type":"option","description":"Go and Java specific, define the package to use for the generated models. This is required when language is `go` or `java`.","required":false,"multiple":false},"namespace":{"name":"namespace","type":"option","description":"C# specific, define the namespace to use for the generated models. This is required when language is `csharp`.","required":false,"multiple":false}},"args":[{"name":"language","description":"The language you want the typed models generated for.","required":true,"options":["typescript","csharp","golang","java","javascript","dart"]},{"name":"file","description":"Path or URL to the AsyncAPI document, or context-name","required":true}],"_globalFlags":{}},"start":{"id":"start","description":"Start asyncapi studio","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"start:studio":{"id":"start:studio","description":"starts a new local instance of Studio","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false},"file":{"name":"file","type":"option","char":"f","description":"path to the AsyncAPI file to link with Studio","multiple":false},"port":{"name":"port","type":"option","char":"p","description":"port in which to start Studio","multiple":false}},"args":[],"_globalFlags":{}},"config:context:add":{"id":"config:context:add","description":"Add or modify a context in the store","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false}},"args":[{"name":"context-name","description":"context name","required":true},{"name":"spec-file-path","description":"file path of the spec file","required":true}],"_globalFlags":{}},"config:context:current":{"id":"config:context:current","description":"Shows the current context that is being used","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false}},"args":[],"_globalFlags":{}},"config:context":{"id":"config:context","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{},"args":[]},"config:context:list":{"id":"config:context:list","description":"List all the stored context in the store","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false}},"args":[],"_globalFlags":{}},"config:context:remove":{"id":"config:context:remove","description":"Delete a context from the store","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false}},"args":[{"name":"context-name","description":"Name of the context to delete","required":true}],"_globalFlags":{}},"config:context:use":{"id":"config:context:use","description":"Set a context as current","strict":true,"pluginName":"@asyncapi/cli","pluginAlias":"@asyncapi/cli","pluginType":"core","aliases":[],"flags":{"help":{"name":"help","type":"boolean","char":"h","description":"Show CLI help.","allowNo":false}},"args":[{"name":"context-name","description":"name of the saved context","required":true}],"_globalFlags":{}}}}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@asyncapi/cli",
3
3
  "description": "All in one CLI for all AsyncAPI tools",
4
- "version": "0.23.1",
4
+ "version": "0.24.0",
5
5
  "author": "@asyncapi",
6
6
  "bin": {
7
7
  "asyncapi": "./bin/run"
@@ -10,6 +10,7 @@
10
10
  "dependencies": {
11
11
  "@asyncapi/converter": "^1.0.0",
12
12
  "@asyncapi/diff": "^0.4.0",
13
+ "@asyncapi/generator": "^1.9.4",
13
14
  "@asyncapi/modelina": "^0.59.7",
14
15
  "@asyncapi/parser": "^1.16.0",
15
16
  "@asyncapi/studio": "^0.12.15",
@@ -33,6 +34,7 @@
33
34
  "ws": "^8.2.3"
34
35
  },
35
36
  "devDependencies": {
37
+ "@babel/core": "^7.17.9",
36
38
  "@oclif/test": "^2",
37
39
  "@semantic-release/commit-analyzer": "^8.0.1",
38
40
  "@semantic-release/github": "^7.2.3",
@@ -67,7 +69,8 @@
67
69
  "souvikns-oclif": "^2.5.0",
68
70
  "ts-node": "^10.4.0",
69
71
  "tslib": "^2.3.1",
70
- "typescript": "^4.4.3"
72
+ "typescript": "^4.4.3",
73
+ "@asyncapi/minimaltemplate": "./test/minimaltemplate"
71
74
  },
72
75
  "engines": {
73
76
  "node": ">12.16"
@@ -97,7 +100,12 @@
97
100
  "topicSeparator": " ",
98
101
  "topics": {
99
102
  "config:context": {},
100
- "config": {}
103
+ "config": {
104
+ "description": "CLI config settings"
105
+ },
106
+ "generate": {
107
+ "description": "Generate models and template"
108
+ }
101
109
  }
102
110
  },
103
111
  "publishConfig": {