evaengine 0.11.2 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/.ai/analysis.md +657 -0
  2. package/.ai/architecture/runtime.md +504 -0
  3. package/.ai/contracts/public-api.md +447 -0
  4. package/.ai/external-project-guide.md +248 -0
  5. package/.ai/vision.md +314 -0
  6. package/README.md +51 -62
  7. package/bin/engine +15 -21
  8. package/index.js +2 -18
  9. package/package.json +60 -64
  10. package/src/commands/index.js +12 -0
  11. package/{lib → src}/commands/interface.js +5 -11
  12. package/src/commands/make_entity.js +490 -0
  13. package/src/commands/tramp.js +34 -0
  14. package/{lib → src}/config/index.js +7 -9
  15. package/{lib → src}/di.js +11 -26
  16. package/src/engine.js +551 -0
  17. package/{lib → src}/entities/index.js +72 -82
  18. package/{lib → src}/exceptions/index.js +71 -92
  19. package/src/index.js +57 -0
  20. package/src/middlewares/auth.js +67 -0
  21. package/src/middlewares/auth_kong.js +28 -0
  22. package/src/middlewares/debug.js +88 -0
  23. package/src/middlewares/index.js +18 -0
  24. package/src/middlewares/providers.js +73 -0
  25. package/src/middlewares/session.js +58 -0
  26. package/{lib → src}/middlewares/trace.js +74 -86
  27. package/src/middlewares/validator.js +50 -0
  28. package/src/middlewares/view_cache.js +127 -0
  29. package/{lib → src}/services/cache.js +45 -56
  30. package/src/services/config.js +111 -0
  31. package/src/services/env.js +28 -0
  32. package/src/services/event_manager.js +84 -0
  33. package/src/services/http_client.js +160 -0
  34. package/src/services/index.js +27 -0
  35. package/src/services/interface.js +5 -0
  36. package/src/services/joi.js +13 -0
  37. package/src/services/jwt_token.js +89 -0
  38. package/src/services/jwt_token_kong.js +75 -0
  39. package/src/services/logger.js +148 -0
  40. package/src/services/namespace.js +176 -0
  41. package/src/services/now.js +49 -0
  42. package/src/services/providers.js +158 -0
  43. package/src/services/redis.js +55 -0
  44. package/src/services/rest_client.js +112 -0
  45. package/src/swagger/index.js +668 -0
  46. package/{lib → src}/utils/api_scaffold.js +26 -39
  47. package/src/utils/case_converter.js +33 -0
  48. package/src/utils/crc32.js +44 -0
  49. package/src/utils/datetime.js +13 -0
  50. package/src/utils/host.js +39 -0
  51. package/src/utils/index.js +35 -0
  52. package/{lib → src}/utils/pagination.js +22 -31
  53. package/src/utils/random.js +23 -0
  54. package/{lib → src}/utils/smart_query.js +27 -27
  55. package/src/utils/test.js +48 -0
  56. package/src/utils/wrapper.js +8 -0
  57. package/bin/engine-babel +0 -38
  58. package/lib/commands/index.js +0 -25
  59. package/lib/commands/index.js.map +0 -1
  60. package/lib/commands/interface.js.map +0 -1
  61. package/lib/commands/make_entity.js +0 -549
  62. package/lib/commands/make_entity.js.map +0 -1
  63. package/lib/commands/tramp.js +0 -53
  64. package/lib/commands/tramp.js.map +0 -1
  65. package/lib/config/index.js.map +0 -1
  66. package/lib/di.js.map +0 -1
  67. package/lib/engine.js +0 -558
  68. package/lib/engine.js.map +0 -1
  69. package/lib/entities/index.js.map +0 -1
  70. package/lib/exceptions/index.js.map +0 -1
  71. package/lib/index.js +0 -105
  72. package/lib/index.js.map +0 -1
  73. package/lib/middlewares/auth.js +0 -97
  74. package/lib/middlewares/auth.js.map +0 -1
  75. package/lib/middlewares/auth_kong.js +0 -48
  76. package/lib/middlewares/auth_kong.js.map +0 -1
  77. package/lib/middlewares/debug.js +0 -68
  78. package/lib/middlewares/debug.js.map +0 -1
  79. package/lib/middlewares/index.js +0 -45
  80. package/lib/middlewares/index.js.map +0 -1
  81. package/lib/middlewares/providers.js +0 -102
  82. package/lib/middlewares/providers.js.map +0 -1
  83. package/lib/middlewares/session.js +0 -76
  84. package/lib/middlewares/session.js.map +0 -1
  85. package/lib/middlewares/trace.js.map +0 -1
  86. package/lib/middlewares/validator.js +0 -71
  87. package/lib/middlewares/validator.js.map +0 -1
  88. package/lib/middlewares/view_cache.js +0 -160
  89. package/lib/middlewares/view_cache.js.map +0 -1
  90. package/lib/services/cache.js.map +0 -1
  91. package/lib/services/config.js +0 -140
  92. package/lib/services/config.js.map +0 -1
  93. package/lib/services/env.js +0 -40
  94. package/lib/services/env.js.map +0 -1
  95. package/lib/services/event_manager.js +0 -110
  96. package/lib/services/event_manager.js.map +0 -1
  97. package/lib/services/http_client.js +0 -178
  98. package/lib/services/http_client.js.map +0 -1
  99. package/lib/services/index.js +0 -70
  100. package/lib/services/index.js.map +0 -1
  101. package/lib/services/interface.js +0 -12
  102. package/lib/services/interface.js.map +0 -1
  103. package/lib/services/joi.js +0 -28
  104. package/lib/services/joi.js.map +0 -1
  105. package/lib/services/jwt_token.js +0 -120
  106. package/lib/services/jwt_token.js.map +0 -1
  107. package/lib/services/jwt_token_kong.js +0 -111
  108. package/lib/services/jwt_token_kong.js.map +0 -1
  109. package/lib/services/logger.js +0 -172
  110. package/lib/services/logger.js.map +0 -1
  111. package/lib/services/namespace.js +0 -188
  112. package/lib/services/namespace.js.map +0 -1
  113. package/lib/services/now.js +0 -69
  114. package/lib/services/now.js.map +0 -1
  115. package/lib/services/providers.js +0 -195
  116. package/lib/services/providers.js.map +0 -1
  117. package/lib/services/redis.js +0 -77
  118. package/lib/services/redis.js.map +0 -1
  119. package/lib/services/rest_client.js +0 -136
  120. package/lib/services/rest_client.js.map +0 -1
  121. package/lib/swagger/index.js +0 -684
  122. package/lib/swagger/index.js.map +0 -1
  123. package/lib/utils/api_scaffold.js.map +0 -1
  124. package/lib/utils/case_converter.js +0 -48
  125. package/lib/utils/case_converter.js.map +0 -1
  126. package/lib/utils/crc32.js +0 -22
  127. package/lib/utils/crc32.js.map +0 -1
  128. package/lib/utils/datetime.js +0 -21
  129. package/lib/utils/datetime.js.map +0 -1
  130. package/lib/utils/host.js +0 -48
  131. package/lib/utils/host.js.map +0 -1
  132. package/lib/utils/index.js +0 -61
  133. package/lib/utils/index.js.map +0 -1
  134. package/lib/utils/pagination.js.map +0 -1
  135. package/lib/utils/random.js +0 -33
  136. package/lib/utils/random.js.map +0 -1
  137. package/lib/utils/smart_query.js.map +0 -1
  138. package/lib/utils/test.js +0 -62
  139. package/lib/utils/test.js.map +0 -1
  140. package/lib/utils/wrapper.js +0 -9
  141. package/lib/utils/wrapper.js.map +0 -1
