@adaptivestone/framework 3.4.3 → 4.1.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +37 -3
  2. package/LICENCE +21 -0
  3. package/cluster.js +3 -3
  4. package/commands/CreateUser.js +27 -0
  5. package/commands/Documentation.js +1 -1
  6. package/commands/GetOpenApiJson.js +53 -23
  7. package/commands/migration/Create.js +2 -2
  8. package/config/auth.js +1 -1
  9. package/config/i18n.js +4 -3
  10. package/config/mail.js +5 -1
  11. package/controllers/Home.js +2 -2
  12. package/controllers/Home.test.js +11 -0
  13. package/controllers/index.js +15 -15
  14. package/folderConfig.js +1 -1
  15. package/helpers/yup.js +24 -0
  16. package/index.js +8 -0
  17. package/models/User.js +40 -30
  18. package/models/User.test.js +68 -18
  19. package/modules/AbstractController.js +144 -208
  20. package/modules/AbstractModel.js +2 -1
  21. package/modules/Base.js +3 -2
  22. package/modules/BaseCli.js +6 -2
  23. package/package.json +20 -16
  24. package/server.d.ts +1 -1
  25. package/server.js +25 -8
  26. package/services/cache/Cache.d.ts +3 -3
  27. package/services/cache/Cache.js +17 -3
  28. package/services/documentation/DocumentationGenerator.js +171 -0
  29. package/services/http/HttpServer.js +16 -96
  30. package/services/http/middleware/AbstractMiddleware.js +20 -0
  31. package/services/http/middleware/GetUserByToken.js +4 -0
  32. package/services/http/middleware/I18n.js +119 -0
  33. package/services/http/middleware/I18n.test.js +77 -0
  34. package/services/http/middleware/Pagination.js +56 -0
  35. package/services/http/middleware/PrepareAppInfo.test.js +22 -0
  36. package/services/http/middleware/{Middlewares.test.js → RateLimiter.test.js} +1 -1
  37. package/services/http/middleware/RequestLogger.js +22 -0
  38. package/services/http/middleware/RequestParser.js +36 -0
  39. package/services/messaging/email/index.js +162 -42
  40. package/services/messaging/email/resources/.gitkeep +1 -0
  41. package/services/validate/ValidateService.js +161 -0
  42. package/services/validate/ValidateService.test.js +105 -0
  43. package/services/validate/drivers/AbstractValidator.js +37 -0
  44. package/services/validate/drivers/CustomValidator.js +52 -0
  45. package/services/validate/drivers/YupValidator.js +103 -0
  46. package/tests/setup.js +2 -0
  47. package/services/messaging/email/templates/emptyTemplate/style.less +0 -0
  48. package/services/messaging/email/templates/password/html.handlebars +0 -13
  49. package/services/messaging/email/templates/password/style.less +0 -0
  50. package/services/messaging/email/templates/password/subject.handlebars +0 -1
  51. package/services/messaging/email/templates/password/text.handlebars +0 -1
  52. package/services/messaging/email/templates/verification/style.less +0 -0
@@ -22,7 +22,7 @@ describe('user model', () => {
22
22
  const user = await global.server.app.getModel('User').findOne({
23
23
  email: userEmail,
24
24
  });
25
- expect(user.password !== userPassword).toBe(true);
25
+ expect(user.password).not.toBe(userPassword);
26
26
  });
27
27
 
28
28
  it('passwords should not be changed on other fields save', async () => {
@@ -32,22 +32,74 @@ describe('user model', () => {
32
32
  });
33
33
  const psw = user.password;
34
34
  user.email = 'rrrr';
35
- user.save();
35
+ await user.save();
36
+ user.email = userEmail;
37
+ await user.save();
36
38
 
37
- expect(user.password === psw).toBe(true);
39
+ expect(user.password).toBe(psw);
38
40
  });
39
41
 
