@adaptivestone/framework 5.0.0-beta.4 → 5.0.0-beta.6

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/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ### 5.0.0-beta.5
2
+
3
+ [BREAKING] remove minimist CLI parsing and replace it by commandArguments parser
4
+ [UPDATE] migrated from eslint-plugin-import to eslint-plugin-import-x
5
+
6
+ ### 5.0.0-beta.5
7
+
8
+ [UPDATE] migrate to eslint 9 and away from aibnb styles (they are abonded)
9
+
1
10
  ### 5.0.0-beta.4
2
11
 
3
12
  [NEW] on shutdown event now after timeout we are forcing to shutdown
@@ -462,7 +471,7 @@ await doc.populate([
462
471
  [UPDATE] update deps
463
472
  [UPDATE] winston console transport now using timestapms
464
473
  [UPDATE] PrepareAppInfo middleware now a global one. Do not need to include it on every controller
465
- [NEW] Request anso works with req.query, but req.body have bigger priority
474
+ [NEW] Request also works with req.query, but req.body have bigger priority
466
475
 
467
476
  ### 2.18.0
468
477
 
package/Cli.js CHANGED
@@ -1,4 +1,3 @@
1
- import parseArgs from 'minimist';
2
1
  import mongoose from 'mongoose';
3
2
  import BaseCli from './modules/BaseCli.js';
4
3
  import Server from './server.js';
@@ -8,14 +7,14 @@ class Cli extends BaseCli {
8
7
  mongoose.set('autoIndex', false); // we do not need create indexes on CLI.
9
8
  const server = new Server(serverConfig);
10
9
  super(server);
11
- this.args = parseArgs(process.argv.slice(3));
12
10
  }
13
11
 
14
12
  async run() {
15
13
  await this.server.init({ isSkipModelInit: true, isSkipModelLoading: true });
16
14
  const command = process.argv[2]?.toLowerCase();
17
- await super.run(command, this.args);
15
+ await super.run(command);
18
16
  this.app.events.emit('shutdown');
17
+ return true;
19
18
  }
20
19
  }
21
20
 
package/cluster.js CHANGED
@@ -4,7 +4,6 @@ import { cpus } from 'node:os';
4
4
  const numCPUs = cpus().length;
5
5
 
6
6
  if (cluster.isPrimary) {
7
- // eslint-disable-next-line no-console
8
7
  console.log(`Master ${process.pid} is running`);
9
8
  // Fork workers.
10
9
  for (let i = 0; i < numCPUs; i += 1) {
@@ -12,7 +11,6 @@ if (cluster.isPrimary) {
12
11
  }
13
12
 
14
13
  cluster.on('exit', (worker, code, signal) => {
15
- // eslint-disable-next-line no-console
16
14
  console.log(
17
15
  `Worker \x1B[45m ${
18
16
  worker.process.pid
@@ -6,6 +6,37 @@ class CreateUser extends AbstractCommand {
6
6
  return 'Create user in a database';
7
7
  }
8
8
 
9
+ /**
10
+ * You able to add command arguments for parsing there.
11
+ * @returns {import("../types/ICommandArguments.js").ICommandArguments}
12
+ */
13
+ static get commandArguments() {
14
+ return {
15
+ id: {
16
+ type: 'string',
17
+ description: 'User id to find user',
18
+ },
19
+ email: {
20
+ type: 'string',
21
+ description: 'User id to find/create user',
22
+ },
23
+ password: {
24
+ type: 'string',
25
+ description: 'New password for user',
26
+ },
27
+ roles: {
28
+ type: 'string',
29
+ description:
30
+ 'User roles comma separated string (--roles=user,admin,someOtherRoles)',
31
+ },
32
+ update: {
33
+ type: 'boolean',
34
+ default: false,
35
+ description: 'Update user if it exists',
36
+ },
37
+ };
38
+ }
39
+
9
40
  async run() {
10
41
  const User = this.app.getModel('User');
11
42
  const { id, email, password, roles, update } = this.args;
@@ -5,12 +5,21 @@ class DropIndex extends AbstractCommand {
5
5
  return 'Drop indexes of model';
6
6
  }
7
7
 
8
- async run() {
9
- if (!this.args.model) {
10
- this.logger.error('Please provide model name as "--model=BestUserModel"');
11
- return false;
12
- }
8
+ /**
9
+ * You able to add command arguments for parsing there.
10
+ * @returns {import("../types/ICommandArguments.js").ICommandArguments}
11
+ */
12
+ static get commandArguments() {
13
+ return {
14
+ model: {
15
+ type: 'string',
16
+ description: 'Model name',
17
+ required: true,
18
+ },
19
+ };
20
+ }
13
21
 
22
+ async run() {
14
23
  const Model = this.app.getModel(this.args.model);
15
24
 
16
25
  if (!Model) {
@@ -8,6 +8,18 @@ class GetOpenApiJson extends AbstractCommand {
8
8
  static get description() {
9
9
  return 'Generate documentation (openApi) ';
10
10
  }
11
+ /**
12
+ * You able to add command arguments for parsing there.
13
+ * @returns {import("../types/ICommandArguments.js").ICommandArguments}
14
+ */
15
+ static get commandArguments() {
16
+ return {
17
+ output: {
18
+ type: 'string',
19
+ description: 'Output file path',
20
+ },
21
+ };
22
+ }
11
23
 
12
24
  async run() {
13
25
  const { myDomain } = this.app.getConfig('http');
@@ -18,7 +30,7 @@ class GetOpenApiJson extends AbstractCommand {
18
30
 
19
31
  try {
20
32
  jsonFile = JSON.parse(await fs.readFile(jsonFile, 'utf8'));
21
- } catch (e) {
33
+ } catch {
22
34
  this.logger.error(
23
35
  'No npm package detected. Please start this command via NPM as it depends on package.json',
24
36
  );
@@ -123,7 +135,6 @@ class GetOpenApiJson extends AbstractCommand {
123
135
 
124
136
  let routeName = Object.keys(route)[0];
125
137
  if (routeName === '/') {
126
- // eslint-disable-next-line no-continue
127
138
  continue;
128
139
  }
129
140
 
@@ -29,6 +29,7 @@ class SyncIndexes extends AbstractCommand {
29
29
  this.logger.info(`Model - ${modelName} NO removed indexes`);
30
30
  }
31
31
  }
32
+ return true;
32
33
  }
33
34
 
34
35
  static get description() {
@@ -7,28 +7,39 @@ class CreateMigration extends AbstractCommand {
7
7
  return 'Create new migration';
8
8
  }
9
9
 
10
+ /**
11
+ * You able to add command arguments for parsing there.
12
+ * @returns {import("../../types/ICommandArguments.js").ICommandArguments}
13
+ */
14
+ static get commandArguments() {
15
+ return {
16
+ name: {
17
+ type: 'string',
18
+ description: 'Migration name',
19
+ required: true,
20
+ },
21
+ };
22
+ }
23
+
10
24
  async run() {
11
- if (!this.args.name) {
12
- return this.logger.error(
13
- 'Please provide migration name with key "--name={someName}"',
14
- );
15
- }
16
25
  if (this.args.name.match(/^\d/)) {
17
- return this.logger.error('Command cant start from nubmer');
26
+ this.logger.error('Command cant start from nubmer');
27
+ return false;
18
28
  }
19
- const fileName = `${Date.now()}_${this.constructor.camelSentence(
29
+ const fileName = `${Date.now()}_${CreateMigration.camelSentence(
20
30
  this.args.name,
21
31
  )}.js`;
22
32
 
23
- const fileContent = this.constructor.getTemplate(
24
- this.constructor.camelSentence(this.args.name),
33
+ const fileContent = CreateMigration.getTemplate(
34
+ CreateMigration.camelSentence(this.args.name),
25
35
  );
26
36
 
27
37
  await fs.writeFile(
28
38
  path.join(this.app.foldersConfig.migrations, fileName),
29
39
  fileContent,
30
40
  );
31
- return this.logger.info(`Migration created ${fileName}`);
41
+ this.logger.info(`Migration created ${fileName}`);
42
+ return true;
32
43
  }
33
44
 
34
45
  static camelSentence(str) {
@@ -49,6 +49,7 @@ class Migrate extends AbstractCommand {
49
49
  this.logger.info(
50
50
  `=== Migration Finished. Migrated ${migrations.length} files ===`,
51
51
  );
52
+ return true;
52
53
  }
53
54
  }
54
55
 
@@ -1,4 +1,4 @@
1
- import yup from 'yup';
1
+ import { object, string } from 'yup';
2
2
  import AbstractController from '../modules/AbstractController.js';
3
3
  import GetUserByToken from '../services/http/middleware/GetUserByToken.js';
4
4
  import RateLimiter from '../services/http/middleware/RateLimiter.js';
@@ -9,58 +9,52 @@ class Auth extends AbstractController {
9
9
  post: {
10
10
  '/login': {
11
11
  handler: this.postLogin,
12
- request: yup.object().shape({
13
- email: yup.string().email().required('auth.emailProvided'), // if not provided then error will be generated
14
- password: yup.string().required('auth.passwordProvided'), // possible to provide values from translation
12
+ request: object().shape({
13
+ email: string().email().required('auth.emailProvided'), // if not provided then error will be generated
14
+ password: string().required('auth.passwordProvided'), // possible to provide values from translation
15
15
  }),
16
16
  },
17
17
  '/register': {
18
18
  handler: this.postRegister,
19
- request: yup.object().shape({
20
- email: yup
21
- .string()
19
+ request: object().shape({
20
+ email: string()
22
21
  .email('auth.emailValid')
23
22
  .required('auth.emailProvided'),
24
- password: yup
25
- .string()
23
+ password: string()
26
24
  .matches(
27
25
  /^[a-zA-Z0-9!@#$%ˆ^&*()_+\-{}[\]<>]+$/,
28
26
  'auth.passwordValid',
29
27
  )
30
28
  .required('auth.passwordProvided'),
31
- nickName: yup
32
- .string()
33
- .matches(/^[a-zA-Z0-9_\-.]+$/, 'auth.nickNameValid'),
34
- firstName: yup.string(),
35
- lastName: yup.string(),
29
+ nickName: string().matches(
30
+ /^[a-zA-Z0-9_\-.]+$/,
31
+ 'auth.nickNameValid',
32
+ ),
33
+ firstName: string(),
34
+ lastName: string(),
36
35
  }),
37
36
  },
38
37
  '/logout': this.postLogout,
39
38
  '/verify': this.verifyUser,
40
39
  '/send-recovery-email': {
41
40
  handler: this.sendPasswordRecoveryEmail,
42
- request: yup
43
- .object()
44
- .shape({ email: yup.string().email().required() }),
41
+ request: object().shape({ email: string().email().required() }),
45
42
  },
46
43
  '/recover-password': {
47
44
  handler: this.recoverPassword,
48
- request: yup.object().shape({
49
- password: yup
50
- .string()
45
+ request: object().shape({
46
+ password: string()
51
47
  .matches(
52
48
  /^[a-zA-Z0-9!@#$%ˆ^&*()_+\-{}[\]<>]+$/,
53
49
  'auth.passwordValid',
54
50
  )
55
51
  .required(),
56
- passwordRecoveryToken: yup.string().required(),
52
+ passwordRecoveryToken: string().required(),
57
53
  }),
58
54
  },
59
55
  '/send-verification': {
60
56
  handler: this.sendVerification,
61
- request: yup
62
- .object()
63
- .shape({ email: yup.string().email().required() }),
57
+ request: object().shape({ email: string().email().required() }),
64
58
  },
65
59
  },
66
60
  };
@@ -136,7 +130,7 @@ class Auth extends AbstractController {
136
130
  user = await User.getUserByVerificationToken(
137
131
  req.query.verification_token,
138
132
  );
139
- } catch (e) {
133
+ } catch {
140
134
  return res.status(400).json({
141
135
  message: req.i18n.t('email.alreadyVerifiedOrWrongToken'),
142
136
  });
@@ -0,0 +1,68 @@
1
+ import globals from 'globals';
2
+ import pluginJs from '@eslint/js';
3
+ import importPlugin from 'eslint-plugin-import-x';
4
+ import vitest from '@vitest/eslint-plugin';
5
+ import eslintConfigPrettier from 'eslint-config-prettier';
6
+ import prettierPlugin from 'eslint-plugin-prettier/recommended';
7
+
8
+ /** @type {import('eslint').Linter.Config[]} */
9
+ // @ts-ignore
10
+ export default [
11
+ pluginJs.configs.recommended,
12
+ importPlugin.flatConfigs.recommended,
13
+ eslintConfigPrettier,
14
+ prettierPlugin,
15
+ {
16
+ languageOptions: {
17
+ sourceType: 'module',
18
+ ecmaVersion: 'latest',
19
+ globals: {
20
+ ...globals.es2023,
21
+ ...globals.node,
22
+ },
23
+ },
24
+ },
25
+ {
26
+ rules: {
27
+ 'no-await-in-loop': 'error',
28
+ 'no-param-reassign': 'error',
29
+ 'class-methods-use-this': 'error',
30
+ 'no-shadow': 'error',
31
+ 'prefer-const': 'error',
32
+ 'import-x/no-extraneous-dependencies': ['error'],
33
+ 'import-x/first': ['error'],
34
+ camelcase: ['error', { properties: 'never', ignoreDestructuring: false }],
35
+ 'prefer-destructuring': [
36
+ 'error',
37
+ {
38
+ VariableDeclarator: {
39
+ array: false,
40
+ object: true,
41
+ },
42
+ AssignmentExpression: {
43
+ array: true,
44
+ object: false,
45
+ },
46
+ },
47
+ {
48
+ enforceForRenamedProperties: false,
49
+ },
50
+ ],
51
+ 'no-plusplus': 'error',
52
+ 'consistent-return': 'error',
53
+ 'no-return-await': 'error',
54
+ 'arrow-body-style': 'error',
55
+ 'dot-notation': 'error',
56
+ curly: 'error',
57
+ },
58
+ },
59
+ {
60
+ files: ['**/*.test.js'],
61
+ plugins: {
62
+ vitest,
63
+ },
64
+ rules: {
65
+ ...vitest.configs.recommended.rules,
66
+ },
67
+ },
68
+ ];
package/helpers/files.js CHANGED
@@ -73,7 +73,4 @@ const getFilesPathWithInheritance = async ({
73
73
  return filesToLoad;
74
74
  };
75
75
 
76
- export {
77
- // eslint-disable-next-line import/prefer-default-export
78
- getFilesPathWithInheritance,
79
- };
76
+ export { getFilesPathWithInheritance };
package/helpers/logger.js CHANGED
@@ -1,4 +1,3 @@
1
- /* eslint-disable no-console */
2
1
  const levels = ['error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly'];
3
2
 
4
3
  const consoleLogger = (level, message) => {
package/helpers/yup.js CHANGED
@@ -13,12 +13,10 @@ class YupFile extends Schema {
13
13
  constructor() {
14
14
  super({
15
15
  type: 'file',
16
+ // @ts-ignore
16
17
  check: (value) => value.every((item) => item instanceof PersistentFile),
17
18
  });
18
19
  }
19
20
  }
20
21
 
21
- export {
22
- // eslint-disable-next-line import/prefer-default-export
23
- YupFile,
24
- };
22
+ export { YupFile };
package/jsconfig.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "module": "node16",
3
+ "module": "NodeNext",
4
4
  "target": "ES2022",
5
- "moduleResolution": "node16",
5
+ "moduleResolution": "nodenext",
6
6
  "checkJs": true
7
7
  },
8
- "exclude": ["node_modules"]
8
+ "exclude": ["node_modules", "coverage"]
9
9
  }
package/models/User.js CHANGED
@@ -1,5 +1,3 @@
1
- /* eslint-disable no-param-reassign */
2
-
3
1
  import { scrypt } from 'node:crypto';
4
2
 
5
3
  import { promisify } from 'node:util';
@@ -26,9 +26,18 @@ class AbstractCommand extends Base {
26
26
  return `CLI: ${commandName} ${JSON.stringify(args)}`;
27
27
  }
28
28
 
29
+ /**
30
+ * You able to add command arguments for parsing there.
31
+ * @see https://nodejs.org/api/util.html#utilparseargsconfig in config.options plus extended with description and required
32
+ * @returns {import("../types/ICommandArguments.js").ICommandArguments}
33
+ */
34
+ static get commandArguments() {
35
+ return {};
36
+ }
37
+
29
38
  /**
30
39
  * Entry point to every command. This method should be overridden
31
- * @return {Promise<boolean>} resut
40
+ * @return {Promise<boolean>} result
32
41
  */
33
42
  async run() {
34
43
  this.logger.error('You should implement run method');
@@ -1,5 +1,3 @@
1
- /* eslint-disable no-restricted-syntax */
2
- /* eslint-disable guard-for-in */
3
1
  import express from 'express';
4
2
 
5
3
  import Base from './Base.js';
@@ -88,7 +86,6 @@ class AbstractController extends Base {
88
86
  this.logger.error(
89
87
  `Method ${verb} not exist for router. Please check your codebase`,
90
88
  );
91
- // eslint-disable-next-line no-continue
92
89
  continue;
93
90
  }
94
91
  for (const path in routes[verb]) {
@@ -112,7 +109,6 @@ class AbstractController extends Base {
112
109
  routeObject.handler
113
110
  }' for controller '${this.getConstructorName()}'`,
114
111
  );
115
- // eslint-disable-next-line no-continue
116
112
  continue;
117
113
  }
118
114
  }
@@ -300,14 +296,12 @@ class AbstractController extends Base {
300
296
  let realPath = path;
301
297
  if (typeof realPath !== 'string') {
302
298
  this.logger.error(`Path not a string ${realPath}. Please check it`);
303
- // eslint-disable-next-line no-continue
304
299
  continue;
305
300
  }
306
301
  if (!realPath.startsWith('/')) {
307
302
  method = realPath.split('/')[0]?.toLowerCase();
308
303
  if (!method) {
309
304
  this.logger.error(`Method not found for ${realPath}`);
310
- // eslint-disable-next-line no-continue
311
305
  continue;
312
306
  }
313
307
  realPath = realPath.substring(method.length);
@@ -316,7 +310,6 @@ class AbstractController extends Base {
316
310
  this.logger.error(
317
311
  `Method ${method} not exist for middleware. Please check your codebase`,
318
312
  );
319
- // eslint-disable-next-line no-continue
320
313
  continue;
321
314
  }
322
315
  const fullPath = `/${httpPath}/${realPath.toUpperCase()}`
package/modules/Base.js CHANGED
@@ -21,7 +21,7 @@ class Base {
21
21
  let l;
22
22
  try {
23
23
  l = this.#realLogger;
24
- } catch (e) {
24
+ } catch {
25
25
  console.warn(
26
26
  `You try to accees logger not from class. that can be ok in case of models.`,
27
27
  );
@@ -29,8 +29,9 @@ class Base {
29
29
  }
30
30
 
31
31
  if (!l) {
32
+ const { loggerGroup } = /** @type {typeof Base} */ (this.constructor);
32
33
  this.#realLogger = this.getLogger(
33
- this.constructor.loggerGroup + this.getConstructorName(),
34
+ loggerGroup + this.getConstructorName(),
34
35
  );
35
36
  }
36
37
  return this.#realLogger;
@@ -1,6 +1,6 @@
1
- /* eslint-disable no-console */
2
1
  import path from 'node:path';
3
2
  import * as url from 'node:url';
3
+ import { parseArgs } from 'node:util';
4
4
  import Base from './Base.js';
5
5
 
6
6
  class Cli extends Base {
@@ -35,7 +35,6 @@ class Cli extends Base {
35
35
  console.log('Available commands:');
36
36
  let commandsClasses = [];
37
37
  for (const c of commands) {
38
- // eslint-disable-next-line no-await-in-loop
39
38
  commandsClasses.push(import(this.commands[c]));
40
39
  // console.log(
41
40
  // ` \x1b[36m${c.padEnd(maxLength)}\x1b[0m - ${f.default.description}`,
@@ -43,14 +42,41 @@ class Cli extends Base {
43
42
  }
44
43
  commandsClasses = await Promise.all(commandsClasses);
45
44
  for (const [key, c] of Object.entries(commands)) {
46
- // eslint-disable-next-line no-await-in-loop
47
45
  console.log(
48
46
  ` \x1b[36m${c.padEnd(maxLength)}\x1b[0m - ${commandsClasses[key].default.description}`,
49
47
  );
50
48
  }
49
+ console.log(
50
+ '\nUsage (use one of option): \n node cli.js <command> [options] \n npm run cli <command> -- [options]',
51
+ );
52
+ }
53
+
54
+ static showHelp(Command, finalArguments) {
55
+ console.log(`\n\x1b[32m${Command.description}\x1b[0m`);
56
+ let output = '';
57
+
58
+ Object.entries(finalArguments).forEach(([key, opt]) => {
59
+ const outputLocal = [];
60
+ outputLocal.push(`\n\x1b[36m --${key} \x1b[0m`);
61
+ if (opt.type !== 'boolean') {
62
+ outputLocal.push(`<${opt.type}>`);
63
+ // flag += `<${opt.type}>`;
64
+ }
65
+ if (opt.required) {
66
+ outputLocal.push('(required)');
67
+ }
68
+ outputLocal.push(`\n \x1b[2m${opt.description}`);
69
+ if (opt.default !== undefined) {
70
+ outputLocal.push(` (default: ${opt.default})`);
71
+ }
72
+ outputLocal.push('\x1b[0m');
73
+ output += outputLocal.join(' ');
74
+ });
75
+
76
+ console.log(output);
51
77
  }
52
78
 
53
- async run(command, args) {
79
+ async run(command) {
54
80
  await this.loadCommands();
55
81
 
56
82
  if (!command) {
@@ -66,20 +92,54 @@ class Cli extends Base {
66
92
  }
67
93
  const { default: Command } = await import(this.commands[command]);
68
94
 
95
+ const defaultArgs = {
96
+ help: {
97
+ type: 'boolean',
98
+ description: 'Show help',
99
+ },
100
+ };
101
+
102
+ const finalArguments = {
103
+ ...Command.commandArguments,
104
+ ...defaultArgs,
105
+ };
106
+
107
+ const parsedArgs = parseArgs({
108
+ args: process.argv.slice(3), // remove command name
109
+ options: finalArguments,
110
+ tokens: true,
111
+ });
112
+
113
+ // @ts-ignore
114
+ if (parsedArgs.values.help) {
115
+ Cli.showHelp(Command, finalArguments);
116
+ return true;
117
+ }
118
+
119
+ for (const [key, opt] of Object.entries(finalArguments)) {
120
+ if (opt.required && !parsedArgs.values[key]) {
121
+ console.log(
122
+ `\x1b[31mRequired field not proivded. Please provide "${key}" argument\x1b[0m`,
123
+ );
124
+ Cli.showHelp(Command, finalArguments);
125
+ return false;
126
+ }
127
+ }
128
+
69
129
  if (Command.isShouldInitModels) {
70
130
  this.logger.debug(
71
131
  `Command ${command} isShouldInitModels called. If you want to skip loading and init models, please set isShouldInitModels to false in tyou command`,
72
132
  );
73
133
  process.env.MONGO_APP_NAME = Command.getMongoConnectionName(
74
134
  command,
75
- args,
135
+ parsedArgs.values,
76
136
  );
77
137
  await this.server.initAllModels();
78
138
  } else {
79
139
  this.logger.debug(`Command ${command} NOT need to isShouldInitModels`);
80
140
  }
81
141
 
82
- const c = new Command(this.app, this.commands, args);
142
+ const c = new Command(this.app, this.commands, parsedArgs.values);
83
143
  let result = false;
84
144
 
85
145
  result = await c.run().catch((e) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adaptivestone/framework",
3
- "version": "5.0.0-beta.4",
3
+ "version": "5.0.0-beta.6",
4
4
  "description": "Adaptive stone node js framework",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -18,8 +18,8 @@
18
18
  "test": "vitest run",
19
19
  "t": "vitest --coverage=false --reporter=default",
20
20
  "prettier": "prettier --check '**/*.(js|jsx|ts|tsx|json|css|scss|md)'",
21
- "lint": "eslint '**/*.js'",
22
- "lint:fix": "eslint '**/*.js' --fix",
21
+ "lint": "eslint",
22
+ "lint:fix": "eslint --fix",
23
23
  "codestyle": "npm run prettier && npm run lint",
24
24
  "prepare": "husky",
25
25
  "cli": "node cliCommand",
@@ -37,7 +37,6 @@
37
37
  "i18next": "^24.0.0",
38
38
  "i18next-fs-backend": "^2.0.0",
39
39
  "juice": "^11.0.0",
40
- "minimist": "^1.2.5",
41
40
  "mongoose": "^8.0.0",
42
41
  "nodemailer": "^6.6.3",
43
42
  "nodemailer-sendmail-transport": "^1.0.2",
@@ -50,12 +49,15 @@
50
49
  "yup": "^1.0.0"
51
50
  },
52
51
  "devDependencies": {
52
+ "@eslint/js": "^9.19.0",
53
+ "@types/node": "^22.13.1",
53
54
  "@vitest/coverage-v8": "^3.0.0",
54
- "eslint": "^8.0.0",
55
- "eslint-config-airbnb-base": "^15.0.0",
55
+ "@vitest/eslint-plugin": "^1.1.25",
56
+ "eslint": "^9.0.0",
56
57
  "eslint-config-prettier": "^10.0.0",
58
+ "eslint-plugin-import-x": "^4.6.1",
57
59
  "eslint-plugin-prettier": "^5.0.0",
58
- "eslint-plugin-vitest": "^0.4.0",
60
+ "globals": "^15.14.0",
59
61
  "husky": "^9.0.0",
60
62
  "lint-staged": "^15.0.0",
61
63
  "mongodb-memory-server": "^10.0.0",
package/server.d.ts CHANGED
@@ -10,6 +10,7 @@ import Cache from './services/cache/Cache';
10
10
  import winston from 'winston';
11
11
 
12
12
  import HttpServer from './services/http/HttpServer.js';
13
+ import ControllerManager from './services/http/ControllerManager.js';
13
14
 
14
15
  type ServerConfig = {
15
16
  folders: ExpandDeep<TFolderConfig>;
@@ -27,7 +28,7 @@ declare class Server {
27
28
  get cache(): Server['cacheService'];
28
29
  get logger(): winston.Logger;
29
30
  httpServer: HttpServer | null;
30
- controllerManager: null;
31
+ controllerManager: ControllerManager | null;
31
32
  };
32
33
  cacheService: Cache;
33
34
 
@@ -92,7 +93,7 @@ declare class Server {
92
93
  /**
93
94
  * Run cli command into framework (http, ws, etc)
94
95
  */
95
- runCliCommand(commandName: string, args: {}): Promise<BaseCli['run']>;
96
+ runCliCommand(commandName: string, args?: {}): Promise<BaseCli['run']>;
96
97
  }
97
98
 
98
99
  export default Server;
package/server.js CHANGED
@@ -1,4 +1,3 @@
1
- /* eslint-disable no-console */
2
1
  import EventEmitter from 'node:events';
3
2
  import { hrtime, loadEnvFile } from 'node:process';
4
3
  import * as url from 'node:url';
@@ -12,7 +11,7 @@ import Cache from './services/cache/Cache.js';
12
11
 
13
12
  try {
14
13
  loadEnvFile();
15
- } catch (e) {
14
+ } catch {
16
15
  console.warn('No env file found. This is ok. But please check youself.');
17
16
  }
18
17
 
@@ -323,7 +322,7 @@ class Server {
323
322
  function IsConstructor(f) {
324
323
  try {
325
324
  Reflect.construct(String, [], f);
326
- } catch (e) {
325
+ } catch {
327
326
  return false;
328
327
  }
329
328
  return true;
@@ -415,7 +414,7 @@ class Server {
415
414
  * @param {String} commandName name of command to load
416
415
  * @param {Object} args list of arguments to pass into command
417
416
  */
418
- async runCliCommand(commandName, args) {
417
+ async runCliCommand(commandName, args = {}) {
419
418
  if (!this.cli) {
420
419
  const { default: BaseCli } = await import('./modules/BaseCli.js'); // Speed optimisation
421
420
  this.cli = new BaseCli(this);
@@ -94,7 +94,7 @@ class Cache extends Base {
94
94
  }
95
95
  return value;
96
96
  });
97
- } catch (e) {
97
+ } catch {
98
98
  this.logger.warn(
99
99
  'Not able to parse json from redis cache. That can be a normal in case you store string here',
100
100
  );
@@ -43,7 +43,6 @@ class HttpServer extends Base {
43
43
  // eslint-disable-next-line no-unused-vars
44
44
  this.express.use((err, req, res, next) => {
45
45
  // error handling
46
- // eslint-disable-next-line no-console
47
46
  console.error(err.stack);
48
47
  // TODO
49
48
  res.status(500).json({ message: 'Something broke!' });
@@ -55,15 +54,15 @@ class HttpServer extends Base {
55
54
  httpConfig.port,
56
55
  httpConfig.hostname,
57
56
  () => {
58
- this.logger.info(
59
- `App started and listening on port ${listener.address().port}`,
60
- );
61
- if (+listener.address().port !== +httpConfig.port) {
57
+ const address = listener.address();
58
+ const port = typeof address === 'string' ? 0 : address.port;
59
+ this.logger.info(`App started and listening on port ${port}`);
60
+ if (+port !== +httpConfig.port) {
62
61
  // in case we using port 0
63
- this.app.updateConfig('http', { port: listener.address().port });
62
+ this.app.updateConfig('http', { port });
64
63
  this.logger.info(
65
64
  `Updating http config to use new port ${
66
- listener.address().port
65
+ port
67
66
  }. Old was ${httpConfig.port} `,
68
67
  );
69
68
  }
@@ -1,4 +1,4 @@
1
- import yup from 'yup';
1
+ import { object } from 'yup';
2
2
  import Base from '../../../modules/Base.js';
3
3
 
4
4
  class AbstractMiddleware extends Base {
@@ -18,13 +18,13 @@ class AbstractMiddleware extends Base {
18
18
  // eslint-disable-next-line class-methods-use-this
19
19
  get relatedQueryParameters() {
20
20
  // For example yup.object().shape({page: yup.number().required(),limit: yup.number()})
21
- return yup.object().shape({});
21
+ return object().shape({});
22
22
  }
23
23
 
24
24
  // eslint-disable-next-line class-methods-use-this
25
25
  get relatedRequestParameters() {
26
26
  // For example yup.object().shape({page: yup.number().required(),limit: yup.number()})
27
- return yup.object().shape({});
27
+ return object().shape({});
28
28
  }
29
29
 
30
30
  get relatedReqParameters() {
@@ -27,6 +27,7 @@ class I18n extends AbstractMiddleware {
27
27
  if (I18NConfig.enabled) {
28
28
  this.logger.info('Enabling i18n support');
29
29
  this.i18n = i18next;
30
+ // eslint-disable-next-line import-x/no-named-as-default-member
30
31
  i18next.use(BackendFS).init({
31
32
  backend: {
32
33
  loadPath: `${this.app.foldersConfig.locales}/{{lng}}/{{ns}}.json`,
@@ -98,7 +99,6 @@ class I18n extends AbstractMiddleware {
98
99
  for (const detectorName of this.detectorOrder) {
99
100
  const lng = this.detectors[detectorName](req);
100
101
  if (!lng) {
101
- // eslint-disable-next-line no-continue
102
102
  continue;
103
103
  }
104
104
  if (i18next.services.languageUtils.isSupportedCode(lng)) {
@@ -1,4 +1,4 @@
1
- import yup from 'yup';
1
+ import { object, number } from 'yup';
2
2
  import AbstractMiddleware from './AbstractMiddleware.js';
3
3
  /**
4
4
  * Middleware for reusing pagination
@@ -10,9 +10,9 @@ class Pagination extends AbstractMiddleware {
10
10
 
11
11
  // eslint-disable-next-line class-methods-use-this
12
12
  get relatedQueryParameters() {
13
- return yup.object().shape({
14
- page: yup.number(),
15
- limit: yup.number(),
13
+ return object().shape({
14
+ page: number(),
15
+ limit: number(),
16
16
  });
17
17
  }
18
18
 
@@ -123,6 +123,7 @@ class Mail extends Base {
123
123
  this.#renderTemplateFile(templates.style),
124
124
  ]);
125
125
 
126
+ // @ts-ignore
126
127
  juice.tableElements = ['TABLE'];
127
128
 
128
129
  const juiceResourcesAsync = promisify(juice.juiceResources);
@@ -139,7 +140,6 @@ class Mail extends Base {
139
140
  inlinedHTML,
140
141
  };
141
142
  }
142
-
143
143
  /**
144
144
  * Send email
145
145
  * @param {string} to email send to
@@ -150,7 +150,7 @@ class Mail extends Base {
150
150
  async send(to, from = null, aditionalNodemailerOptions = {}) {
151
151
  const { subject, text, inlinedHTML } = await this.renderTemplate();
152
152
 
153
- return this.constructor.sendRaw(
153
+ return Mail.sendRaw(
154
154
  this.app,
155
155
  to,
156
156
  subject,
@@ -1,4 +1,4 @@
1
- import yup from 'yup';
1
+ import { isSchema } from 'yup';
2
2
  import YupValidator from './drivers/YupValidator.js';
3
3
  import CustomValidator from './drivers/CustomValidator.js';
4
4
  import Base from '../../modules/Base.js';
@@ -7,7 +7,7 @@ class ValidateService extends Base {
7
7
  constructor(app, validator) {
8
8
  super(app);
9
9
  this.validator = validator
10
- ? this.constructor.getDriverByValidatorBody(app, validator)
10
+ ? ValidateService.getDriverByValidatorBody(app, validator)
11
11
  : null;
12
12
  }
13
13
 
@@ -30,7 +30,7 @@ class ValidateService extends Base {
30
30
  if (this.isValidatorExists(body)) {
31
31
  return body;
32
32
  }
33
- if (yup.isSchema(body)) {
33
+ if (isSchema(body)) {
34
34
  const yupValidator = new YupValidator(app, body);
35
35
  return yupValidator;
36
36
  }
@@ -104,7 +104,7 @@ class ValidateService extends Base {
104
104
  const result = [];
105
105
 
106
106
  for (const validator of validators) {
107
- const formatedValidator = this.constructor.getDriverByValidatorBody(
107
+ const formatedValidator = ValidateService.getDriverByValidatorBody(
108
108
  this.app,
109
109
  validator,
110
110
  );
@@ -18,8 +18,8 @@ class AbstractValidator extends Base {
18
18
  return {};
19
19
  }
20
20
 
21
- // eslint-disable-next-line class-methods-use-this
22
- async validateFields() {
21
+ // eslint-disable-next-line class-methods-use-this, no-unused-vars
22
+ async validateFields(data, { query, body, appInfo }) {
23
23
  // IMPLENT;
24
24
  return true;
25
25
  }
@@ -1,4 +1,4 @@
1
- import yup from 'yup';
1
+ import { ValidationError } from 'yup';
2
2
  import AbstractValidator from './AbstractValidator.js';
3
3
 
4
4
  class CustomValidator extends AbstractValidator {
@@ -19,7 +19,7 @@ class CustomValidator extends AbstractValidator {
19
19
  } catch (e) {
20
20
  this.logger.warn(`CustomValidator validateFields ${e}`);
21
21
  if (e.path) {
22
- throw new yup.ValidationError({
22
+ throw new ValidationError({
23
23
  [e.path]: e.message,
24
24
  });
25
25
  }
@@ -1,9 +1,9 @@
1
- import yup from 'yup';
1
+ import { ValidationError } from 'yup';
2
2
  import AbstractValidator from './AbstractValidator.js';
3
3
 
4
4
  class YupValidator extends AbstractValidator {
5
5
  get fieldsInJsonFormat() {
6
- return this.constructor.convertFieldsToJson(this.body);
6
+ return YupValidator.convertFieldsToJson(this.body);
7
7
  }
8
8
 
9
9
  static convertFieldsToJson(fields) {
@@ -76,7 +76,7 @@ class YupValidator extends AbstractValidator {
76
76
  });
77
77
  }
78
78
 
79
- throw new yup.ValidationError({
79
+ throw new ValidationError({
80
80
  ...errorAnswer,
81
81
  });
82
82
  }
package/tests/setup.js CHANGED
@@ -12,7 +12,9 @@ mongoose.set('autoIndex', false);
12
12
 
13
13
  let mongoMemoryServerInstance;
14
14
 
15
+ // @ts-ignore
15
16
  jest.setTimeout(1000000);
17
+ // @ts-ignore
16
18
  beforeAll(async () => {
17
19
  mongoMemoryServerInstance = await MongoMemoryReplSet.create({
18
20
  // binary: { version: '4.4.6' },
@@ -64,9 +66,7 @@ beforeAll(async () => {
64
66
  nick: 'testUserNickName',
65
67
  },
66
68
  }).catch((e) => {
67
- // eslint-disable-next-line no-console
68
69
  console.error(e);
69
- // eslint-disable-next-line no-console
70
70
  console.info(
71
71
  'That error can happens in case you have custom user model. Please use global.testSetup.disableUserCreate flag to skip user creating',
72
72
  );
@@ -79,6 +79,7 @@ beforeAll(async () => {
79
79
  await global.server.startServer();
80
80
  });
81
81
 
82
+ // @ts-ignore
82
83
  beforeEach(() => {
83
84
  if (global.server) {
84
85
  const key = `test-${Math.random().toString(36).substring(7)}`;
@@ -88,6 +89,7 @@ beforeEach(() => {
88
89
  }
89
90
  });
90
91
 
92
+ // @ts-ignore
91
93
  afterEach(async () => {
92
94
  if (global.server) {
93
95
  const { url, namespace } = global.server.getConfig('redis');
@@ -97,12 +99,13 @@ afterEach(async () => {
97
99
  await redisClient.connect();
98
100
  await clearRedisNamespace(redisClient, namespace);
99
101
  await redisClient.disconnect();
100
- } catch (err) {
102
+ } catch {
101
103
  // that ok. No redis connection
102
104
  }
103
105
  }
104
106
  });
105
107
 
108
+ // @ts-ignore
106
109
  afterAll(async () => {
107
110
  if (global.server) {
108
111
  global.server.app.httpServer.shutdown();
@@ -57,9 +57,7 @@ beforeAll(async () => {
57
57
  nick: 'testUserNickName',
58
58
  },
59
59
  }).catch((e) => {
60
- // eslint-disable-next-line no-console
61
60
  console.error(e);
62
- // eslint-disable-next-line no-console
63
61
  console.info(
64
62
  'That error can happens in case you have custom user model. Please use global.testSetup.disableUserCreate flag to skip user creating',
65
63
  );
@@ -90,7 +88,7 @@ afterEach(async () => {
90
88
  await redisClient.connect();
91
89
  await clearRedisNamespace(redisClient, namespace);
92
90
  await redisClient.disconnect();
93
- } catch (err) {
91
+ } catch {
94
92
  // that ok. No redis connection
95
93
  }
96
94
  }
@@ -0,0 +1,41 @@
1
+ // this is from nodejs types
2
+ interface ParseArgsOptionConfig {
3
+ /**
4
+ * Type of argument.
5
+ */
6
+ type: 'string' | 'boolean';
7
+ /**
8
+ * Whether this option can be provided multiple times.
9
+ * If `true`, all values will be collected in an array.
10
+ * If `false`, values for the option are last-wins.
11
+ * @default false.
12
+ */
13
+ multiple?: boolean | undefined;
14
+ /**
15
+ * A single character alias for the option.
16
+ */
17
+ short?: string | undefined;
18
+ /**
19
+ * The default option value when it is not set by args.
20
+ * It must be of the same type as the the `type` property.
21
+ * When `multiple` is `true`, it must be an array.
22
+ * @since v18.11.0
23
+ */
24
+ default?: string | boolean | string[] | boolean[] | undefined;
25
+ }
26
+
27
+ interface ParseArgsOptionsConfigExtended extends ParseArgsOptionConfig {
28
+ /**
29
+ * A description of the option.
30
+ */
31
+ description?: string;
32
+
33
+ /**
34
+ * Is it required?
35
+ */
36
+ required?: boolean;
37
+ }
38
+
39
+ export interface ICommandArguments {
40
+ [longOption: string]: ParseArgsOptionsConfigExtended;
41
+ }
package/vitest.config.js CHANGED
@@ -1,4 +1,4 @@
1
- // eslint-disable-next-line import/no-unresolved
1
+ // eslint-disable-next-line import-x/no-unresolved
2
2
  import { defineConfig } from 'vitest/config';
3
3
 
4
4
  export default defineConfig({
@@ -9,6 +9,7 @@ export default defineConfig({
9
9
  outputFile: './coverage/rspec.xml',
10
10
  reporters: ['default', 'junit'],
11
11
  coverage: {
12
+ provider: 'v8',
12
13
  enabled: true,
13
14
  reporter: ['text', 'html', 'clover', 'json', 'cobertura'],
14
15
  },