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.
Files changed (119) hide show
  1. package/.github/workflows/ci.yml +50 -48
  2. package/.github/workflows/release.yml +13 -3
  3. package/.husky/commit-msg +1 -1
  4. package/.husky/pre-commit +1 -1
  5. package/.lintstagedrc.json +5 -1
  6. package/.nvmrc +1 -1
  7. package/.prettierrc.json +12 -5
  8. package/CHANGELOG.md +11 -0
  9. package/CONTRIBUTING.md +12 -12
  10. package/EXAMPLES.md +135 -57
  11. package/PERFORMANCE.md +4 -4
  12. package/README.md +87 -64
  13. package/SECURITY.md +1 -1
  14. package/dist/src/cli.js +11 -18
  15. package/dist/src/generator.d.ts +2 -2
  16. package/dist/src/generator.d.ts.map +1 -1
  17. package/dist/src/generator.js +5 -3
  18. package/dist/src/interfaces/code-generator.d.ts.map +1 -1
  19. package/dist/src/services/code-generator.service.d.ts +3 -1
  20. package/dist/src/services/code-generator.service.d.ts.map +1 -1
  21. package/dist/src/services/code-generator.service.js +236 -219
  22. package/dist/src/services/file-reader.service.d.ts.map +1 -1
  23. package/dist/src/services/file-reader.service.js +1 -1
  24. package/dist/src/services/file-writer.service.d.ts.map +1 -1
  25. package/dist/src/services/file-writer.service.js +2 -2
  26. package/dist/src/services/import-builder.service.d.ts.map +1 -1
  27. package/dist/src/services/import-builder.service.js +3 -3
  28. package/dist/src/services/type-builder.service.d.ts.map +1 -1
  29. package/dist/src/types/generator-options.d.ts.map +1 -1
  30. package/dist/src/types/openapi.d.ts.map +1 -1
  31. package/dist/src/types/openapi.js +20 -20
  32. package/dist/src/utils/error-handler.d.ts.map +1 -1
  33. package/dist/src/utils/naming-convention.d.ts.map +1 -1
  34. package/dist/src/utils/naming-convention.js +6 -3
  35. package/dist/src/utils/signal-handler.d.ts.map +1 -1
  36. package/dist/tests/integration/cli-comprehensive.test.d.ts +2 -0
  37. package/dist/tests/integration/cli-comprehensive.test.d.ts.map +1 -0
  38. package/dist/tests/integration/cli-comprehensive.test.js +110 -0
  39. package/dist/tests/integration/cli.test.d.ts +2 -0
  40. package/dist/tests/integration/cli.test.d.ts.map +1 -0
  41. package/dist/tests/integration/cli.test.js +25 -0
  42. package/dist/tests/integration/error-scenarios.test.d.ts +2 -0
  43. package/dist/tests/integration/error-scenarios.test.d.ts.map +1 -0
  44. package/dist/tests/integration/error-scenarios.test.js +169 -0
  45. package/dist/tests/integration/snapshots.test.d.ts +2 -0
  46. package/dist/tests/integration/snapshots.test.d.ts.map +1 -0
  47. package/dist/tests/integration/snapshots.test.js +100 -0
  48. package/dist/tests/unit/code-generator-edge-cases.test.d.ts +2 -0
  49. package/dist/tests/unit/code-generator-edge-cases.test.d.ts.map +1 -0
  50. package/dist/tests/unit/code-generator-edge-cases.test.js +506 -0
  51. package/dist/tests/unit/code-generator.test.d.ts +2 -0
  52. package/dist/tests/unit/code-generator.test.d.ts.map +1 -0
  53. package/dist/tests/unit/code-generator.test.js +1364 -0
  54. package/dist/tests/unit/file-reader.test.d.ts +2 -0
  55. package/dist/tests/unit/file-reader.test.d.ts.map +1 -0
  56. package/dist/tests/unit/file-reader.test.js +125 -0
  57. package/dist/tests/unit/generator.test.d.ts +2 -0
  58. package/dist/tests/unit/generator.test.d.ts.map +1 -0
  59. package/dist/tests/unit/generator.test.js +119 -0
  60. package/dist/tests/unit/naming-convention.test.d.ts +2 -0
  61. package/dist/tests/unit/naming-convention.test.d.ts.map +1 -0
  62. package/dist/tests/unit/naming-convention.test.js +256 -0
  63. package/dist/tests/unit/reporter.test.d.ts +2 -0
  64. package/dist/tests/unit/reporter.test.d.ts.map +1 -0
  65. package/dist/tests/unit/reporter.test.js +44 -0
  66. package/dist/tests/unit/type-builder.test.d.ts +2 -0
  67. package/dist/tests/unit/type-builder.test.d.ts.map +1 -0
  68. package/dist/tests/unit/type-builder.test.js +108 -0
  69. package/dist/vitest.config.d.ts.map +1 -1
  70. package/dist/vitest.config.js +10 -20
  71. package/eslint.config.mjs +38 -28
  72. package/examples/.gitkeep +1 -1
  73. package/examples/README.md +4 -2
  74. package/examples/petstore/README.md +18 -17
  75. package/examples/petstore/{type.ts → api.ts} +158 -74
  76. package/examples/petstore/authenticated-usage.ts +6 -4
  77. package/examples/petstore/basic-usage.ts +4 -3
  78. package/examples/petstore/error-handling-usage.ts +84 -0
  79. package/examples/petstore/retry-handler-usage.ts +11 -18
  80. package/examples/petstore/server-variables-usage.ts +10 -10
  81. package/examples/pokeapi/README.md +8 -8
  82. package/examples/pokeapi/api.ts +218 -0
  83. package/examples/pokeapi/basic-usage.ts +3 -2
  84. package/examples/pokeapi/custom-client.ts +5 -4
  85. package/package.json +17 -21
  86. package/src/cli.ts +20 -25
  87. package/src/generator.ts +13 -11
  88. package/src/interfaces/code-generator.ts +1 -1
  89. package/src/services/code-generator.service.ts +799 -1120
  90. package/src/services/file-reader.service.ts +6 -5
  91. package/src/services/file-writer.service.ts +7 -7
  92. package/src/services/import-builder.service.ts +9 -13
  93. package/src/services/type-builder.service.ts +8 -19
  94. package/src/types/generator-options.ts +1 -1
  95. package/src/types/openapi.ts +22 -22
  96. package/src/utils/error-handler.ts +2 -2
  97. package/src/utils/naming-convention.ts +13 -10
  98. package/src/utils/reporter.ts +2 -2
  99. package/src/utils/signal-handler.ts +7 -8
  100. package/tests/integration/cli-comprehensive.test.ts +38 -32
  101. package/tests/integration/cli.test.ts +5 -5
  102. package/tests/integration/error-scenarios.test.ts +20 -26
  103. package/tests/integration/snapshots.test.ts +19 -23
  104. package/tests/unit/code-generator-edge-cases.test.ts +133 -133
  105. package/tests/unit/code-generator.test.ts +431 -330
  106. package/tests/unit/file-reader.test.ts +14 -14
  107. package/tests/unit/generator.test.ts +30 -18
  108. package/tests/unit/naming-convention.test.ts +27 -27
  109. package/tests/unit/type-builder.test.ts +2 -2
  110. package/tsconfig.json +5 -3
  111. package/vitest.config.ts +11 -21
  112. package/dist/scripts/update-manifest.d.ts +0 -14
  113. package/dist/scripts/update-manifest.d.ts.map +0 -1
  114. package/dist/scripts/update-manifest.js +0 -33
  115. package/dist/src/assets/manifest.json +0 -5
  116. package/examples/pokeapi/type.ts +0 -109
  117. package/generated/type.ts +0 -371
  118. package/scripts/update-manifest.ts +0 -49
  119. package/src/assets/manifest.json +0 -5