40
- describe('getUserByVerificationToken', () => {
42
+ describe('getUserByEmailAndPassword', () => {
43
+ it('should WORK with valid creds', async () => {
44
+ expect.assertions(1);
45
+ const userModel = await global.server.app.getModel('User');
46
+ const user = await userModel.getUserByEmailAndPassword(
47
+ userEmail,
48
+ userPassword,
49
+ );
50
+ expect(user.id).toBe(globalUser.id);
51
+ });
52
+
53
+ it('should NOT with INvalid creds', async () => {
54
+ expect.assertions(1);
55
+ const userModel = await global.server.app.getModel('User');
56
+ const user = await userModel.getUserByEmailAndPassword(
57
+ userEmail,
58
+ 'wrongPassword',
59
+ );
60
+ expect(user).toBe(false);
61
+ });
62
+
63
+ it('should NOT with wrong email', async () => {
64
+ expect.assertions(1);
65
+ const userModel = await global.server.app.getModel('User');
66
+ const user = await userModel.getUserByEmailAndPassword(
67
+ 'not@exists.com',
68
+ userPassword,
69
+ );
70
+ expect(user).toBe(false);
71
+ });
72
+ });
73
+
74
+ describe('getUserByToken', () => {
41
75
  it('should NOT work for non valid token', async () => {
42
- expect.assertions(2);
76
+ expect.assertions(1);
77
+
78
+ const user = await global.server.app
79
+ .getModel('User')
80
+ .getUserByToken('fake one');
81
+ expect(user).toBe(false);
82
+ });
43
83
 
84
+ it('should work for VALID token', async () => {
85
+ expect.assertions(1);
86
+ const token = await globalUser.generateToken();
44
87
  const user = await global.server.app
45
88
  .getModel('User')
46
- .getUserByVerificationToken('fake one')
47
- .catch((e) => {
48
- expect(e).toBeDefined();
49
- });
50
- expect(user).toBeUndefined();
89
+ .getUserByToken(token.token);
90
+ expect(user.id).toBe(globalUser.id);
91
+ });
92
+ });
93
+
94
+ describe('getUserByVerificationToken', () => {
95
+ it('should NOT work for non valid token', async () => {
96
+ expect.assertions(1);
97
+
98
+ await expect(
99
+ global.server.app
100
+ .getModel('User')
101
+ .getUserByVerificationToken('fake one'),
102
+ ).rejects.toStrictEqual(new Error('User not exists'));
51
103
  });
52
104
 
53
105
  it('should work for VALID token', async () => {
@@ -65,15 +117,13 @@ describe('user model', () => {
65
117
 
66
118
  describe('getUserByPasswordRecoveryToken', () => {
67
119
  it('should NOT work for non valid token', async () => {
68
- expect.assertions(2);
120
+ expect.assertions(1);
69
121
 
70
- const user = await global.server.app
71
- .getModel('User')
72
- .getUserByPasswordRecoveryToken('fake one')
73
- .catch((e) => {
74
- expect(e).toBeDefined();
75
- });
76
- expect(user).toBeUndefined();
122
+ await expect(
123
+ global.server.app
124
+ .getModel('User')
125
+ .getUserByPasswordRecoveryToken('fake one'),
126
+ ).rejects.toStrictEqual(new Error('User not exists'));
77
127
  });
78
128
 
79
129
  it('should work for VALID token', async () => {
@@ -1,19 +1,17 @@
1
- /* eslint-disable array-callback-return */
2
1
  /* eslint-disable no-restricted-syntax */
3
2
  /* eslint-disable guard-for-in */
4
3
  const express = require('express');
5
- const merge = require('deepmerge');
6
-
7
4
  const Base = require('./Base');
8
5
  const GetUserByToken = require('../services/http/middleware/GetUserByToken');
9
6
  const Auth = require('../services/http/middleware/Auth');
10
-
7
+ const ValidateService = require('../services/validate/ValidateService');
8
+ const DocumentationGenerator = require('../services/documentation/DocumentationGenerator');
11
9
  /**
12
10
  * Abstract controller. You should extend any controller from them.
13
11
  * Place you cintroller into controller folder and it be inited in auto way.
14
12
  * By default name of route will be controller name not file name. But please name it in same ways.
15
13
  * You can overwrite base controllers byt creating controllers with tha same file name (yes file name, not class name)
16
- * In most cases you will want to have a 'home' route that not include controller name. For this case please check 'getExpressPath'
14
+ * In most cases you will want to have a 'home' route that not include controller name. For this case please check ' getHttpPath'
17
15
  */
18
16
  class AbstractController extends Base {
19
17
  constructor(app, prefix, isExpressMergeParams = false) {
@@ -24,7 +22,14 @@ class AbstractController extends Base {
24
22
  mergeParams: isExpressMergeParams,
25
23
  });
26
24
  const { routes } = this;
27
- const expressPath = this.getExpressPath();
25
+ let httpPath = this.getHttpPath();
26
+
27
+ if (this.getExpressPath) {
28
+ this.logger.warn(
29
+ `getExpressPath deprecated. Please use getHttpPath instead. Will be removed on v5`,
30
+ );
31
+ httpPath = this.getExpressPath();
32
+ }
28
33
 
29
34
  /**
30
35
  * Grab route middleware onlo one Map
@@ -47,69 +52,15 @@ class AbstractController extends Base {
47
52
  });
48
53
  });
49
54
 
50
- /**
51
- * Parse middlewares to be an object.
52
- */
53
- const parseMiddlewares = (middlewareMap) => {
54
- const middlewaresInfo = [];
55
- // eslint-disable-next-line prefer-const
56
- for (let [path, middleware] of middlewareMap) {
57
- if (!Array.isArray(middleware)) {
58
- middleware = [middleware];
59
- }
60
- for (const M of middleware) {
61
- let method = 'all';
62
- let realPath = path;
63
- if (typeof realPath !== 'string') {
64
- this.logger.error(`Path not a string ${realPath}. Please check it`);
65
- // eslint-disable-next-line no-continue
66
- continue;
67
- }
68
- if (!realPath.startsWith('/')) {
69
- method = realPath.split('/')[0]?.toLowerCase();
70
- if (!method) {
71
- this.logger.error(`Method not found for ${realPath}`);
72
- // eslint-disable-next-line no-continue
73
- continue;
74
- }
75
- realPath = realPath.substring(method.length);
76
- }
77
- if (typeof this.router[method] !== 'function') {
78
- this.logger.error(
79
- `Method ${method} not exist for middleware. Please check your codebase`,
80
- );
81
- // eslint-disable-next-line no-continue
82
- continue;
83
- }
84
- const fullPath = `/${expressPath}/${realPath.toUpperCase()}`
85
- .split('//')
86
- .join('/')
87
- .split('//')
88
- .join('/');
89
- let MiddlewareFunction = M;
90
- let middlewareParams = {};
91
- if (Array.isArray(M)) {
92
- [MiddlewareFunction, middlewareParams] = M;
93
- }
94
-
95
- middlewaresInfo.push({
96
- name: MiddlewareFunction.name,
97
- method,
98
- path: realPath,
99
- fullPath,
100
- params: middlewareParams,
101
- authParams: new MiddlewareFunction(this.app, middlewareParams)
102
- ?.usedAuthParameters,
103
- MiddlewareFunction,
104
- });
105
- }
106
- }
107
- return middlewaresInfo;
108
- };
109
-
110
- const routeMiddlewaresReg = parseMiddlewares(routeMiddlewares);
55
+ const routeMiddlewaresReg = this.parseMiddlewares(
56
+ routeMiddlewares,
57
+ httpPath,
58
+ );
59
+ const middlewaresInfo = this.parseMiddlewares(
60
+ this.constructor.middleware,
61
+ httpPath,
62
+ );
111
63
 
112
- const middlewaresInfo = parseMiddlewares(this.constructor.middleware);
113
64
  const routesInfo = [];
114
65
 
115
66
  /**
@@ -141,11 +92,13 @@ class AbstractController extends Base {
141
92
  (middleware) =>
142
93
  middleware.path === path && middleware.method === verb,
143
94
  );
95
+
144
96
  let routeObject = routes[verb][path];
145
97
  if (Object.prototype.toString.call(routeObject) !== '[object Object]') {
146
98
  routeObject = {
147
99
  handler: routeObject,
148
100
  request: null,
101
+ query: null,
149
102
  middleware: null,
150
103
  };
151
104
 
@@ -165,7 +118,7 @@ class AbstractController extends Base {
165
118
  fnName = fnName.name;
166
119
  }
167
120
 
168
- const fullPath = `/${expressPath}/${path}`
121
+ const fullPath = `/${httpPath}/${path}`
169
122
  .split('//')
170
123
  .join('/')
171
124
  .split('//')
@@ -176,6 +129,7 @@ class AbstractController extends Base {
176
129
  description: routeObject?.description,
177
130
  method: verb.toUpperCase(),
178
131
  fields: routeObject?.request?.fields,
132
+ queryFields: routeObject?.query?.fields,
179
133
  path,
180
134
  fullPath,
181
135
  });
@@ -192,61 +146,51 @@ class AbstractController extends Base {
192
146
  new MiddlewareFunction(this.app, params).getMiddleware(),
193
147
  );
194
148
  }
195
- const { controllerValidationAbortEarly } =
196
- this.app.getConfig('validate');
149
+
197
150
  this.router[verb](
198
151
  path,
199
152
  additionalMiddlewares || [],
200
153
  async (req, res, next) => {
201
- if (routeObject.request) {
202
- if (typeof routeObject.request.validate !== 'function') {
203
- this.logger.error('request.validate should be a function');
204
- }
205
- if (typeof routeObject.request.cast !== 'function') {
206
- this.logger.error('request.cast should be a function');
207
- }
208
- const bodyAndQuery = merge(req.query, req.body);
209
-
210
- try {
211
- await routeObject.request.validate(bodyAndQuery, {
212
- abortEarly: controllerValidationAbortEarly,
213
- req,
214
- });
215
- } catch (e) {
216
- let { errors } = e;
217
- // translate it
218
- if (req.i18n && errors) {
219
- errors = errors.map((err) => req.i18n.t(err));
220
- }
221
- this.logger.error(
222
- `Request validation failed with message: ${e.message}. errors: ${errors}`,
223
- );
224
-
225
- const errorAnswer = {};
226
- if (!e.inner || !e.inner.length) {
227
- errorAnswer[e.path] = errors;
228
- } else {
229
- e.inner.forEach((err) => {
230
- errorAnswer[err.path] = err.errors;
231
- if (req.i18n && err.errors) {
232
- errorAnswer[err.path] = err.errors.map((err1) =>
233
- req.i18n.t(err1),
234
- );
235
- }
236
- });
237
- }
238
-
239
- return res.status(400).json({
240
- errors: errorAnswer,
241
- });
242
- }
243
- req.appInfo.request = await routeObject.request.cast(
244
- bodyAndQuery,
245
- {
246
- stripUnknown: true,
247
- req,
154
+ const requestObj = {
155
+ query: req.query,
156
+ body: req.body,
157
+ appInfo: req.appInfo,
158
+ };
159
+ try {
160
+ req.appInfo.request = await new ValidateService(
161
+ this.app,
162
+ routeObject?.request,
163
+ ).validateReqData(requestObj, {
164
+ selectedReqData: req.body,
165
+ additionalMiddlewareFieldsData: {
166
+ middlewaresInfo,
167
+ routeMiddlewaresReg,
168
+ options: {
169
+ method: verb,
170
+ path: fullPath,
171
+ prefix: 'request',
172
+ },
248
173
  },
249
- );
174
+ });
175
+ req.appInfo.query = await new ValidateService(
176
+ this.app,
177
+ routeObject?.query,
178
+ ).validateReqData(requestObj, {
179
+ selectedReqData: req.query,
180
+ additionalMiddlewareFieldsData: {
181
+ middlewaresInfo,
182
+ routeMiddlewaresReg,
183
+ options: {
184
+ method: verb,
185
+ path: fullPath,
186
+ prefix: 'query',
187
+ },
188
+ },
189
+ });
190
+ } catch (err) {
191
+ return res.status(400).json({
192
+ errors: err.message,
193
+ });
250
194
  }
251
195
  req.body = new Proxy(req.body, {
252
196
  get: (target, prop) => {
@@ -256,6 +200,14 @@ class AbstractController extends Base {
256
200
  return target[prop];
257
201
  },
258
202
  });
203
+ req.query = new Proxy(req.query, {
204
+ get: (target, prop) => {
205
+ this.logger.warn(
206
+ 'Please not use "req.query" directly. Implement "query" and use "req.appInfo.query" ',
207
+ );
208
+ return target[prop];
209
+ },
210
+ });
259
211
 
260
212
  if (!routeObject.handler) {
261
213
  this.logger.error(`Route object have no handler defined`);
@@ -276,6 +228,7 @@ class AbstractController extends Base {
276
228
  }
277
229
  return routeObject.handler.call(this, req, res, next).catch((e) => {
278
230
  this.logger.error(e.message);
231
+ // eslint-disable-next-line no-console
279
232
  console.error(e);
280
233
  return res.status(500).json({
281
234
  message:
@@ -311,104 +264,87 @@ class AbstractController extends Base {
311
264
  text.push(`Time: ${Date.now() - time} ms`);
312
265
 
313
266
  this.logger.verbose(text.join('\n'));
267
+ this.loadedTime = Date.now() - time;
314
268
 
315
269
  /**
316
270
  * Generate documentation
317
271
  */
272
+ if (!this.app.httpServer) {
273
+ this.app.documentation.push(
274
+ DocumentationGenerator.convertDataToDocumentationElement(
275
+ this.getConstructorName(),
276
+ routesInfo,
277
+ middlewaresInfo,
278
+ routeMiddlewaresReg,
279
+ ),
280
+ );
281
+ } else {
282
+ this.app.httpServer.express.use(httpPath, this.router);
283
+ }
284
+ }
318
285
 
319
- const processingFields = (fieldsByRoute) => {
320
- const fields = [];
321
- if (!fieldsByRoute) {
322
- return fields;
286
+ /**
287
+ * Parse middlewares to be an object.
288
+ */
289
+ parseMiddlewares(middlewareMap, httpPath) {
290
+ const middlewaresInfo = [];
291
+ // eslint-disable-next-line prefer-const
292
+ for (let [path, middleware] of middlewareMap) {
293
+ if (!Array.isArray(middleware)) {
294
+ middleware = [middleware];
323
295
  }
324
- const entries = Object.entries(fieldsByRoute);
325
- entries.forEach(([key, value]) => {
326
- const field = {};
327
- field.name = key;
328
- field.type = value.type;
329
- if (value.exclusiveTests) {
330
- field.isRequired = value.exclusiveTests.required;
296
+ for (const M of middleware) {
297
+ let method = 'all';
298
+ let realPath = path;
299
+ if (typeof realPath !== 'string') {
300
+ this.logger.error(`Path not a string ${realPath}. Please check it`);
301
+ // eslint-disable-next-line no-continue
302
+ continue;
331
303
  }
332
- if (value?.innerType) {
333
- field.innerType = value?.innerType?.type;
304
+ if (!realPath.startsWith('/')) {
305
+ method = realPath.split('/')[0]?.toLowerCase();
306
+ if (!method) {
307
+ this.logger.error(`Method not found for ${realPath}`);
308
+ // eslint-disable-next-line no-continue
309
+ continue;
310
+ }
311
+ realPath = realPath.substring(method.length);
334
312
  }
335
-
336
- if (value.fields) {
337
- field.fields = [];
338
- // eslint-disable-next-line no-shadow
339
- const entries = Object.entries(value.fields);
340
- // eslint-disable-next-line no-shadow
341
- entries.forEach(([key, value]) => {
342
- field.fields.push({
343
- name: key,
344
- type: value.type,
345
- });
346
- });
313
+ if (typeof this.router[method] !== 'function') {
314
+ this.logger.error(
315
+ `Method ${method} not exist for middleware. Please check your codebase`,
316
+ );
317
+ // eslint-disable-next-line no-continue
318
+ continue;
319
+ }
320
+ const fullPath = `/${httpPath}/${realPath.toUpperCase()}`
321
+ .split('//')
322
+ .join('/')
323
+ .split('//')
324
+ .join('/');
325
+ let MiddlewareFunction = M;
326
+ let middlewareParams = {};
327
+ if (Array.isArray(M)) {
328
+ [MiddlewareFunction, middlewareParams] = M;
347
329
  }
348
- fields.push(field);
349
- });
350
- return fields;
351
- };
352
-
353
- if (!this.app.httpServer) {
354
- this.app.documentation.push({
355
- contollerName: this.getConstructorName(),
356
- routesInfo: routesInfo.map((route) => ({
357
- [route.fullPath]: {
358
- method: route.method,
359
- name: route.name,
360
- description: route?.description,
361
- fields: processingFields(route.fields),
362
- routeMiddlewares: routeMiddlewaresReg
363
- // eslint-disable-next-line consistent-return
364
- .map((middleware) => {
365
- const routeFullPath = route.fullPath.toUpperCase();
366
- const middlewareFullPath = middleware.fullPath.toUpperCase();
367
- if (
368
- route.method.toLowerCase() ===
369
- middleware.method.toLowerCase() &&
370
- (middlewareFullPath === routeFullPath ||
371
- middlewareFullPath === `${routeFullPath}*`)
372
- ) {
373
- return {
374
- name: middleware.name,
375
- params: middleware.params,
376
- authParams: middleware.authParams,
377
- };
378
- }
379
- })
380
- .filter(Boolean),
381
- controllerMiddlewares: [
382
- ...new Set(
383
- middlewaresInfo
384
- .filter((middleware) => {
385
- const routeFullPath = route.fullPath.toUpperCase();
386
- const middlewareFullPath =
387
- middleware.fullPath.toUpperCase();
388
- const middlewareFullPathWithSliced = middleware.fullPath
389
- .toUpperCase()
390
- .slice(0, -1);
391
330
 
392
- return (
393
- middlewareFullPath === routeFullPath ||
394
- middlewareFullPath === `${routeFullPath}*` ||
395
- routeFullPath?.indexOf(middlewareFullPathWithSliced) !==
396
- -1
397
- );
398
- })
399
- .map(({ name, params, authParams }) => ({
400
- name,
401
- params,
402
- authParams,
403
- })),
404
- ),
405
- ],
406
- },
407
- })),
408
- });
409
- } else {
410
- this.app.httpServer.express.use(expressPath, this.router);
331
+ middlewaresInfo.push({
332
+ name: MiddlewareFunction.name,
333
+ method,
334
+ path: realPath,
335
+ fullPath,
336
+ params: middlewareParams,
337
+ relatedQueryParameters: new MiddlewareFunction(
338
+ this.app,
339
+ middlewareParams,
340
+ )?.relatedQueryParameters,
341
+ authParams: new MiddlewareFunction(this.app, middlewareParams)
342
+ ?.usedAuthParameters,
343
+ MiddlewareFunction,
344
+ });
345
+ }
411
346
  }
347
+ return middlewaresInfo;
412
348
  }
413
349
 
414
350
  /**
@@ -460,9 +396,9 @@ class AbstractController extends Base {
460
396
  }
461
397
 
462
398
  /**
463
- * Get express path with inheritance of path
399
+ * Get http path with inheritance of path
464
400
  */
465
- getExpressPath() {
401
+ getHttpPath() {
466
402
  return `/${this.getConstructorName().toLowerCase()}`.replace('//', '/');
467
403
  }
468
404
 
@@ -3,12 +3,13 @@ const Base = require('./Base');
3
3
 
4
4
  class AbstractModel extends Base {
5
5
  /**
6
- * @param {import('../Server')} app //TODO change to *.d.ts as this is a Server, not app
6
+ * @param {import('../server')} app //TODO change to *.d.ts as this is a Server, not app
7
7
  * @param function callback optional callback when connection ready
8
8
  */
9
9
  constructor(app, callback = () => {}) {
10
10
  super(app);
11
11
  this.mongooseSchema = mongoose.Schema(this.modelSchema);
12
+ mongoose.set('strictQuery', true);
12
13
  this.mongooseSchema.set('timestamps', true);
13
14
  this.mongooseSchema.set('minimize', false);
14
15
  this.mongooseSchema.loadClass(this.constructor);
package/modules/Base.js CHANGED
@@ -1,5 +1,5 @@
1
- const fs = require('fs').promises;
2
- const { join, normalize } = require('path');
1
+ const fs = require('node:fs').promises;
2
+ const { join, normalize } = require('node:path');
3
3
 
4
4
  class Base {
5
5
  #realLogger = null;
@@ -77,6 +77,7 @@ class Base {
77
77
  if (!IsConstructor(Tr) && Tr.default) {
78
78
  Tr = Tr.default;
79
79
  } else {
80
+ // eslint-disable-next-line no-console
80
81
  console.error(
81
82
  `${log.transport} not a constructor. Please check it`,
82
83
  );
@@ -1,4 +1,5 @@
1
- const path = require('path');
1
+ /* eslint-disable no-console */
2
+ const path = require('node:path');
2
3
  const Base = require('./Base');
3
4
 
4
5
  class Cli extends Base {
@@ -46,9 +47,12 @@ class Cli extends Base {
46
47
  );
47
48
  return false;
48
49
  }
50
+ // TODO wait until https://github.com/nodejs/node/issues/35889
51
+ // const { default: Command } = await import(this.commands[command]);
49
52
 
50
- // eslint-disable-next-line global-require, import/no-dynamic-require
53
+ // eslint-disable-next-line import/no-dynamic-require, global-require
51
54
  const Command = require(this.commands[command]);
55
+
52
56
  const c = new Command(this.app, this.commands, args);
53
57
  let result = false;
54
58