@adaptivestone/framework 5.0.0-beta.5 → 5.0.0-beta.7

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,15 @@
1
+ ### 5.0.0-beta.7
2
+ [UPDATE] update deps
3
+ [UPDATE] change vitest shutdown behavior as mongo driver v6.13 change befaviur that affect us (MongoClient.close now closes any outstanding cursors)
4
+
5
+
6
+
7
+
8
+ ### 5.0.0-beta.5
9
+
10
+ [BREAKING] remove minimist CLI parsing and replace it by commandArguments parser
11
+ [UPDATE] migrated from eslint-plugin-import to eslint-plugin-import-x
12
+
1
13
  ### 5.0.0-beta.5
2
14
 
3
15
  [UPDATE] migrate to eslint 9 and away from aibnb styles (they are abonded)
@@ -466,7 +478,7 @@ await doc.populate([
466
478
  [UPDATE] update deps
467
479
  [UPDATE] winston console transport now using timestapms
468
480
  [UPDATE] PrepareAppInfo middleware now a global one. Do not need to include it on every controller
469
- [NEW] Request anso works with req.query, but req.body have bigger priority
481
+ [NEW] Request also works with req.query, but req.body have bigger priority
470
482
 
471
483
  ### 2.18.0
472
484
 
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
 
@@ -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');
@@ -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
 
package/eslint.config.js CHANGED
@@ -1,11 +1,12 @@
1
1
  import globals from 'globals';
2
2
  import pluginJs from '@eslint/js';
3
- import importPlugin from 'eslint-plugin-import';
3
+ import importPlugin from 'eslint-plugin-import-x';
4
4
  import vitest from '@vitest/eslint-plugin';
5
5
  import eslintConfigPrettier from 'eslint-config-prettier';
6
6
  import prettierPlugin from 'eslint-plugin-prettier/recommended';
7
7
 
8
8
  /** @type {import('eslint').Linter.Config[]} */
9
+ // @ts-ignore
9
10
  export default [
10
11
  pluginJs.configs.recommended,
11
12
  importPlugin.flatConfigs.recommended,
@@ -28,8 +29,31 @@ export default [
28
29
  'class-methods-use-this': 'error',
29
30
  'no-shadow': 'error',
30
31
  'prefer-const': 'error',
31
- 'import/no-extraneous-dependencies': ['error'],
32
- 'import/first': ['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',
33
57
  },
34
58
  },
35
59
  {
package/helpers/yup.js CHANGED
@@ -13,6 +13,7 @@ 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
  }
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
  }
@@ -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');
package/modules/Base.js CHANGED
@@ -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,5 +1,6 @@
1
1
  import path from 'node:path';
2
2
  import * as url from 'node:url';
3
+ import { parseArgs } from 'node:util';
3
4
  import Base from './Base.js';
4
5
 
5
6
  class Cli extends Base {
@@ -45,9 +46,37 @@ class Cli extends Base {
45
46
  ` \x1b[36m${c.padEnd(maxLength)}\x1b[0m - ${commandsClasses[key].default.description}`,
46
47
  );
47
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);
48
77
  }
49
78
 
50
- async run(command, args) {
79
+ async run(command) {
51
80
  await this.loadCommands();
52
81
 
53
82
  if (!command) {
@@ -63,20 +92,54 @@ class Cli extends Base {
63
92
  }
64
93
  const { default: Command } = await import(this.commands[command]);
65
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
+
66
129
  if (Command.isShouldInitModels) {
67
130
  this.logger.debug(
68
131
  `Command ${command} isShouldInitModels called. If you want to skip loading and init models, please set isShouldInitModels to false in tyou command`,
69
132
  );
70
133
  process.env.MONGO_APP_NAME = Command.getMongoConnectionName(
71
134
  command,
72
- args,
135
+ parsedArgs.values,
73
136
  );
74
137
  await this.server.initAllModels();
75
138
  } else {
76
139
  this.logger.debug(`Command ${command} NOT need to isShouldInitModels`);
77
140
  }
78
141
 
79
- const c = new Command(this.app, this.commands, args);
142
+ const c = new Command(this.app, this.commands, parsedArgs.values);
80
143
  let result = false;
81
144
 
82
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.5",
3
+ "version": "5.0.0-beta.7",
4
4
  "description": "Adaptive stone node js framework",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -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",
@@ -51,11 +50,12 @@
51
50
  },
52
51
  "devDependencies": {
53
52
  "@eslint/js": "^9.19.0",
53
+ "@types/node": "^22.13.1",
54
54
  "@vitest/coverage-v8": "^3.0.0",
55
55
  "@vitest/eslint-plugin": "^1.1.25",
56
56
  "eslint": "^9.0.0",
57
57
  "eslint-config-prettier": "^10.0.0",
58
- "eslint-plugin-import": "^2.31.0",
58
+ "eslint-plugin-import-x": "^4.6.1",
59
59
  "eslint-plugin-prettier": "^5.0.0",
60
60
  "globals": "^15.14.0",
61
61
  "husky": "^9.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
 
@@ -45,7 +46,7 @@ declare class Server {
45
46
  /**
46
47
  * Start server (http + init all http ralated functions)
47
48
  */
48
- startServer(callbackBefore404?: Promise<null>): Promise<null>;
49
+ startServer(callbackBefore404?: () => Promise<null>): Promise<null>;
49
50
 
50
51
  /**
51
52
  * Do an initialization (config reading, etc)
@@ -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
@@ -414,7 +414,7 @@ class Server {
414
414
  * @param {String} commandName name of command to load
415
415
  * @param {Object} args list of arguments to pass into command
416
416
  */
417
- async runCliCommand(commandName, args) {
417
+ async runCliCommand(commandName, args = {}) {
418
418
  if (!this.cli) {
419
419
  const { default: BaseCli } = await import('./modules/BaseCli.js'); // Speed optimisation
420
420
  this.cli = new BaseCli(this);
@@ -54,15 +54,15 @@ class HttpServer extends Base {
54
54
  httpConfig.port,
55
55
  httpConfig.hostname,
56
56
  () => {
57
- this.logger.info(
58
- `App started and listening on port ${listener.address().port}`,
59
- );
60
- 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) {
61
61
  // in case we using port 0
62
- this.app.updateConfig('http', { port: listener.address().port });
62
+ this.app.updateConfig('http', { port });
63
63
  this.logger.info(
64
64
  `Updating http config to use new port ${
65
- listener.address().port
65
+ port
66
66
  }. Old was ${httpConfig.port} `,
67
67
  );
68
68
  }
@@ -27,7 +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/no-named-as-default-member
30
+ // eslint-disable-next-line import-x/no-named-as-default-member
31
31
  i18next.use(BackendFS).init({
32
32
  backend: {
33
33
  loadPath: `${this.app.foldersConfig.locales}/{{lng}}/{{ns}}.json`,
@@ -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,
@@ -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
 
@@ -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
  }
@@ -3,7 +3,7 @@ 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) {
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' },
@@ -77,6 +79,7 @@ beforeAll(async () => {
77
79
  await global.server.startServer();
78
80
  });
79
81
 
82
+ // @ts-ignore
80
83
  beforeEach(() => {
81
84
  if (global.server) {
82
85
  const key = `test-${Math.random().toString(36).substring(7)}`;
@@ -86,6 +89,7 @@ beforeEach(() => {
86
89
  }
87
90
  });
88
91
 
92
+ // @ts-ignore
89
93
  afterEach(async () => {
90
94
  if (global.server) {
91
95
  const { url, namespace } = global.server.getConfig('redis');
@@ -101,6 +105,7 @@ afterEach(async () => {
101
105
  }
102
106
  });
103
107
 
108
+ // @ts-ignore
104
109
  afterAll(async () => {
105
110
  if (global.server) {
106
111
  global.server.app.httpServer.shutdown();
@@ -102,6 +102,11 @@ afterAll(async () => {
102
102
  if (typeof global.testSetup.afterAll === 'function') {
103
103
  await global.testSetup.afterAll();
104
104
  }
105
- await mongoose.connection.db.dropDatabase(); // clean database after test
105
+ try {
106
+ await mongoose.connection.db.dropDatabase(); // clean database after test
107
+ } catch {
108
+ // that ok. No mongoose connection
109
+ }
110
+
106
111
  await mongoose.disconnect();
107
112
  });
@@ -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
  },