@@ -0,0 +1,108 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import { TypeScriptTypeBuilderService } from '../../src/services/type-builder.service';
3
+ import * as ts from 'typescript';
4
+ describe('TypeScriptTypeBuilderService', () => {
5
+ let builder;
6
+ beforeEach(() => {
7
+ builder = new TypeScriptTypeBuilderService();
8
+ });
9
+ describe('buildType', () => {
10
+ it('should build string type', () => {
11
+ const typeNode = builder.buildType('string');
12
+ expect(typeNode.kind).toBe(ts.SyntaxKind.StringKeyword);
13
+ });
14
+ it('should build number type', () => {
15
+ const typeNode = builder.buildType('number');
16
+ expect(typeNode.kind).toBe(ts.SyntaxKind.NumberKeyword);
17
+ });
18
+ it('should build boolean type', () => {
19
+ const typeNode = builder.buildType('boolean');
20
+ expect(typeNode.kind).toBe(ts.SyntaxKind.BooleanKeyword);
21
+ });
22
+ it('should build unknown type', () => {
23
+ const typeNode = builder.buildType('unknown');
24
+ expect(typeNode.kind).toBe(ts.SyntaxKind.UnknownKeyword);
25
+ });
26
+ it('should build array type', () => {
27
+ const typeNode = builder.buildType('string[]');
28
+ expect(typeNode.kind).toBe(ts.SyntaxKind.ArrayType);
29
+ });
30
+ it('should build Record type (returns unknown)', () => {
31
+ const typeNode = builder.buildType('Record<string, number>');
32
+ expect(typeNode.kind).toBe(ts.SyntaxKind.UnknownKeyword);
33
+ });
34
+ it('should build custom type reference', () => {
35
+ const typeNode = builder.buildType('CustomType');
36
+ expect(typeNode.kind).toBe(ts.SyntaxKind.TypeReference);
37
+ });
38
+ });
39
+ describe('createProperty', () => {
40
+ it('should create property with readonly modifier', () => {
41
+ const property = builder.createProperty('name', 'string', true);
42
+ expect(property.modifiers).toBeDefined();
43
+ expect(property.modifiers?.some((m) => m.kind === ts.SyntaxKind.ReadonlyKeyword)).toBe(true);
44
+ });
45
+ it('should create property without readonly modifier', () => {
46
+ const property = builder.createProperty('name', 'string', false);
47
+ const hasReadonly = property.modifiers?.some((m) => m.kind === ts.SyntaxKind.ReadonlyKeyword) ?? false;
48
+ expect(hasReadonly).toBe(false);
49
+ });
50
+ it('should handle private identifier (#)', () => {
51
+ const property = builder.createProperty('#private', 'string');
52
+ expect(property.name).toBeDefined();
53
+ });
54
+ });
55
+ describe('createParameter', () => {
56
+ it('should create optional parameter', () => {
57
+ const param = builder.createParameter('name', 'string', undefined, true);
58
+ expect(param.questionToken).toBeDefined();
59
+ });
60
+ it('should create required parameter', () => {
61
+ const param = builder.createParameter('name', 'string', undefined, false);
62
+ expect(param.questionToken).toBeUndefined();
63
+ });
64
+ it('should create parameter with TypeNode type', () => {
65
+ const typeNode = ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
66
+ const param = builder.createParameter('name', typeNode);
67
+ expect(param.type).toBe(typeNode);
68
+ });
69
+ });
70
+ describe('sanitizeIdentifier', () => {
71
+ it('should sanitize identifier with special characters', () => {
72
+ const sanitized = builder.sanitizeIdentifier('test-name@123');
73
+ expect(sanitized).toBe('test_name_123');
74
+ });
75
+ it('should add prefix for identifiers starting with number', () => {
76
+ const sanitized = builder.sanitizeIdentifier('123test');
77
+ expect(sanitized).toBe('_123test');
78
+ });
79
+ it('should handle empty string', () => {
80
+ const sanitized = builder.sanitizeIdentifier('');
81
+ expect(sanitized).toBe('_');
82
+ });
83
+ });
84
+ describe('toCamelCase', () => {
85
+ it('should convert first letter to lowercase', () => {
86
+ const result = builder.toCamelCase('Test');
87
+ expect(result).toBe('test');
88
+ });
89
+ it('should handle single character', () => {
90
+ const result = builder.toCamelCase('T');
91
+ expect(result).toBe('t');
92
+ });
93
+ it('should handle empty string', () => {
94
+ const result = builder.toCamelCase('');
95
+ expect(result).toBe('');
96
+ });
97
+ });
98
+ describe('toPascalCase', () => {
99
+ it('should convert first letter to uppercase', () => {
100
+ const result = builder.toPascalCase('test');
101
+ expect(result).toBe('Test');
102
+ });
103
+ it('should handle single character', () => {
104
+ const result = builder.toPascalCase('t');
105
+ expect(result).toBe('T');
106
+ });
107
+ });
108
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"vitest.config.d.ts","sourceRoot":"","sources":["../vitest.config.ts"],"names":[],"mappings":";AAGA,wBAmCG"}
1
+ {"version":3,"file":"vitest.config.d.ts","sourceRoot":"","sources":["../vitest.config.ts"],"names":[],"mappings":";AAGA,wBAyBG"}
@@ -1,38 +1,28 @@
1
- import { defineConfig } from 'vitest/config';
2
1
  import { resolve } from 'node:path';
2
+ import { defineConfig } from 'vitest/config';
3
3
  export default defineConfig({
4
4
  test: {
5
5
  globals: true,
6
6
  environment: 'node',
7
- include: ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
7
+ include: ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts}'],
8
8
  exclude: ['node_modules', 'dist', 'coverage'],
9
9
  coverage: {
10
10
  provider: 'v8',
11
11
  reporter: ['text', 'json', 'html', 'lcov'],
12
- exclude: [
13
- 'coverage/**',
14
- 'dist/**',
15
- 'node_modules/**',
16
- '**/*.d.ts',
17
- '**/*.config.*',
18
- '**/*.test.*',
19
- '**/*.spec.*',
20
- 'scripts/**',
21
- 'samples/**',
22
- ],
12
+ exclude: ['coverage/**', 'dist/**', 'node_modules/**', '**/*.d.ts', '**/*.config.*', '**/*.test.*', '**/*.spec.*', 'scripts/**', 'samples/**'],
23
13
  thresholds: {
24
14
  global: {
25
15
  branches: 80,
26
16
  functions: 80,
27
17
  lines: 80,
28
- statements: 80,
29
- },
30
- },
31
- },
18
+ statements: 80
19
+ }
20
+ }
21
+ }
32
22
  },
