@tronsfey/openapi2cli 1.0.10

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 (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +173 -0
  3. package/README.zh.md +173 -0
  4. package/bin/openapi2cli +2 -0
  5. package/dist/analyzer/schema-analyzer.d.ts +4 -0
  6. package/dist/analyzer/schema-analyzer.d.ts.map +1 -0
  7. package/dist/analyzer/schema-analyzer.js +329 -0
  8. package/dist/analyzer/schema-analyzer.js.map +1 -0
  9. package/dist/auth/auth-provider.d.ts +22 -0
  10. package/dist/auth/auth-provider.d.ts.map +1 -0
  11. package/dist/auth/auth-provider.js +100 -0
  12. package/dist/auth/auth-provider.js.map +1 -0
  13. package/dist/generator/command-generator.d.ts +3 -0
  14. package/dist/generator/command-generator.d.ts.map +1 -0
  15. package/dist/generator/command-generator.js +96 -0
  16. package/dist/generator/command-generator.js.map +1 -0
  17. package/dist/generator/template-engine.d.ts +2 -0
  18. package/dist/generator/template-engine.d.ts.map +1 -0
  19. package/dist/generator/template-engine.js +154 -0
  20. package/dist/generator/template-engine.js.map +1 -0
  21. package/dist/index.d.ts +3 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +135 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/parser/oas-parser.d.ts +3 -0
  26. package/dist/parser/oas-parser.d.ts.map +1 -0
  27. package/dist/parser/oas-parser.js +64 -0
  28. package/dist/parser/oas-parser.js.map +1 -0
  29. package/dist/templates/README.md.hbs +197 -0
  30. package/dist/templates/README.zh.md.hbs +197 -0
  31. package/dist/templates/SKILL.md.hbs +134 -0
  32. package/dist/templates/api-client.ts.hbs +217 -0
  33. package/dist/templates/command.ts.hbs +130 -0
  34. package/dist/templates/flat-commands.ts.hbs +126 -0
  35. package/dist/templates/index.ts.hbs +38 -0
  36. package/dist/templates/package.json.hbs +31 -0
  37. package/dist/templates/tsconfig.json.hbs +16 -0
  38. package/dist/types/index.d.ts +104 -0
  39. package/dist/types/index.d.ts.map +1 -0
  40. package/dist/types/index.js +3 -0
  41. package/dist/types/index.js.map +1 -0
  42. package/package.json +88 -0
@@ -0,0 +1,126 @@
1
+ import { Command, Option } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { createApiClient } from './lib/api-client';
4
+
5
+ export function registerFlatCommands(program: Command): void {
6
+ {{#each flatCommands}}
7
+ program
8
+ .command({{jsString name}})
9
+ .description({{jsString description}})
10
+ {{#each aliases}}
11
+ .alias({{jsString this}})
12
+ {{/each}}
13
+ {{#each options}}
14
+ {{#if enum}}
15
+ .addOption(new Option('{{optionFlag name required type}}', {{jsString description}}){{#if defaultValue}}.default({{jsString defaultValue}}){{/if}}.choices([{{#each enum}}{{jsString this}}{{#unless @last}}, {{/unless}}{{/each}}]))
16
+ {{else}}
17
+ {{#eq type "boolean"}}
18
+ .option('{{optionFlag name required type}}', {{jsString description}})
19
+ {{else}}
20
+ .option('{{optionFlag name required type}}', {{jsString description}}{{#if defaultValue}}, {{jsString defaultValue}}{{/if}})
21
+ {{/eq}}
22
+ {{/if}}
23
+ {{/each}}
24
+ {{#if (subcommandHelpText this)}}
25
+ .addHelpText('after', {{subcommandHelpText this}})
26
+ {{/if}}
27
+ .action(async (opts: Record<string, unknown>, cmd: Command) => {
28
+ const allOpts = cmd.optsWithGlobals<Record<string, unknown>>();
29
+ const endpoint = (allOpts['endpoint'] as string | undefined) ?? {{jsString ../structure.baseUrl}};
30
+ const format = (allOpts['format'] as string | undefined) ?? 'json';
31
+ const verbose = Boolean(allOpts['verbose']);
32
+ const allPages = Boolean(allOpts['allPages']);
33
+ const query = allOpts['query'] as string | undefined;
34
+ const client = createApiClient(endpoint);
35
+
36
+ // Separate body keys from path/query params
37
+ const bodyKeys = new Set<string>([{{#if requestBody}}{{#each requestBody.fields}}'{{optKey}}', {{/each}}'data'{{/if}}]);
38
+ const params: Record<string, string | undefined> = {};
39
+ for (const [k, v] of Object.entries(opts)) {
40
+ if (!bodyKeys.has(k)) params[k] = v as string | undefined;
41
+ }
42
+
43
+ {{#if requestBody}}
44
+ // Assemble request body
45
+ let body: unknown;
46
+ const rawData = opts['data'] as string | undefined;
47
+ if (rawData) {
48
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
49
+ const { readFileSync } = require('fs') as typeof import('fs');
50
+ body = rawData.startsWith('@')
51
+ ? JSON.parse(readFileSync(rawData.slice(1), 'utf-8'))
52
+ : JSON.parse(rawData);
53
+ {{#if (hasItems requestBody.fields)}}
54
+ } else {
55
+ const bodyObj: Record<string, unknown> = {};
56
+ {{#each requestBody.fields}}if (opts['{{optKey}}'] !== undefined) bodyObj[{{jsString name}}] = opts['{{optKey}}'];
57
+ {{/each}}if (Object.keys(bodyObj).length) body = bodyObj;
58
+ {{/if}}
59
+ }
60
+ {{/if}}
61
+
62
+ try {
63
+ {{#eq streaming "sse"}}
64
+ // Server-Sent Events — stream events to stdout, one compact JSON line each
65
+ for await (const eventData of client.requestStream({
66
+ method: '{{method}}',
67
+ path: '{{path}}',
68
+ params,
69
+ verbose,
70
+ })) {
71
+ try {
72
+ let output: unknown = JSON.parse(eventData);
73
+ if (query) {
74
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
75
+ const jmespath = require('jmespath') as { search: (d: unknown, e: string) => unknown };
76
+ output = jmespath.search(output, query);
77
+ }
78
+ process.stdout.write(JSON.stringify(output) + '\n');
79
+ } catch {
80
+ process.stdout.write(eventData + '\n');
81
+ }
82
+ }
83
+ {{else}}
84
+ const result = await client.request({
85
+ method: '{{method}}',
86
+ path: '{{path}}',
87
+ params,
88
+ {{#if requestBody}}body,{{/if}}
89
+ verbose,
90
+ allPages,
91
+ });
92
+ formatOutput(result, format, query);
93
+ {{/eq}}
94
+ } catch (err: unknown) {
95
+ const message = err instanceof Error ? err.message : String(err);
96
+ console.error(chalk.red('Error:'), message);
97
+ process.exit(1);
98
+ }
99
+ });
100
+
101
+ {{/each}}
102
+ }
103
+
104
+ function formatOutput(data: unknown, format: string, query?: string): void {
105
+ let output = data;
106
+ if (query) {
107
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
108
+ const jmespath = require('jmespath') as { search: (d: unknown, e: string) => unknown };
109
+ output = jmespath.search(data, query);
110
+ }
111
+ if (format === 'json') {
112
+ console.log(JSON.stringify(output, null, 2));
113
+ } else if (format === 'yaml') {
114
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
115
+ const yaml = require('yaml') as { stringify: (d: unknown) => string };
116
+ process.stdout.write(yaml.stringify(output));
117
+ } else if (format === 'table') {
118
+ if (Array.isArray(output)) {
119
+ console.table(output);
120
+ } else {
121
+ console.log(JSON.stringify(output, null, 2));
122
+ }
123
+ } else {
124
+ console.log(JSON.stringify(output, null, 2));
125
+ }
126
+ }
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import chalk from 'chalk';
4
+ {{#each structure.groups}}
5
+ import { register{{pascalCase name}}Commands } from './commands/{{name}}';
6
+ {{/each}}
7
+ {{#if (hasItems structure.flatCommands)}}
8
+ import { registerFlatCommands } from './flat-commands';
9
+ {{/if}}
10
+
11
+ const program = new Command();
12
+
13
+ program
14
+ .name({{jsString structure.name}})
15
+ .description({{jsString structure.description}})
16
+ .version({{jsString structure.version}});
17
+
18
+ // Global options
19
+ program
20
+ .option('--endpoint <url>', 'Override the base API URL', {{jsString structure.baseUrl}})
21
+ .addOption(new (require('commander').Option)('--format <format>', 'Output format').choices(['json', 'yaml', 'table']).default('json'))
22
+ .option('--verbose', 'Enable verbose request logging')
23
+ .option('--query [expr]', 'JMESPath expression to filter the response')
24
+ .option('--all-pages', 'Auto-follow Link rel="next" headers to collect all pages');
25
+
26
+ // Register tag-level command groups
27
+ {{#each structure.groups}}
28
+ register{{pascalCase name}}Commands(program);
29
+ {{/each}}
30
+ {{#if (hasItems structure.flatCommands)}}
31
+ // Register untagged operations directly on the program
32
+ registerFlatCommands(program);
33
+ {{/if}}
34
+
35
+ program.parseAsync(process.argv).catch((err: Error) => {
36
+ console.error(chalk.red('Error:'), err.message);
37
+ process.exit(1);
38
+ });
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "{{structure.name}}",
3
+ "version": "1.0.0",
4
+ "description": {{jsString structure.description}},
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "{{structure.name}}": "./bin/{{structure.name}}"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "node dist/index.js",
12
+ "dev": "ts-node src/index.ts"
13
+ },
14
+ "dependencies": {
15
+ "eventsource-parser": "^3.0.6",
16
+ "axios": "^1.7.9",
17
+ "chalk": "^4.1.2",
18
+ "commander": "^12.1.0",
19
+ "jmespath": "^0.16.0",
20
+ "ora": "^5.4.1",
21
+ "yaml": "^2.6.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/node": "^22.0.0",
25
+ "ts-node": "^10.9.2",
26
+ "typescript": "^5.7.3"
27
+ },
28
+ "engines": {
29
+ "node": ">=18"
30
+ }
31
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "allowSyntheticDefaultImports": true,
11
+ "sourceMap": true,
12
+ "skipLibCheck": true
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "dist"]
16
+ }
@@ -0,0 +1,104 @@
1
+ export type HTTPMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'options';
2
+ export interface CLIConfig {
3
+ oas: string;
4
+ name: string;
5
+ output: string;
6
+ }
7
+ export interface Option {
8
+ name: string;
9
+ alias?: string;
10
+ description: string;
11
+ required: boolean;
12
+ type: 'string' | 'number' | 'boolean';
13
+ defaultValue?: string | number | boolean;
14
+ enum?: string[];
15
+ }
16
+ export interface Parameter {
17
+ name: string;
18
+ in: 'query' | 'path' | 'header' | 'cookie';
19
+ description: string;
20
+ required: boolean;
21
+ schema: {
22
+ type: string;
23
+ enum?: string[];
24
+ default?: unknown;
25
+ };
26
+ }
27
+ export interface RequestBodyField {
28
+ name: string;
29
+ optKey: string;
30
+ type: string;
31
+ required: boolean;
32
+ description: string;
33
+ enum?: string[];
34
+ }
35
+ export interface ResponseField {
36
+ name: string;
37
+ type: string;
38
+ description: string;
39
+ }
40
+ export interface RequestBody {
41
+ description: string;
42
+ required: boolean;
43
+ contentType: string;
44
+ schema: Record<string, unknown>;
45
+ fields: RequestBodyField[];
46
+ }
47
+ export interface APIResponse {
48
+ statusCode: string;
49
+ description: string;
50
+ contentType?: string;
51
+ fields: ResponseField[];
52
+ isArray: boolean;
53
+ }
54
+ export interface SubCommand {
55
+ name: string;
56
+ description: string;
57
+ method: HTTPMethod;
58
+ path: string;
59
+ parameters: Parameter[];
60
+ requestBody?: RequestBody;
61
+ responses: Record<string, APIResponse>;
62
+ options: Option[];
63
+ securitySchemes: string[];
64
+ aliases: string[];
65
+ streaming?: 'sse';
66
+ }
67
+ export interface CommandGroup {
68
+ name: string;
69
+ description: string;
70
+ subcommands: SubCommand[];
71
+ }
72
+ /** Body field name → environment variable mapping for dynamic token providers. */
73
+ export interface TokenEnvVar {
74
+ name: string;
75
+ env: string;
76
+ }
77
+ export type AuthType = 'bearer' | 'apiKey' | 'basic' | 'oauth2-cc' | 'dynamic' | 'none';
78
+ export interface AuthConfig {
79
+ type: AuthType;
80
+ envVar: string;
81
+ headerName?: string;
82
+ tokenUrl?: string;
83
+ clientIdEnvVar?: string;
84
+ clientSecretEnvVar?: string;
85
+ scopesEnvVar?: string;
86
+ tokenEnvVars?: TokenEnvVar[];
87
+ }
88
+ export interface CommandStructure {
89
+ name: string;
90
+ description: string;
91
+ version: string;
92
+ baseUrl: string;
93
+ groups: CommandGroup[];
94
+ flatCommands: SubCommand[];
95
+ globalOptions: Option[];
96
+ authConfig: AuthConfig;
97
+ allAuthSchemes: string[];
98
+ warnings: string[];
99
+ }
100
+ export interface GeneratedFile {
101
+ relativePath: string;
102
+ content: string;
103
+ }
104
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1F,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACzC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC3C,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;QAChB,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,gBAAgB,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACvC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,eAAe,EAAE,MAAM,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,KAAK,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,kFAAkF;AAClF,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,QAAQ,GAChB,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,WAAW,GACX,SAAS,GACT,MAAM,CAAC;AAEX,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,QAAQ,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,WAAW,EAAE,CAAC;CAC9B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,YAAY,EAAE,CAAC;IACvB,YAAY,EAAE,UAAU,EAAE,CAAC;IAC3B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,UAAU,EAAE,UAAU,CAAC;IACvB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@tronsfey/openapi2cli",
3
+ "version": "1.0.10",
4
+ "description": "Generate a fully typed Commander.js CLI project from an OpenAPI 3.x specification",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "openapi2cli": "./bin/openapi2cli"
8
+ },
9
+ "files": [
10
+ "dist/",
11
+ "bin/",
12
+ "README.md",
13
+ "LICENSE"
14
+ ],
15
+ "keywords": [
16
+ "openapi",
17
+ "cli",
18
+ "generator",
19
+ "commander",
20
+ "typescript",
21
+ "swagger",
22
+ "codegen",
23
+ "oas3",
24
+ "rest"
25
+ ],
26
+ "author": "",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/tronsfey928/openapi2cli.git"
31
+ },
32
+ "homepage": "https://github.com/tronsfey928/openapi2cli#readme",
33
+ "bugs": {
34
+ "url": "https://github.com/tronsfey928/openapi2cli/issues"
35
+ },
36
+ "engines": {
37
+ "node": ">=18"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc && copyfiles -u 1 \"src/templates/**/*.hbs\" dist/",
41
+ "dev": "ts-node src/index.ts",
42
+ "start": "node dist/index.js",
43
+ "test": "jest --testPathIgnorePatterns=tests/integration",
44
+ "test:e2e": "jest tests/integration/express-server.test.ts --testTimeout=180000",
45
+ "test:integration": "jest tests/integration/github.test.ts --testTimeout=180000",
46
+ "test:all": "jest --testTimeout=180000",
47
+ "generate:examples": "ts-node scripts/generate-example.ts",
48
+ "test:coverage": "jest --coverage --testPathIgnorePatterns=tests/integration",
49
+ "lint": "tsc --noEmit",
50
+ "clean": "rm -rf dist",
51
+ "prepublishOnly": "npm run lint && npm test && npm run build && npm run generate:examples && npm run test:e2e"
52
+ },
53
+ "dependencies": {
54
+ "@apidevtools/swagger-parser": "^10.1.0",
55
+ "axios": "^1.7.9",
56
+ "chalk": "^4.1.2",
57
+ "commander": "^12.1.0",
58
+ "fs-extra": "^11.2.0",
59
+ "handlebars": "^4.7.8",
60
+ "lodash": "^4.17.23",
61
+ "openapi-types": "^12.1.3",
62
+ "ora": "^5.4.1",
63
+ "pinyin-pro": "^3.28.0"
64
+ },
65
+ "devDependencies": {
66
+ "@types/express": "^4.17.21",
67
+ "@types/fs-extra": "^11.0.4",
68
+ "express": "^4.21.2",
69
+ "@types/jest": "^29.5.14",
70
+ "@types/lodash": "^4.17.16",
71
+ "@types/node": "^22.0.0",
72
+ "copyfiles": "^2.4.1",
73
+ "jest": "^29.7.0",
74
+ "ts-jest": "^29.4.0",
75
+ "ts-node": "^10.9.2",
76
+ "typescript": "^5.7.3"
77
+ },
78
+ "jest": {
79
+ "preset": "ts-jest",
80
+ "testEnvironment": "node",
81
+ "testMatch": [
82
+ "**/tests/**/*.test.ts"
83
+ ],
84
+ "collectCoverageFrom": [
85
+ "src/**/*.ts"
86
+ ]
87
+ }
88
+ }