@@ -0,0 +1,668 @@
1
+ /*eslint new-cap: [1]*/
2
+ import fs from 'fs';
3
+ import { createRequire } from 'module';
4
+ import { format } from 'util';
5
+ import assert from 'assert';
6
+ import merge from 'lodash/merge.js';
7
+ import * as doctrine from 'doctrine';
8
+ import * as acorn from 'acorn';
9
+ import { glob } from 'glob';
10
+ import * as yaml from 'js-yaml';
11
+ import Entitles from '../entities/index.js';
12
+ import { RuntimeException, StandardException } from '../exceptions/index.js';
13
+
14
+ const require = createRequire(import.meta.url);
15
+
16
+ export class AcornParsingException extends StandardException {
17
+ }
18
+
19
+ export class YamlParsingException extends StandardException {
20
+ /**
21
+ * @param {Annotation} annotation
22
+ * @returns {YamlParsingException}
23
+ */
24
+ setAnnotation(annotation) {
25
+ this.annotation = annotation || {};
26
+ return this;
27
+ }
28
+
29
+ /**
30
+ * @returns {Annotation}
31
+ */
32
+ getAnnotation() {
33
+ return this.annotation || {};
34
+ }
35
+
36
+ toString() {
37
+ const {
38
+ file, start, end, value
39
+ } = this.getAnnotation();
40
+ return format(
41
+ 'Yaml parsing error happened in %s Line[%s - %s]\n Yaml error: %s \nOriginal Yaml Text:',
42
+ file, start, end,
43
+ this.getPrevError() ? this.getPrevError().message : '',
44
+ value ? value.split('\n').map((v, i) => `${(i - 1).toString().padStart(5)} ${v}`) : []
45
+ );
46
+ }
47
+
48
+ constructor(...args) {
49
+ super(...args);
50
+ this.annotation = {};
51
+ }
52
+ }
53
+
54
+
55
+ //Mapping sequelize data types to swagger data types
56
+ //Sequelize types: http://docs.sequelizejs.com/en/latest/api/datatypes/
57
+ //Swagger Data Types: http://swagger.io/specification/
58
+ export const MODEL_TO_FRAGMENT_TYPES_MAPPING = {
59
+ BIGINT: 'integer',
60
+ ENUM: 'string',
61
+ INTEGER: 'integer',
62
+ FLOAT: 'number',
63
+ DOUBLE: 'number',
64
+ DECIMAL: 'number',
65
+ BOOLEAN: 'boolean',
66
+ CHAR: 'string',
67
+ STRING: 'string',
68
+ TEXT: 'string',
69
+ REAL: 'string',
70
+ TIME: 'string',
71
+ DATE: 'string',
72
+ DATEONLY: 'string',
73
+ HSTORE: 'string',
74
+ JSON: 'string',
75
+ JSONB: 'string',
76
+ NOW: 'string',
77
+ BLOB: 'string',
78
+ UUID: 'string',
79
+ UUIDV1: 'string',
80
+ UUIDV4: 'string',
81
+ VIRTUAL: 'string'
82
+ };
83
+
84
+ const modelDefaultValueHandlers = {
85
+ integer: v => parseInt(v, 10),
86
+ boolean: v => Boolean(parseInt(v, 10)),
87
+ number: v => parseFloat(v),
88
+ default: v => v
89
+ };
90
+
91
+ const FRAGMENT_TYPE_PATH = 'path';
92
+ const FRAGMENT_TYPE_DEFINITION = 'definition';
93
+ const FRAGMENT_TYPE_EXCEPTION = 'exception';
94
+ const FRAGMENT_TYPE_UNKNOWN = 'unknown';
95
+
96
+ export class Fragment {
97
+ /**
98
+ * @returns {[*,*,*,*]}
99
+ */
100
+ getTypes() {
101
+ return [FRAGMENT_TYPE_PATH, FRAGMENT_TYPE_DEFINITION,
102
+ FRAGMENT_TYPE_EXCEPTION, FRAGMENT_TYPE_UNKNOWN];
103
+ }
104
+
105
+ isPath() {
106
+ return this.type === FRAGMENT_TYPE_PATH;
107
+ }
108
+
109
+ isDefinition() {
110
+ return this.type === FRAGMENT_TYPE_DEFINITION;
111
+ }
112
+
113
+ isUnknown() {
114
+ return this.type === FRAGMENT_TYPE_UNKNOWN;
115
+ }
116
+
117
+ isException() {
118
+ return this.type === FRAGMENT_TYPE_EXCEPTION;
119
+ }
120
+
121
+ /**
122
+ * @param {Fragment} fragment
123
+ * @returns {Fragment}
124
+ */
125
+ setParentFragment(fragment) {
126
+ assert(fragment && fragment instanceof Fragment, 'Parent fragment must be instance of Fragment');
127
+ this.parent = fragment;
128
+ return this;
129
+ }
130
+
131
+ /**
132
+ * @returns {null|Fragment}
133
+ */
134
+ getParentFragment() {
135
+ return this.parent;
136
+ }
137
+
138
+ correctSwaggerPath(swaggerDoc = {}) {
139
+ return swaggerDoc;
140
+ }
141
+
142
+ toSwaggerDoc(exceptions = {}) {
143
+ if (this.isPath()) {
144
+ const path = Object.keys(this.value)[0];
145
+ const method = Object.keys(this.value[path])[0];
146
+ return {
147
+ paths: {
148
+ [path]: {
149
+ [method]: this.correctSwaggerPath(this.value[path][method])
150
+ }
151
+ }
152
+ };
153
+ }
154
+
155
+ if (this.isDefinition()) {
156
+ const key = Object.keys(this.value)[0];
157
+ return {
158
+ definitions: {
159
+ [key]: this.value[key]
160
+ }
161
+ };
162
+ }
163
+
164
+ if (!this.isUnknown()) {
165
+ return {};
166
+ }
167
+
168
+ if (this.isException()) {
169
+ if (!this.getParentFragment()) {
170
+ return {};
171
+ }
172
+ const key = this.value;
173
+ const exception = exceptions[key];
174
+ if (!exception) {
175
+ return {};
176
+ }
177
+
178
+ const path = Object.keys(this.getParentFragment().value)[0];
179
+ const method = Object.keys(this.getParentFragment().value[path])[0];
180
+ return {
181
+ definitions: {
182
+ [key]: Fragment.exceptionMapping(exception)
183
+ },
184
+ paths: {
185
+ [path]: {
186
+ [method]: {
187
+ responses: {
188
+ [exception.getStatusCode()]: {
189
+ description: this.description,
190
+ schema: {
191
+ $ref: `#/definitions/${key}`
192
+ }
193
+ }
194
+ }
195
+ }
196
+ }
197
+ }
198
+ };
199
+ }
200
+
201
+ return {};
202
+ }
203
+
204
+ static exceptionMapping(exception) {
205
+ return {
206
+ properties: {
207
+ code: {
208
+ type: 'integer',
209
+ format: 'int64'
210
+ },
211
+ message: {
212
+ type: 'string'
213
+ }
214
+ },
215
+ required: [
216
+ 'code',
217
+ 'message'
218
+ ],
219
+ example: {
220
+ code: exception.getCode(),
221
+ message: exception.message
222
+ }
223
+ };
224
+ }
225
+
226
+ /**
227
+ * @param jsdoc
228
+ * @param {Annotation} annotation
229
+ * @returns {Fragment}
230
+ */
231
+ static factory(jsdoc, annotation) {
232
+ const { title, description, type } = jsdoc;
233
+ if (title === 'throws') {
234
+ return new Fragment({
235
+ type: FRAGMENT_TYPE_EXCEPTION,
236
+ description,
237
+ value: type ? type.name : FRAGMENT_TYPE_UNKNOWN,
238
+ file: annotation.file,
239
+ start: annotation.start,
240
+ end: annotation.end
241
+ });
242
+ }
243
+
244
+ let elementType = FRAGMENT_TYPE_UNKNOWN;
245
+ const value = yaml.load(description);
246
+ const key = Object.keys(value)[0];
247
+ if (title === 'swagger') {
248
+ elementType = key.startsWith('/') ? FRAGMENT_TYPE_PATH : FRAGMENT_TYPE_DEFINITION;
249
+ }
250
+ return new Fragment({
251
+ type: elementType,
252
+ description,
253
+ value,
254
+ file: annotation.file,
255
+ start: annotation.start,
256
+ end: annotation.end
257
+ });
258
+ }
259
+
260
+ constructor({
261
+ type, description, value, file, start, end
262
+ }) {
263
+ assert(type && description && value && file && start && end, 'Fragment require type && description && value && file && start && end');
264
+ assert(this.getTypes().includes(type), 'Fragment types not match input');
265
+ this.type = type;
266
+ this.description = description;
267
+ this.value = value;
268
+ this.file = file;
269
+ this.start = start;
270
+ this.end = end;
271
+ this.parent = null;
272
+ }
273
+ }
274
+
275
+ export class Annotation {
276
+ /**
277
+ * @returns {Array<Fragment>}
278
+ */
279
+ getFragments() {
280
+ if (this.fragments) {
281
+ return this.fragments;
282
+ }
283
+ //支持两种注解:
284
+ //1. 所有行首必定为 space*
285
+ //2. 所有行首必定不为 space*
286
+ const unwrap = this.value.startsWith('*\n *');
287
+ const { tags: jsDocs = [] } = doctrine.parse(this.value, { unwrap });
288
+ if (jsDocs.length < 1) {
289
+ return [];
290
+ }
291
+
292
+ /**
293
+ * @type {Array<Fragment>}
294
+ */
295
+ const fragments = [];
296
+ jsDocs.filter(jsDoc =>
297
+ jsDoc.title
298
+ && (jsDoc.title === 'swagger' || jsDoc.title === 'throws')
299
+ && jsDoc.description)
300
+ .forEach((jsDoc) => {
301
+ try {
302
+ fragments.push(Fragment.factory(jsDoc, this));
303
+ } catch (e) {
304
+ this.yamlErrors.push((new YamlParsingException(e)).setAnnotation(this));
305
+ }
306
+ });
307
+
308
+ fragments.forEach((fragment) => {
309
+ if (fragment.isException()) {
310
+ fragment.setParentFragment(fragments.filter(f => f.isPath())[0]);
311
+ }
312
+ });
313
+
314
+ this.fragments = fragments;
315
+ return fragments;
316
+ }
317
+
318
+ toString() {
319
+ return format('Annotation %s [%s - %s]: %s', this.file, this.start, this.end, this.value);
320
+ }
321
+
322
+ getYamlErrors() {
323
+ return this.yamlErrors;
324
+ }
325
+
326
+ constructor({
327
+ type, //Block
328
+ value, //Long string
329
+ file, //String
330
+ start,
331
+ end
332
+ }) {
333
+ assert(type && type === 'Block', 'Annotation type must be Block');
334
+ assert(value && typeof value === 'string', 'Annotation value must be Block');
335
+ assert(file, 'Annotation must have file');
336
+ this.value = value;
337
+ this.file = file;
338
+ this.start = start;
339
+ this.end = end;
340
+ this.yamlErrors = [];
341
+ this.fragments = null;
342
+ }
343
+ }
344
+
345
+ export class AnnotationsContainer {
346
+ /**
347
+ * @returns {string}
348
+ */
349
+ getFile() {
350
+ return this.file;
351
+ }
352
+
353
+ /**
354
+ * @returns {boolean}
355
+ */
356
+ hasAnnotations() {
357
+ return this.annotations.length > 0;
358
+ }
359
+
360
+ /**
361
+ * @returns {Array<Annotation>}
362
+ */
363
+ getAnnotations() {
364
+ return this.annotations;
365
+ }
366
+
367
+ /**
368
+ * @returns {Array.<Fragment>}
369
+ */
370
+ collectFragments() {
371
+ if (this.fragments.length > 0) {
372
+ return this.fragments;
373
+ }
374
+
375
+ for (const annotation of this.getAnnotations()) {
376
+ this.fragments = this.fragments.concat(annotation.getFragments());
377
+ }
378
+
379
+ return this.fragments;
380
+ }
381
+
382
+ /**
383
+ * @returns {Array}
384
+ */
385
+ collectYamlErrors() {
386
+ let errors = [];
387
+ for (const annotation of this.getAnnotations()) {
388
+ errors = errors.concat(annotation.getYamlErrors());
389
+ }
390
+ return errors;
391
+ }
392
+
393
+ constructor(file, acornComments) {
394
+ assert(file && typeof file === 'string', 'Annotations require a file input');
395
+ assert(acornComments && Array.isArray(acornComments), `Annotations for ${file} require an array of Acorn comments input`);
396
+ this.file = file;
397
+ //Annotations MUST start with double stars
398
+ this.annotations = acornComments
399
+ .filter(v => v.type === 'Block' && v.value.startsWith('*\n'))
400
+ .map(c => new Annotation(Object.assign(c, { file })));
401
+ this.fragments = [];
402
+ }
403
+ }
404
+
405
+ /**
406
+ * A Swagger Json document generator
407
+ * Generate document from
408
+ * - JS source code annotations (Parser is Doctrine)
409
+ * - ORM entities (Based on Sequelize)
410
+ * - EvaEngine Exceptions
411
+ */
412
+ export class ExSwagger {
413
+ /**
414
+ * Get file path array by glob
415
+ * @param path
416
+ * @param options
417
+ * @returns {Promise|Array.<string>}
418
+ */
419
+ static async scanFiles(path, options = {}) {
420
+ return glob(path, options);
421
+ }
422
+
423
+ /**
424
+ * Get annotations from comments
425
+ * An annotation MUST start with double stars
426
+ * @param files {Array.<string>}
427
+ * @returns {Array.<AnnotationsContainer>}
428
+ */
429
+ static async filesToAnnotationsContainers(files) {
430
+ const results = [];
431
+ for (const file of files) {
432
+ const comments = [];
433
+ const source = await fs.readFileSync(file);
434
+ try {
435
+ acorn.parse(source, {
436
+ ecmaVersion: 9,
437
+ allowImportExportEverywhere: true,
438
+ onComment: comments
439
+ });
440
+ } catch (e) {
441
+ throw (new AcornParsingException(`Acorn parsing file ${file} failed`)).setPrevError(e);
442
+ }
443
+
444
+ results.push(new AnnotationsContainer(file, comments));
445
+ }
446
+ return results;
447
+ }
448
+
449
+ static modelToSwaggerDefinition(model) {
450
+ const definition = {
451
+ type: 'object',
452
+ properties: {}
453
+ };
454
+ const requires = [];
455
+ Object.keys(model).forEach((columnName) => {
456
+ const column = model[columnName];
457
+ const swaggerType = MODEL_TO_FRAGMENT_TYPES_MAPPING[column.type.key];
458
+ const property = {
459
+ type: swaggerType,
460
+ description: column.comment
461
+ };
462
+ if (column.defaultValue) {
463
+ const defaultValueHandler = modelDefaultValueHandlers[swaggerType] ?
464
+ modelDefaultValueHandlers[swaggerType] : modelDefaultValueHandlers.default;
465
+ property.default = defaultValueHandler(column.defaultValue);
466
+ }
467
+ if (column.allowNull === false) {
468
+ requires.push(columnName);
469
+ }
470
+ definition.properties[columnName] = property;
471
+ });
472
+ definition.required = requires;
473
+ return definition;
474
+ }
475
+
476
+ static modelsToSwaggerDefinitions(models, blacklist = []) {
477
+ const definitions = new Map();
478
+ Object.keys(models).forEach((modelName) => {
479
+ if (blacklist.includes(modelName)) {
480
+ return true;
481
+ }
482
+ const model = models[modelName];
483
+ const attributes = model.attributes || model.rawAttributes;
484
+ const definition = ExSwagger.modelToSwaggerDefinition(attributes);
485
+ definitions.set(modelName, definition);
486
+ return true;
487
+ });
488
+ return definitions;
489
+ }
490
+
491
+ static async scanExceptions(path, exceptionInterface = Error) {
492
+ const exceptions = {};
493
+ const files = await ExSwagger.scanFiles(path);
494
+ if (files.length < 1) {
495
+ return exceptions;
496
+ }
497
+ for (const file of files) {
498
+ const exceptionsInFile = require(file);
499
+ Object.keys(exceptionsInFile).forEach((exceptionName) => {
500
+ const exceptionClass = exceptionsInFile[exceptionName];
501
+ const exception = new exceptionClass(exceptionName); //eslint-disable-line new-cap
502
+ if (exception instanceof exceptionInterface) {
503
+ exceptions[exceptionName] = exception;
504
+ }
505
+ });
506
+ }
507
+ return exceptions;
508
+ }
509
+
510
+
511
+ async exportJson(dist = this.swaggerDocsPath) {
512
+ this.logger.debug('Start export swagger docs by meta %j', this.getStates());
513
+ const fileGroups = await Promise
514
+ .all(this.sourceFilesPath.map(path => ExSwagger.scanFiles(path)));
515
+ let files = [];
516
+ fileGroups.forEach((fileGroup) => {
517
+ files = files.concat(fileGroup);
518
+ });
519
+ if (!files || files.length < 1) {
520
+ throw new RuntimeException('No swagger source files found');
521
+ }
522
+ this.logger.debug('Scanner will scan %s files under %j:', files.length, this.sourceFilesPath);
523
+
524
+ const annotationsContainers = await ExSwagger.filesToAnnotationsContainers(files);
525
+ let fragments = [];
526
+ annotationsContainers.forEach((annotationsContainer) => {
527
+ if (!annotationsContainer.hasAnnotations()) {
528
+ return false;
529
+ }
530
+ const annotationFragments = annotationsContainer.collectFragments();
531
+ fragments = fragments.concat(annotationFragments);
532
+ this.logger.debug(
533
+ 'Scanner found %s annotations and collected %s fragments in file %s',
534
+ annotationsContainer.getAnnotations().length.toString().padStart(3),
535
+ annotationFragments.length.toString().padStart(3),
536
+ annotationsContainer.getFile()
537
+ );
538
+
539
+ const yamlErrors = annotationsContainer.collectYamlErrors();
540
+ yamlErrors.forEach(yamlError => this.logger.error(yamlError));
541
+ return true;
542
+ });
543
+
544
+ this.logger.debug('Scanner collected %s fragments in total', fragments.length);
545
+
546
+ const template = this.swaggerDocsTemplate;
547
+ const exceptions = {};
548
+ for (const exceptionPath of this.exceptionPaths) {
549
+ this.logger.debug('Search exception in %s', exceptionPath);
550
+ const exceptionsInFile = await ExSwagger.scanExceptions(
551
+ exceptionPath,
552
+ this.exceptionInterface
553
+ );
554
+ Object.assign(exceptions, exceptionsInFile);
555
+ this.logger.debug(
556
+ 'Scanner found %s exceptions',
557
+ Object.keys(exceptions).length, Object.keys(exceptions)
558
+ );
559
+ }
560
+ const modelDefinitions = this.models ?
561
+ ExSwagger.modelsToSwaggerDefinitions(this.models, this.modelBlacklist) : new Map();
562
+ const swaggerDocs = ExSwagger.mergeAll(template, fragments, exceptions, modelDefinitions);
563
+ this.logger.debug('Export to', dist);
564
+ await fs.writeFileSync(dist, JSON.stringify(swaggerDocs));
565
+ return swaggerDocs;
566
+ }
567
+
568
+ /**
569
+ * @param _template
570
+ * @param {Array<Fragment>} fragments
571
+ * @param exceptions
572
+ * @param modelDefinitions
573
+ * @returns {*}
574
+ */
575
+ static mergeAll(_template, fragments, exceptions, modelDefinitions) {
576
+ const template = _template;
577
+ fragments.forEach(fragment => merge(template, fragment.toSwaggerDoc(exceptions)));
578
+ if (modelDefinitions) {
579
+ modelDefinitions.forEach((definition, modelName) => {
580
+ template.definitions[modelName] = definition;
581
+ });
582
+ }
583
+ return template;
584
+ }
585
+
586
+ getStates() {
587
+ return {
588
+ swaggerTemplate: this.swaggerDocsTemplate,
589
+ modelBlacklist: this.modelBlacklist,
590
+ sourceFilesPath: this.sourceFilesPath,
591
+ exceptionPaths: this.exceptionPaths,
592
+ compileDistPath: this.compileDistPath,
593
+ swaggerUIPath: this.swaggerUIPath,
594
+ swaggerDocsPath: this.swaggerDocsPath
595
+ };
596
+ }
597
+
598
+ getSwaggerUIPath() {
599
+ return this.swaggerUIPath;
600
+ }
601
+
602
+ async getSwaggerIndexHtml() {
603
+ const uiPath = this.getSwaggerUIPath();
604
+ const content = await fs.readFileSync(`${uiPath}/index.html`);
605
+ return content.toString().replace(
606
+ 'https://petstore.swagger.io/v2/swagger/index.json',
607
+ this.swaggerDocsPath.replace(this.compileDistPath, '')
608
+ );
609
+ }
610
+
611
+ getCompileDistPath() {
612
+ return this.compileDistPath;
613
+ }
614
+
615
+ constructor({
616
+ sourceRootPath,
617
+ compileDistPath,
618
+ models,
619
+ modelBlacklist = [],
620
+ swaggerDocsTemplate,
621
+ logger,
622
+ swaggerUIPath,
623
+ swaggerDocsPath = `${compileDistPath}/docs.json`,
624
+ sourceFilesPath = `${sourceRootPath}/**/*.js`,
625
+ exceptionInterface = StandardException,
626
+ extraSourcePaths = [`${import.meta.dirname}/../utils/**/*.js`],
627
+ exceptionPaths
628
+ }) {
629
+ this.logger = logger ||
630
+ {
631
+ debug: () => {
632
+ },
633
+ warn: () => {
634
+ },
635
+ error: () => {
636
+ }
637
+ };
638
+
639
+ if (!swaggerDocsTemplate) {
640
+ throw new RuntimeException('No swagger docs template input');
641
+ }
642
+ this.swaggerDocsTemplate = swaggerDocsTemplate;
643
+ if (models && !(models instanceof Entitles)) {
644
+ throw new RuntimeException('Input models must instance of engine.Entities');
645
+ }
646
+ this.models = models ? models.getAll() : null;
647
+ this.modelBlacklist = modelBlacklist;
648
+
649
+ this.sourceFilesPath = Array.isArray(sourceFilesPath)
650
+ ? extraSourcePaths.concat(sourceFilesPath) :
651
+ extraSourcePaths.concat([sourceFilesPath]);
652
+ this.exceptionPaths = exceptionPaths ?
653
+ [`${import.meta.dirname}/../exceptions`].concat(exceptionPaths) : [`${import.meta.dirname}/../exceptions`];
654
+ this.exceptionInterface = exceptionInterface;
655
+ this.compileDistPath = compileDistPath;
656
+ if (swaggerUIPath) {
657
+ this.swaggerUIPath = swaggerUIPath;
658
+ } else {
659
+ this.swaggerUIPath = `${import.meta.dirname}/../../node_modules/swagger-ui-dist`;
660
+ try {
661
+ fs.accessSync(this.swaggerUIPath, fs.F_OK);
662
+ } catch {
663
+ this.swaggerUIPath = `${import.meta.dirname}/../../../swagger-ui-dist`; //For NPM v3.x
664
+ }
665
+ }
666
+ this.swaggerDocsPath = swaggerDocsPath;
667
+ }
668
+ }