33
23
  resolve: {
34
24
  alias: {
35
- '@': resolve(__dirname, 'src'),
36
- },
37
- },
25
+ '@': resolve(__dirname, 'src')
26
+ }
27
+ }
38
28
  });
package/eslint.config.mjs CHANGED
@@ -1,52 +1,62 @@
1
1
  import eslint from '@eslint/js';
2
+ import eslintConfigPrettier from 'eslint-config-prettier';
2
3
  import { defineConfig } from 'eslint/config';
3
4
  import tseslint from 'typescript-eslint';
4
5
 
6
+ // Default ignores for consuming projects
7
+ const defaultIgnores = ['**/*.mjs', '**/dist/**', '**/coverage/**'];
8
+
5
9
  export default defineConfig([
6
10
  {
7
- ignores: [
8
- '**/*.mjs',
9
- 'dist/**/*',
10
- 'node_modules/**/*',
11
- '**/*.test*.ts',
12
- '**/coverage/*',
13
- 'examples/**/*.ts',
14
- ],
11
+ ignores: defaultIgnores
15
12
  },
16
13
  eslint.configs.recommended,
17
- tseslint.configs.strictTypeChecked,
18
- tseslint.configs.stylisticTypeChecked,
14
+ ...tseslint.configs.strictTypeChecked,
15
+ ...tseslint.configs.stylisticTypeChecked,
16
+ eslintConfigPrettier,
17
+ {
18
+ files: ['examples/**/*.ts', 'tests/**/*.ts'],
19
+ extends: [tseslint.configs.disableTypeChecked],
20
+ rules: {
21
+ '@typescript-eslint/no-explicit-any': 'off',
22
+ '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }]
23
+ }
24
+ },
19
25
  {
20
26
  languageOptions: {
21
27
  parserOptions: {
22
28
  projectService: true,
23
- },
29
+ tsconfigRootDir: import.meta.dirname
30
+ }
24
31
  },
25
32
  rules: {
33
+ '@typescript-eslint/no-deprecated': 'warn',
34
+ '@typescript-eslint/no-unused-vars': [
35
+ 'error',
36
+ {
37
+ argsIgnorePattern: '^_',
38
+ varsIgnorePattern: '^_'
39
+ }
40
+ ],
41
+ 'brace-style': ['error', '1tbs', { allowSingleLine: false }],
42
+ 'curly': ['error', 'all'],
26
43
  'no-var': 'error',
44
+ 'padding-line-between-statements': ['error', { blankLine: 'always', prev: 'if', next: '*' }],
27
45
  'prefer-const': [
28
46
  'error',
29
47
  {
30
48
  destructuring: 'all',
31
- ignoreReadBeforeAssign: false,
32
- },
49
+ ignoreReadBeforeAssign: false
50
+ }
33
51
  ],
34
- quotes: ['error', 'single', { avoidEscape: true }],
52
+ 'quotes': ['error', 'single', { avoidEscape: true }],
35
53
  'sort-imports': [
36
54
  'error',
37
55
  {
38
56
  ignoreCase: true,
39
- ignoreDeclarationSort: true,
40
- },
41
- ],
42
- '@typescript-eslint/no-deprecated': 'warn',
43
- '@typescript-eslint/no-unused-vars': [
44
- 'error',
45
- {
46
- argsIgnorePattern: '^_',
47
- varsIgnorePattern: '^_',
48
- },
49
- ],
50
- },
51
- },
52
- ]);
57
+ ignoreDeclarationSort: true
58
+ }
59
+ ]
60
+ }
61
+ }
62
+ ]);
package/examples/.gitkeep CHANGED
@@ -1,3 +1,3 @@
1
1
  # This file ensures the examples directory is tracked by git
