zod-codegen 1.6.3 → 1.7.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.
- package/.github/workflows/ci.yml +50 -48
- package/.github/workflows/release.yml +13 -3
- package/.husky/commit-msg +1 -1
- package/.husky/pre-commit +1 -1
- package/.lintstagedrc.json +5 -1
- package/.nvmrc +1 -1
- package/.prettierrc.json +12 -5
- package/CHANGELOG.md +11 -0
- package/CONTRIBUTING.md +12 -12
- package/EXAMPLES.md +135 -57
- package/PERFORMANCE.md +4 -4
- package/README.md +87 -64
- package/SECURITY.md +1 -1
- package/dist/src/cli.js +11 -18
- package/dist/src/generator.d.ts +2 -2
- package/dist/src/generator.d.ts.map +1 -1
- package/dist/src/generator.js +5 -3
- package/dist/src/interfaces/code-generator.d.ts.map +1 -1
- package/dist/src/services/code-generator.service.d.ts +3 -1
- package/dist/src/services/code-generator.service.d.ts.map +1 -1
- package/dist/src/services/code-generator.service.js +236 -219
- package/dist/src/services/file-reader.service.d.ts.map +1 -1
- package/dist/src/services/file-reader.service.js +1 -1
- package/dist/src/services/file-writer.service.d.ts.map +1 -1
- package/dist/src/services/file-writer.service.js +2 -2
- package/dist/src/services/import-builder.service.d.ts.map +1 -1
- package/dist/src/services/import-builder.service.js +3 -3
- package/dist/src/services/type-builder.service.d.ts.map +1 -1
- package/dist/src/types/generator-options.d.ts.map +1 -1
- package/dist/src/types/openapi.d.ts.map +1 -1
- package/dist/src/types/openapi.js +20 -20
- package/dist/src/utils/error-handler.d.ts.map +1 -1
- package/dist/src/utils/naming-convention.d.ts.map +1 -1
- package/dist/src/utils/naming-convention.js +6 -3
- package/dist/src/utils/signal-handler.d.ts.map +1 -1
- package/dist/tests/integration/cli-comprehensive.test.d.ts +2 -0
- package/dist/tests/integration/cli-comprehensive.test.d.ts.map +1 -0
- package/dist/tests/integration/cli-comprehensive.test.js +110 -0
- package/dist/tests/integration/cli.test.d.ts +2 -0
- package/dist/tests/integration/cli.test.d.ts.map +1 -0
- package/dist/tests/integration/cli.test.js +25 -0
- package/dist/tests/integration/error-scenarios.test.d.ts +2 -0
- package/dist/tests/integration/error-scenarios.test.d.ts.map +1 -0
- package/dist/tests/integration/error-scenarios.test.js +169 -0
- package/dist/tests/integration/snapshots.test.d.ts +2 -0
- package/dist/tests/integration/snapshots.test.d.ts.map +1 -0
- package/dist/tests/integration/snapshots.test.js +100 -0
- package/dist/tests/unit/code-generator-edge-cases.test.d.ts +2 -0
- package/dist/tests/unit/code-generator-edge-cases.test.d.ts.map +1 -0
- package/dist/tests/unit/code-generator-edge-cases.test.js +506 -0
- package/dist/tests/unit/code-generator.test.d.ts +2 -0
- package/dist/tests/unit/code-generator.test.d.ts.map +1 -0
- package/dist/tests/unit/code-generator.test.js +1364 -0
- package/dist/tests/unit/file-reader.test.d.ts +2 -0
- package/dist/tests/unit/file-reader.test.d.ts.map +1 -0
- package/dist/tests/unit/file-reader.test.js +125 -0
- package/dist/tests/unit/generator.test.d.ts +2 -0
- package/dist/tests/unit/generator.test.d.ts.map +1 -0
- package/dist/tests/unit/generator.test.js +119 -0
- package/dist/tests/unit/naming-convention.test.d.ts +2 -0
- package/dist/tests/unit/naming-convention.test.d.ts.map +1 -0
- package/dist/tests/unit/naming-convention.test.js +256 -0
- package/dist/tests/unit/reporter.test.d.ts +2 -0
- package/dist/tests/unit/reporter.test.d.ts.map +1 -0
- package/dist/tests/unit/reporter.test.js +44 -0
- package/dist/tests/unit/type-builder.test.d.ts +2 -0
- package/dist/tests/unit/type-builder.test.d.ts.map +1 -0
- package/dist/tests/unit/type-builder.test.js +108 -0
- package/dist/vitest.config.d.ts.map +1 -1
- package/dist/vitest.config.js +10 -20
- package/eslint.config.mjs +38 -28
- package/examples/.gitkeep +1 -1
- package/examples/README.md +4 -2
- package/examples/petstore/README.md +18 -17
- package/examples/petstore/{type.ts → api.ts} +158 -74
- package/examples/petstore/authenticated-usage.ts +6 -4
- package/examples/petstore/basic-usage.ts +4 -3
- package/examples/petstore/error-handling-usage.ts +84 -0
- package/examples/petstore/retry-handler-usage.ts +11 -18
- package/examples/petstore/server-variables-usage.ts +10 -10
- package/examples/pokeapi/README.md +8 -8
- package/examples/pokeapi/api.ts +218 -0
- package/examples/pokeapi/basic-usage.ts +3 -2
- package/examples/pokeapi/custom-client.ts +5 -4
- package/package.json +17 -21
- package/src/cli.ts +20 -25
- package/src/generator.ts +13 -11
- package/src/interfaces/code-generator.ts +1 -1
- package/src/services/code-generator.service.ts +799 -1120
- package/src/services/file-reader.service.ts +6 -5
- package/src/services/file-writer.service.ts +7 -7
- package/src/services/import-builder.service.ts +9 -13
- package/src/services/type-builder.service.ts +8 -19
- package/src/types/generator-options.ts +1 -1
- package/src/types/openapi.ts +22 -22
- package/src/utils/error-handler.ts +2 -2
- package/src/utils/naming-convention.ts +13 -10
- package/src/utils/reporter.ts +2 -2
- package/src/utils/signal-handler.ts +7 -8
- package/tests/integration/cli-comprehensive.test.ts +38 -32
- package/tests/integration/cli.test.ts +5 -5
- package/tests/integration/error-scenarios.test.ts +20 -26
- package/tests/integration/snapshots.test.ts +19 -23
- package/tests/unit/code-generator-edge-cases.test.ts +133 -133
- package/tests/unit/code-generator.test.ts +431 -330
- package/tests/unit/file-reader.test.ts +14 -14
- package/tests/unit/generator.test.ts +30 -18
- package/tests/unit/naming-convention.test.ts +27 -27
- package/tests/unit/type-builder.test.ts +2 -2
- package/tsconfig.json +5 -3
- package/vitest.config.ts +11 -21
- package/dist/scripts/update-manifest.d.ts +0 -14
- package/dist/scripts/update-manifest.d.ts.map +0 -1
- package/dist/scripts/update-manifest.js +0 -33
- package/dist/src/assets/manifest.json +0 -5
- package/examples/pokeapi/type.ts +0 -109
- package/generated/type.ts +0 -371
- package/scripts/update-manifest.ts +0 -49
- package/src/assets/manifest.json +0 -5
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {describe, expect, it, vi
|
|
2
|
-
import {
|
|
3
|
-
import {join} from 'node:path';
|
|
4
|
-
import {fileURLToPath} from 'node:url';
|
|
5
|
-
import {dirname} from 'node:path';
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { OpenApiFileParserService, SyncFileReaderService } from '../../src/services/file-reader.service';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { dirname } from 'node:path';
|
|
6
6
|
|
|
7
7
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
8
8
|
|
|
@@ -36,7 +36,7 @@ describe('SyncFileReaderService', () => {
|
|
|
36
36
|
const originalFetch = global.fetch;
|
|
37
37
|
global.fetch = vi.fn().mockResolvedValue({
|
|
38
38
|
ok: true,
|
|
39
|
-
text: async () => '{"openapi": "3.0.0", "info": {"title": "Test", "version": "1.0.0"}, "paths": {}}'
|
|
39
|
+
text: async () => '{"openapi": "3.0.0", "info": {"title": "Test", "version": "1.0.0"}, "paths": {}}'
|
|
40
40
|
} as Response);
|
|
41
41
|
|
|
42
42
|
const content = await reader.readFile('https://example.com/openapi.json');
|
|
@@ -50,7 +50,7 @@ describe('SyncFileReaderService', () => {
|
|
|
50
50
|
const mockFetch = vi.fn().mockResolvedValue({
|
|
51
51
|
ok: false,
|
|
52
52
|
status: 404,
|
|
53
|
-
statusText: 'Not Found'
|
|
53
|
+
statusText: 'Not Found'
|
|
54
54
|
} as Response);
|
|
55
55
|
global.fetch = mockFetch;
|
|
56
56
|
|
|
@@ -74,8 +74,8 @@ describe('OpenApiFileParserService', () => {
|
|
|
74
74
|
it('should parse valid JSON OpenAPI spec', () => {
|
|
75
75
|
const jsonSpec = JSON.stringify({
|
|
76
76
|
openapi: '3.0.0',
|
|
77
|
-
info: {title: 'Test API', version: '1.0.0'},
|
|
78
|
-
paths: {}
|
|
77
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
78
|
+
paths: {}
|
|
79
79
|
});
|
|
80
80
|
|
|
81
81
|
const result = parser.parse(jsonSpec);
|
|
@@ -102,8 +102,8 @@ paths: {}
|
|
|
102
102
|
it('should parse already parsed objects', () => {
|
|
103
103
|
const spec = {
|
|
104
104
|
openapi: '3.0.0',
|
|
105
|
-
info: {title: 'Test API', version: '1.0.0'},
|
|
106
|
-
paths: {}
|
|
105
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
106
|
+
paths: {}
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
const result = parser.parse(spec);
|
|
@@ -114,8 +114,8 @@ paths: {}
|
|
|
114
114
|
it('should validate OpenAPI structure', () => {
|
|
115
115
|
const invalidSpec = {
|
|
116
116
|
openapi: '2.0.0', // Wrong version format
|
|
117
|
-
info: {title: 'Test API', version: '1.0.0'},
|
|
118
|
-
paths: {}
|
|
117
|
+
info: { title: 'Test API', version: '1.0.0' },
|
|
118
|
+
paths: {}
|
|
119
119
|
};
|
|
120
120
|
|
|
121
121
|
expect(() => parser.parse(invalidSpec)).toThrow();
|
|
@@ -125,7 +125,7 @@ paths: {}
|
|
|
125
125
|
const invalidSpec = {
|
|
126
126
|
openapi: '3.0.0',
|
|
127
127
|
// Missing info
|
|
128
|
-
paths: {}
|
|
128
|
+
paths: {}
|
|
129
129
|
};
|
|
130
130
|
|
|
131
131
|
expect(() => parser.parse(invalidSpec)).toThrow();
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {dirname} from 'node:path';
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { Generator } from '../../src/generator';
|
|
6
|
+
import { Reporter } from '../../src/utils/reporter';
|
|
8
7
|
|
|
9
8
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
9
|
const testOutputDir = join(__dirname, '../../test-output');
|
|
@@ -16,21 +15,22 @@ describe('Generator', () => {
|
|
|
16
15
|
beforeEach(() => {
|
|
17
16
|
// Clean up test output directory
|
|
18
17
|
if (existsSync(testOutputDir)) {
|
|
19
|
-
rmSync(testOutputDir, {recursive: true, force: true});
|
|
18
|
+
rmSync(testOutputDir, { recursive: true, force: true });
|
|
20
19
|
}
|
|
21
|
-
|
|
20
|
+
|
|
21
|
+
mkdirSync(testOutputDir, { recursive: true });
|
|
22
22
|
|
|
23
23
|
// Create a mock reporter
|
|
24
24
|
mockReporter = {
|
|
25
25
|
log: vi.fn(),
|
|
26
|
-
error: vi.fn()
|
|
26
|
+
error: vi.fn()
|
|
27
27
|
} as unknown as Reporter;
|
|
28
28
|
});
|
|
29
29
|
|
|
30
30
|
afterEach(() => {
|
|
31
31
|
// Clean up test output directory
|
|
32
32
|
if (existsSync(testOutputDir)) {
|
|
33
|
-
rmSync(testOutputDir, {recursive: true, force: true});
|
|
33
|
+
rmSync(testOutputDir, { recursive: true, force: true });
|
|
34
34
|
}
|
|
35
35
|
});
|
|
36
36
|
|
|
@@ -62,7 +62,7 @@ describe('Generator', () => {
|
|
|
62
62
|
expect(mockReporter.error).not.toHaveBeenCalled();
|
|
63
63
|
|
|
64
64
|
// Verify output file was created
|
|
65
|
-
const outputFile = join(testOutputDir, '
|
|
65
|
+
const outputFile = join(testOutputDir, 'api.ts');
|
|
66
66
|
expect(existsSync(outputFile)).toBe(true);
|
|
67
67
|
|
|
68
68
|
// Verify file contains expected content
|
|
@@ -84,9 +84,8 @@ describe('Generator', () => {
|
|
|
84
84
|
it('should handle invalid OpenAPI specifications', async () => {
|
|
85
85
|
// Create a temporary invalid OpenAPI file
|
|
86
86
|
const invalidFile = join(testOutputDir, 'invalid.yaml');
|
|
87
|
-
mkdirSync(testOutputDir, {recursive: true});
|
|
88
|
-
|
|
89
|
-
const {writeFileSync} = await import('node:fs');
|
|
87
|
+
mkdirSync(testOutputDir, { recursive: true });
|
|
88
|
+
const { writeFileSync } = await import('node:fs');
|
|
90
89
|
writeFileSync(invalidFile, 'invalid: yaml: content: [unclosed');
|
|
91
90
|
|
|
92
91
|
generator = new Generator('test-app', '1.0.0', mockReporter, invalidFile, testOutputDir);
|
|
@@ -102,7 +101,7 @@ describe('Generator', () => {
|
|
|
102
101
|
|
|
103
102
|
await generator.run();
|
|
104
103
|
|
|
105
|
-
const outputFile = join(testOutputDir, '
|
|
104
|
+
const outputFile = join(testOutputDir, 'api.ts');
|
|
106
105
|
const content = readFileSync(outputFile, 'utf-8');
|
|
107
106
|
|
|
108
107
|
// Check for key components
|
|
@@ -117,7 +116,7 @@ describe('Generator', () => {
|
|
|
117
116
|
|
|
118
117
|
await generator.run();
|
|
119
118
|
|
|
120
|
-
const outputFile = join(testOutputDir, '
|
|
119
|
+
const outputFile = join(testOutputDir, 'api.ts');
|
|
121
120
|
const content = readFileSync(outputFile, 'utf-8');
|
|
122
121
|
|
|
123
122
|
expect(content).toContain('AUTOGENERATED');
|
|
@@ -125,11 +124,24 @@ describe('Generator', () => {
|
|
|
125
124
|
expect(content).toContain('eslint-disable');
|
|
126
125
|
});
|
|
127
126
|
|
|
127
|
+
it('should write to custom file path when output is a .ts path', async () => {
|
|
128
|
+
const customPath = join(testOutputDir, 'custom.ts');
|
|
129
|
+
generator = new Generator('test-app', '1.0.0', mockReporter, './samples/swagger-petstore.yaml', customPath);
|
|
130
|
+
|
|
131
|
+
const exitCode = await generator.run();
|
|
132
|
+
|
|
133
|
+
expect(exitCode).toBe(0);
|
|
134
|
+
expect(existsSync(customPath)).toBe(true);
|
|
135
|
+
expect(existsSync(join(testOutputDir, 'api.ts'))).toBe(false);
|
|
136
|
+
const content = readFileSync(customPath, 'utf-8');
|
|
137
|
+
expect(content).toMatch(/import\s*{\s*z\s*}\s*from\s*['"]zod['"]/);
|
|
138
|
+
});
|
|
139
|
+
|
|
128
140
|
it('should handle unknown error type (not Error instance)', async () => {
|
|
129
141
|
generator = new Generator('test-app', '1.0.0', mockReporter, './samples/swagger-petstore.yaml', testOutputDir);
|
|
130
142
|
|
|
131
143
|
// Mock the fileReader to throw a non-Error object
|
|
132
|
-
const {SyncFileReaderService} = await import('../../src/services/file-reader.service');
|
|
144
|
+
const { SyncFileReaderService } = await import('../../src/services/file-reader.service');
|
|
133
145
|
const originalReadFile = SyncFileReaderService.prototype.readFile;
|
|
134
146
|
SyncFileReaderService.prototype.readFile = vi.fn().mockRejectedValue('string error');
|
|
135
147
|
|
|
@@ -1,38 +1,38 @@
|
|
|
1
|
-
import {describe, expect, it} from 'vitest';
|
|
2
|
-
import {
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { type NamingConvention, transformNamingConvention } from '../../src/utils/naming-convention';
|
|
3
3
|
|
|
4
4
|
describe('transformNamingConvention', () => {
|
|
5
5
|
describe('transform', () => {
|
|
6
|
-
const testCases:
|
|
6
|
+
const testCases: {
|
|
7
7
|
input: string;
|
|
8
8
|
convention: NamingConvention;
|
|
9
9
|
expected: string;
|
|
10
10
|
description: string;
|
|
11
|
-
}
|
|
11
|
+
}[] = [
|
|
12
12
|
// camelCase
|
|
13
13
|
{
|
|
14
14
|
input: 'get_user_by_id',
|
|
15
15
|
convention: 'camelCase',
|
|
16
16
|
expected: 'getUserById',
|
|
17
|
-
description: 'should convert snake_case to camelCase'
|
|
17
|
+
description: 'should convert snake_case to camelCase'
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
input: 'GetUserById',
|
|
21
21
|
convention: 'camelCase',
|
|
22
22
|
expected: 'getUserById',
|
|
23
|
-
description: 'should convert PascalCase to camelCase'
|
|
23
|
+
description: 'should convert PascalCase to camelCase'
|
|
24
24
|
},
|
|
25
25
|
{
|
|
26
26
|
input: 'get-user-by-id',
|
|
27
27
|
convention: 'camelCase',
|
|
28
28
|
expected: 'getUserById',
|
|
29
|
-
description: 'should convert kebab-case to camelCase'
|
|
29
|
+
description: 'should convert kebab-case to camelCase'
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
input: 'getUserById',
|
|
33
33
|
convention: 'camelCase',
|
|
34
34
|
expected: 'getUserById',
|
|
35
|
-
description: 'should keep camelCase as camelCase'
|
|
35
|
+
description: 'should keep camelCase as camelCase'
|
|
36
36
|
},
|
|
37
37
|
|
|
38
38
|
// PascalCase
|
|
@@ -40,19 +40,19 @@ describe('transformNamingConvention', () => {
|
|
|
40
40
|
input: 'get_user_by_id',
|
|
41
41
|
convention: 'PascalCase',
|
|
42
42
|
expected: 'GetUserById',
|
|
43
|
-
description: 'should convert snake_case to PascalCase'
|
|
43
|
+
description: 'should convert snake_case to PascalCase'
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
input: 'get-user-by-id',
|
|
47
47
|
convention: 'PascalCase',
|
|
48
48
|
expected: 'GetUserById',
|
|
49
|
-
description: 'should convert kebab-case to PascalCase'
|
|
49
|
+
description: 'should convert kebab-case to PascalCase'
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
input: 'getUserById',
|
|
53
53
|
convention: 'PascalCase',
|
|
54
54
|
expected: 'GetUserById',
|
|
55
|
-
description: 'should convert camelCase to PascalCase'
|
|
55
|
+
description: 'should convert camelCase to PascalCase'
|
|
56
56
|
},
|
|
57
57
|
|
|
58
58
|
// snake_case
|
|
@@ -60,19 +60,19 @@ describe('transformNamingConvention', () => {
|
|
|
60
60
|
input: 'getUserById',
|
|
61
61
|
convention: 'snake_case',
|
|
62
62
|
expected: 'get_user_by_id',
|
|
63
|
-
description: 'should convert camelCase to snake_case'
|
|
63
|
+
description: 'should convert camelCase to snake_case'
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
input: 'GetUserById',
|
|
67
67
|
convention: 'snake_case',
|
|
68
68
|
expected: 'get_user_by_id',
|
|
69
|
-
description: 'should convert PascalCase to snake_case'
|
|
69
|
+
description: 'should convert PascalCase to snake_case'
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
input: 'get-user-by-id',
|
|
73
73
|
convention: 'snake_case',
|
|
74
74
|
expected: 'get_user_by_id',
|
|
75
|
-
description: 'should convert kebab-case to snake_case'
|
|
75
|
+
description: 'should convert kebab-case to snake_case'
|
|
76
76
|
},
|
|
77
77
|
|
|
78
78
|
// kebab-case
|
|
@@ -80,19 +80,19 @@ describe('transformNamingConvention', () => {
|
|
|
80
80
|
input: 'getUserById',
|
|
81
81
|
convention: 'kebab-case',
|
|
82
82
|
expected: 'get-user-by-id',
|
|
83
|
-
description: 'should convert camelCase to kebab-case'
|
|
83
|
+
description: 'should convert camelCase to kebab-case'
|
|
84
84
|
},
|
|
85
85
|
{
|
|
86
86
|
input: 'GetUserById',
|
|
87
87
|
convention: 'kebab-case',
|
|
88
88
|
expected: 'get-user-by-id',
|
|
89
|
-
description: 'should convert PascalCase to kebab-case'
|
|
89
|
+
description: 'should convert PascalCase to kebab-case'
|
|
90
90
|
},
|
|
91
91
|
{
|
|
92
92
|
input: 'get_user_by_id',
|
|
93
93
|
convention: 'kebab-case',
|
|
94
94
|
expected: 'get-user-by-id',
|
|
95
|
-
description: 'should convert snake_case to kebab-case'
|
|
95
|
+
description: 'should convert snake_case to kebab-case'
|
|
96
96
|
},
|
|
97
97
|
|
|
98
98
|
// SCREAMING_SNAKE_CASE
|
|
@@ -100,13 +100,13 @@ describe('transformNamingConvention', () => {
|
|
|
100
100
|
input: 'getUserById',
|
|
101
101
|
convention: 'SCREAMING_SNAKE_CASE',
|
|
102
102
|
expected: 'GET_USER_BY_ID',
|
|
103
|
-
description: 'should convert camelCase to SCREAMING_SNAKE_CASE'
|
|
103
|
+
description: 'should convert camelCase to SCREAMING_SNAKE_CASE'
|
|
104
104
|
},
|
|
105
105
|
{
|
|
106
106
|
input: 'get-user-by-id',
|
|
107
107
|
convention: 'SCREAMING_SNAKE_CASE',
|
|
108
108
|
expected: 'GET_USER_BY_ID',
|
|
109
|
-
description: 'should convert kebab-case to SCREAMING_SNAKE_CASE'
|
|
109
|
+
description: 'should convert kebab-case to SCREAMING_SNAKE_CASE'
|
|
110
110
|
},
|
|
111
111
|
|
|
112
112
|
// SCREAMING-KEBAB-CASE
|
|
@@ -114,13 +114,13 @@ describe('transformNamingConvention', () => {
|
|
|
114
114
|
input: 'getUserById',
|
|
115
115
|
convention: 'SCREAMING-KEBAB-CASE',
|
|
116
116
|
expected: 'GET-USER-BY-ID',
|
|
117
|
-
description: 'should convert camelCase to SCREAMING-KEBAB-CASE'
|
|
117
|
+
description: 'should convert camelCase to SCREAMING-KEBAB-CASE'
|
|
118
118
|
},
|
|
119
119
|
{
|
|
120
120
|
input: 'get_user_by_id',
|
|
121
121
|
convention: 'SCREAMING-KEBAB-CASE',
|
|
122
122
|
expected: 'GET-USER-BY-ID',
|
|
123
|
-
description: 'should convert snake_case to SCREAMING-KEBAB-CASE'
|
|
123
|
+
description: 'should convert snake_case to SCREAMING-KEBAB-CASE'
|
|
124
124
|
},
|
|
125
125
|
|
|
126
126
|
// Edge cases
|
|
@@ -128,29 +128,29 @@ describe('transformNamingConvention', () => {
|
|
|
128
128
|
input: '',
|
|
129
129
|
convention: 'camelCase',
|
|
130
130
|
expected: '',
|
|
131
|
-
description: 'should handle empty string'
|
|
131
|
+
description: 'should handle empty string'
|
|
132
132
|
},
|
|
133
133
|
{
|
|
134
134
|
input: 'a',
|
|
135
135
|
convention: 'camelCase',
|
|
136
136
|
expected: 'a',
|
|
137
|
-
description: 'should handle single character'
|
|
137
|
+
description: 'should handle single character'
|
|
138
138
|
},
|
|
139
139
|
{
|
|
140
140
|
input: 'API',
|
|
141
141
|
convention: 'camelCase',
|
|
142
142
|
expected: 'aPI',
|
|
143
|
-
description: 'should handle all uppercase (splits at uppercase boundaries)'
|
|
143
|
+
description: 'should handle all uppercase (splits at uppercase boundaries)'
|
|
144
144
|
},
|
|
145
145
|
{
|
|
146
146
|
input: 'getUser123ById',
|
|
147
147
|
convention: 'snake_case',
|
|
148
148
|
expected: 'get_user_123_by_id',
|
|
149
|
-
description: 'should handle numbers in identifiers (splits at digit boundaries)'
|
|
150
|
-
}
|
|
149
|
+
description: 'should handle numbers in identifiers (splits at digit boundaries)'
|
|
150
|
+
}
|
|
151
151
|
];
|
|
152
152
|
|
|
153
|
-
testCases.forEach(({input, convention, expected, description}) => {
|
|
153
|
+
testCases.forEach(({ input, convention, expected, description }) => {
|
|
154
154
|
it(description, () => {
|
|
155
155
|
const result = transformNamingConvention(input, convention);
|
|
156
156
|
expect(result).toBe(expected);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {describe, expect, it
|
|
2
|
-
import {TypeScriptTypeBuilderService} from '../../src/services/type-builder.service';
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { TypeScriptTypeBuilderService } from '../../src/services/type-builder.service';
|
|
3
3
|
import * as ts from 'typescript';
|
|
4
4
|
|
|
5
5
|
describe('TypeScriptTypeBuilderService', () => {
|
package/tsconfig.json
CHANGED
|
@@ -4,17 +4,19 @@
|
|
|
4
4
|
"alwaysStrict": true,
|
|
5
5
|
"baseUrl": ".",
|
|
6
6
|
"checkJs": false,
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"declarationMap": true,
|
|
7
9
|
"downlevelIteration": true,
|
|
10
|
+
"emitDecoratorMetadata": true,
|
|
8
11
|
"esModuleInterop": true,
|
|
9
12
|
"exactOptionalPropertyTypes": true,
|
|
13
|
+
"experimentalDecorators": true,
|
|
10
14
|
"forceConsistentCasingInFileNames": true,
|
|
11
15
|
"incremental": true,
|
|
12
16
|
"isolatedModules": true,
|
|
13
17
|
"lib": ["ES2022", "DOM"],
|
|
14
18
|
"module": "ESNext",
|
|
15
19
|
"moduleResolution": "bundler",
|
|
16
|
-
"declaration": true,
|
|
17
|
-
"declarationMap": true,
|
|
18
20
|
"noEmit": false,
|
|
19
21
|
"noFallthroughCasesInSwitch": true,
|
|
20
22
|
"noImplicitAny": true,
|
|
@@ -41,6 +43,6 @@
|
|
|
41
43
|
"useUnknownInCatchVariables": true,
|
|
42
44
|
"verbatimModuleSyntax": false
|
|
43
45
|
},
|
|
44
|
-
"exclude": ["node_modules", "dist", "build", "examples
|
|
46
|
+
"exclude": ["node_modules", "dist", "build", "examples"],
|
|
45
47
|
"include": ["src/**/*.ts", "scripts/**/*.ts", "tests/**/*.ts", "vitest.config.ts"]
|
|
46
48
|
}
|
package/vitest.config.ts
CHANGED
|
@@ -1,39 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { defineConfig } from 'vitest/config';
|
|
3
3
|
|
|
4
4
|
export default defineConfig({
|
|
5
5
|
test: {
|
|
6
6
|
globals: true,
|
|
7
7
|
environment: 'node',
|
|
8
|
-
include: ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts
|
|
8
|
+
include: ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}'],
|
|
9
9
|
exclude: ['node_modules', 'dist', 'coverage'],
|
|
10
10
|
coverage: {
|
|
11
11
|
provider: 'v8',
|
|
12
12
|
reporter: ['text', 'json', 'html', 'lcov'],
|
|
13
|
-
exclude: [
|
|
14
|
-
'coverage/**',
|
|
15
|
-
'dist/**',
|
|
16
|
-
'node_modules/**',
|
|
17
|
-
'**/*.d.ts',
|
|
18
|
-
'**/*.config.*',
|
|
19
|
-
'**/*.test.*',
|
|
20
|
-
'**/*.spec.*',
|
|
21
|
-
'scripts/**',
|
|
22
|
-
'samples/**',
|
|
23
|
-
],
|
|
13
|
+
exclude: ['coverage/**', 'dist/**', 'node_modules/**', '**/*.d.ts', '**/*.config.*', '**/*.test.*', '**/*.spec.*', 'scripts/**', 'samples/**'],
|
|
24
14
|
thresholds: {
|
|
25
15
|
global: {
|
|
26
16
|
branches: 80,
|
|
27
17
|
functions: 80,
|
|
28
18
|
lines: 80,
|
|
29
|
-
statements: 80
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
19
|
+
statements: 80
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
33
23
|
},
|
|
34
24
|
resolve: {
|
|
35
25
|
alias: {
|
|
36
|
-
'@': resolve(__dirname, 'src')
|
|
37
|
-
}
|
|
38
|
-
}
|
|
26
|
+
'@': resolve(__dirname, 'src')
|
|
27
|
+
}
|
|
28
|
+
}
|
|
39
29
|
});
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
interface PackageJson {
|
|
2
|
-
name: string;
|
|
3
|
-
version: string;
|
|
4
|
-
description: string;
|
|
5
|
-
}
|
|
6
|
-
/**
|
|
7
|
-
* Type guard for the package.json object
|
|
8
|
-
*
|
|
9
|
-
* @param input Unknown input
|
|
10
|
-
* @returns true if the input is an event object
|
|
11
|
-
*/
|
|
12
|
-
export declare function isPackageJson(input: unknown): input is PackageJson;
|
|
13
|
-
export {};
|
|
14
|
-
//# sourceMappingURL=update-manifest.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"update-manifest.d.ts","sourceRoot":"","sources":["../../scripts/update-manifest.ts"],"names":[],"mappings":"AAKA,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAclE"}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
-
import { dirname, resolve } from 'node:path';
|
|
3
|
-
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import { z } from 'zod';
|
|
5
|
-
/**
|
|
6
|
-
* Type guard for the package.json object
|
|
7
|
-
*
|
|
8
|
-
* @param input Unknown input
|
|
9
|
-
* @returns true if the input is an event object
|
|
10
|
-
*/
|
|
11
|
-
export function isPackageJson(input) {
|
|
12
|
-
const event = z
|
|
13
|
-
.object({
|
|
14
|
-
name: z.string(),
|
|
15
|
-
version: z.string(),
|
|
16
|
-
description: z.string(),
|
|
17
|
-
})
|
|
18
|
-
.strict()
|
|
19
|
-
.catchall(z.any())
|
|
20
|
-
.required();
|
|
21
|
-
const { success } = event.safeParse(input);
|
|
22
|
-
return success;
|
|
23
|
-
}
|
|
24
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
25
|
-
const sourcePath = resolve(__dirname, '..', 'package.json');
|
|
26
|
-
const data = JSON.parse(readFileSync(sourcePath, 'utf8'));
|
|
27
|
-
if (!isPackageJson(data)) {
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
const { name, version, description } = data;
|
|
31
|
-
const targetPath = resolve(__dirname, '..', 'src', 'assets', 'manifest.json');
|
|
32
|
-
writeFileSync(targetPath, JSON.stringify({ name, version, description }, null, 2));
|
|
33
|
-
process.exit(0);
|
package/examples/pokeapi/type.ts
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
2
|
-
// Built with zod-codegen@1.0.1
|
|
3
|
-
// Latest edit: Thu, 13 Nov 2025 13:32:35 GMT
|
|
4
|
-
// Source file: ./samples/pokeapi-openapi.json
|
|
5
|
-
/* eslint-disable */
|
|
6
|
-
// @ts-nocheck
|
|
7
|
-
|
|
8
|
-
// Imports
|
|
9
|
-
import {z} from 'zod';
|
|
10
|
-
|
|
11
|
-
// Components schemas
|
|
12
|
-
export const NamedAPIResource = z.object({
|
|
13
|
-
name: z.string(),
|
|
14
|
-
url: z.string().url(),
|
|
15
|
-
});
|
|
16
|
-
export const PokemonType = z.object({
|
|
17
|
-
slot: z.number().int().optional(),
|
|
18
|
-
type: NamedAPIResource.optional(),
|
|
19
|
-
});
|
|
20
|
-
export const PokemonAbility = z.object({
|
|
21
|
-
ability: NamedAPIResource.optional(),
|
|
22
|
-
is_hidden: z.boolean().optional(),
|
|
23
|
-
slot: z.number().int().optional(),
|
|
24
|
-
});
|
|
25
|
-
export const PokemonSprites = z.object({
|
|
26
|
-
front_default: z.string().url().optional(),
|
|
27
|
-
front_shiny: z.string().url().optional(),
|
|
28
|
-
back_default: z.string().url().optional(),
|
|
29
|
-
back_shiny: z.string().url().optional(),
|
|
30
|
-
});
|
|
31
|
-
export const Pokemon = z.object({
|
|
32
|
-
id: z.number().int(),
|
|
33
|
-
name: z.string(),
|
|
34
|
-
height: z.number().int().optional(),
|
|
35
|
-
weight: z.number().int().optional(),
|
|
36
|
-
base_experience: z.number().int().optional(),
|
|
37
|
-
types: z.array(PokemonType).optional(),
|
|
38
|
-
abilities: z.array(PokemonAbility).optional(),
|
|
39
|
-
sprites: PokemonSprites.optional(),
|
|
40
|
-
});
|
|
41
|
-
export const PokemonListResponse = z.object({
|
|
42
|
-
count: z.number().int(),
|
|
43
|
-
next: z.string().url().optional(),
|
|
44
|
-
previous: z.string().url().optional(),
|
|
45
|
-
results: z.array(NamedAPIResource),
|
|
46
|
-
});
|
|
47
|
-
export const defaultBaseUrl = 'https://pokeapi.co/api/v2';
|
|
48
|
-
|
|
49
|
-
// Client class
|
|
50
|
-
export class PokAPI {
|
|
51
|
-
readonly #baseUrl: string;
|
|
52
|
-
constructor(baseUrl: string = defaultBaseUrl, _?: unknown) {
|
|
53
|
-
this.#baseUrl = baseUrl;
|
|
54
|
-
}
|
|
55
|
-
protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
|
|
56
|
-
return {};
|
|
57
|
-
}
|
|
58
|
-
async #makeRequest<T>(
|
|
59
|
-
method: string,
|
|
60
|
-
path: string,
|
|
61
|
-
options: _params___Record_string__string___number___boolean___data___unknown__contentType___string__headers___Record_string__string__ = {},
|
|
62
|
-
): Promise<T> {
|
|
63
|
-
const baseUrl = `${this.#baseUrl}${path}`;
|
|
64
|
-
const url =
|
|
65
|
-
options.params && Object.keys(options.params).length > 0
|
|
66
|
-
? (() => {
|
|
67
|
-
Object.entries(options.params).forEach(([key, value]) => {
|
|
68
|
-
new URL(baseUrl).searchParams.set(key, String(value));
|
|
69
|
-
});
|
|
70
|
-
return new URL(baseUrl).toString();
|
|
71
|
-
})()
|
|
72
|
-
: new URL(baseUrl).toString();
|
|
73
|
-
const baseOptions = this.getBaseRequestOptions();
|
|
74
|
-
const contentType =
|
|
75
|
-
options.contentType === 'application/x-www-form-urlencoded'
|
|
76
|
-
? 'application/x-www-form-urlencoded'
|
|
77
|
-
: 'application/json';
|
|
78
|
-
const baseHeaders = baseOptions.headers !== undefined ? baseOptions.headers : {};
|
|
79
|
-
const headers = Object.assign(
|
|
80
|
-
{},
|
|
81
|
-
baseHeaders,
|
|
82
|
-
{'Content-Type': contentType},
|
|
83
|
-
options.headers !== undefined ? options.headers : {},
|
|
84
|
-
);
|
|
85
|
-
const body =
|
|
86
|
-
options.data !== undefined
|
|
87
|
-
? options.contentType === 'application/x-www-form-urlencoded'
|
|
88
|
-
? (() => {
|
|
89
|
-
const params = new URLSearchParams();
|
|
90
|
-
Object.entries(options.data).forEach(([key, value]) => {
|
|
91
|
-
params.set(key, String(value));
|
|
92
|
-
});
|
|
93
|
-
return params.toString();
|
|
94
|
-
})()
|
|
95
|
-
: JSON.stringify(options.data)
|
|
96
|
-
: null;
|
|
97
|
-
const response = await fetch(url, Object.assign({}, baseOptions, {method, headers: headers, body: body}));
|
|
98
|
-
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
99
|
-
return await response.json();
|
|
100
|
-
}
|
|
101
|
-
async getPokemonById(id: string): Promise<Pokemon> {
|
|
102
|
-
return Pokemon.parse(await this.#makeRequest('GET', `/pokemon/${id}`, {}));
|
|
103
|
-
}
|
|
104
|
-
async getPokemonList(limit?: number, offset?: number): Promise<PokemonListResponse> {
|
|
105
|
-
return PokemonListResponse.parse(
|
|
106
|
-
await this.#makeRequest('GET', '/pokemon', {params: {limit: limit, offset: offset}}),
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
}
|