2
- # Generated type.ts files are ignored via .gitignore
2
+ # Generated api.ts files are ignored via .gitignore
3
3
 
@@ -26,12 +26,14 @@ npx ts-node examples/petstore/basic-usage.ts
26
26
  npx ts-node examples/petstore/authenticated-usage.ts
27
27
  npx ts-node examples/petstore/server-variables-usage.ts
28
28
  npx ts-node examples/petstore/retry-handler-usage.ts
29
+ npx ts-node examples/petstore/error-handling-usage.ts
29
30
  ```
30
31
 
31
32
  **Additional examples:**
32
33
 
33
34
  - `server-variables-usage.ts` - Using server variables for different environments
34
35
  - `retry-handler-usage.ts` - Custom retry handler implementation
36
+ - `error-handling-usage.ts` - Handling 4xx/5xx with custom HttpError
35
37
 
36
38
  ### ⚡ [PokéAPI](./pokeapi/)
37
39
 
@@ -53,7 +55,7 @@ zod-codegen --input ./samples/pokeapi-openapi.json --output ./examples/pokeapi
53
55
 
54
56
  Each example directory contains:
55
57
 
56
- - `type.ts` - Generated client and schemas (created by zod-codegen)
58
+ - `api.ts` - Generated client and schemas (created by zod-codegen)
57
59
  - `README.md` - Example-specific documentation
58
60
  - `basic-usage.ts` - Basic usage examples
59
61
  - `authenticated-usage.ts` - Authentication examples (if applicable)
@@ -62,7 +64,7 @@ Each example directory contains:
62
64
 
63
65
  1. **Choose an example** that matches your use case
64
66
  2. **Generate the client** using the command shown in the example's README
65
- 3. **Review the generated code** in `type.ts`
67
+ 3. **Review the generated code** in `api.ts`
66
68
  4. **Run the example scripts** to see it in action
67
69
  5. **Extend the client** using patterns from [EXAMPLES.md](../EXAMPLES.md)
68
70
 
@@ -6,12 +6,12 @@ This example demonstrates how to use `zod-codegen` with the Swagger Petstore Ope
6
6
 
7
7
  After running `zod-codegen`, you'll get:
8
8
 
9
- - `type.ts` - Contains all Zod schemas and the generated API client
9
+ - `api.ts` - Contains all Zod schemas and the generated API client
10
10
 
11
11
  ## Basic Usage
12
12
 
13
13
  ```typescript
14
- import {SwaggerPetstoreOpenAPI30} from './type';
14
+ import SwaggerPetstoreOpenAPI30 from './api';
15
15
 
16
16
  // Create a client instance using default server from OpenAPI spec
17
17
  const client = new SwaggerPetstoreOpenAPI30({});
@@ -26,19 +26,20 @@ console.log('Available pets:', pets);
26
26
  The generated client supports flexible server configuration:
27
27
 
28
28
  ```typescript
29
- import {SwaggerPetstoreOpenAPI30, ClientOptions} from './type';
29
+ import SwaggerPetstoreOpenAPI30 from './api';
30
+ import type { ClientOptions } from './api';
30
31
 
31
32
  // Option 1: Use default server (first server from OpenAPI spec)
32
33
  const defaultClient = new SwaggerPetstoreOpenAPI30({});
33
34
 
34
35
  // Option 2: Override with custom base URL
35
36
  const customClient = new SwaggerPetstoreOpenAPI30({
36
- baseUrl: 'https://custom-api.example.com/v3',
37
+ baseUrl: 'https://custom-api.example.com/v3'
37
38
  });
38
39
 
39
40
  // Option 3: Select server by index (if multiple servers exist)
40
41
  const indexedClient = new SwaggerPetstoreOpenAPI30({
41
- serverIndex: 0,
42
+ serverIndex: 0
42
43
  });
43
44
 
44
45
  // Option 4: Use server variables (if your OpenAPI spec has templated URLs)
@@ -48,15 +49,15 @@ const variableClient = new SwaggerPetstoreOpenAPI30({
48
49
  serverVariables: {
49
50
  environment: 'api.staging',
50
51
  port: '8443',
51
- version: '2',
52
- },
52
+ version: '2'
53
+ }
53
54
  });
54
55
  ```
55
56
 
56
57
  ## Example: Finding Pets
57
58
 
58
59
  ```typescript
59
- import {SwaggerPetstoreOpenAPI30} from './type';
60
+ import SwaggerPetstoreOpenAPI30 from './api';
60
61
 
61
62
  async function findAvailablePets() {
62
63
  const client = new SwaggerPetstoreOpenAPI30({});
@@ -84,25 +85,25 @@ findAvailablePets();
84
85
  ## Example: Adding a Pet
85
86
 
86
87
  ```typescript
87
- import {SwaggerPetstoreOpenAPI30, Pet, PetStatus} from './type';
88
- import {z} from 'zod';
88
+ import SwaggerPetstoreOpenAPI30 from './api';
89
+ import type { Pet } from './api';
89
90
 
90
91
  async function addNewPet() {
91
92
  const client = new SwaggerPetstoreOpenAPI30({});
92
93
 
93
- const newPet: z.infer<typeof Pet> = {
94
+ const newPet: Pet = {
94
95
  id: 12345,
95
96
  name: 'Fluffy',
96
- status: PetStatus.enum.available,
97
+ status: 'available',
97
98
  category: {
98
99
  id: 1,
99
- name: 'Dogs',
100
+ name: 'Dogs'
100
101
  },
101
102
  photoUrls: ['https://example.com/fluffy.jpg'],
102
103
  tags: [
103
- {id: 1, name: 'friendly'},
104
- {id: 2, name: 'cute'},
105
- ],
104
+ { id: 1, name: 'friendly' },
105
+ { id: 2, name: 'cute' }
106
+ ]
106
107
  };
107
108
 
108
109
  try {
@@ -118,4 +119,4 @@ addNewPet();
118
119
 
119
120
  ## Example: Extending the Client
120
121
 
121
- See the main [EXAMPLES.md](../../EXAMPLES.md) for comprehensive examples on extending the client for authentication, CORS, and custom headers.
122
+ See the main [EXAMPLES.md](../../EXAMPLES.md) for comprehensive examples on extending the client for authentication, CORS, custom headers, and response handling (including [4xx/5xx error handling](../../EXAMPLES.md#handling-4xx5xx-in-handleresponse)).