zod-codegen 1.5.1 → 1.6.2

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 (100) hide show
  1. package/.github/workflows/ci.yml +7 -1
  2. package/.github/workflows/release.yml +3 -3
  3. package/CHANGELOG.md +34 -0
  4. package/CONTRIBUTING.md +1 -1
  5. package/EXAMPLES.md +91 -12
  6. package/README.md +11 -4
  7. package/dist/scripts/add-js-extensions.d.ts +2 -0
  8. package/dist/scripts/add-js-extensions.d.ts.map +1 -0
  9. package/dist/scripts/add-js-extensions.js +66 -0
  10. package/dist/scripts/update-manifest.d.ts +14 -0
  11. package/dist/scripts/update-manifest.d.ts.map +1 -0
  12. package/dist/scripts/update-manifest.js +33 -0
  13. package/dist/src/assets/manifest.json +1 -1
  14. package/dist/src/cli.js +3 -3
  15. package/dist/src/generator.d.ts +46 -4
  16. package/dist/src/generator.d.ts.map +1 -1
  17. package/dist/src/generator.js +43 -1
  18. package/dist/src/interfaces/code-generator.d.ts +1 -1
  19. package/dist/src/interfaces/code-generator.d.ts.map +1 -1
  20. package/dist/src/services/code-generator.service.d.ts +5 -3
  21. package/dist/src/services/code-generator.service.d.ts.map +1 -1
  22. package/dist/src/services/code-generator.service.js +86 -20
  23. package/dist/src/services/file-reader.service.d.ts +2 -2
  24. package/dist/src/services/file-reader.service.d.ts.map +1 -1
  25. package/dist/src/services/file-writer.service.d.ts +1 -1
  26. package/dist/src/services/file-writer.service.d.ts.map +1 -1
  27. package/dist/src/services/import-builder.service.d.ts +1 -1
  28. package/dist/src/services/import-builder.service.d.ts.map +1 -1
  29. package/dist/src/services/type-builder.service.d.ts +1 -1
  30. package/dist/src/services/type-builder.service.d.ts.map +1 -1
  31. package/dist/src/types/generator-options.d.ts +1 -1
  32. package/dist/src/types/generator-options.d.ts.map +1 -1
  33. package/dist/src/utils/error-handler.d.ts +3 -2
  34. package/dist/src/utils/error-handler.d.ts.map +1 -1
  35. package/dist/src/utils/error-handler.js +4 -4
  36. package/dist/src/utils/reporter.d.ts +3 -2
  37. package/dist/src/utils/reporter.d.ts.map +1 -1
  38. package/dist/src/utils/reporter.js +7 -5
  39. package/dist/src/utils/signal-handler.d.ts +3 -2
  40. package/dist/src/utils/signal-handler.d.ts.map +1 -1
  41. package/dist/src/utils/signal-handler.js +4 -4
  42. package/examples/README.md +10 -1
  43. package/examples/petstore/README.md +6 -6
  44. package/examples/petstore/authenticated-usage.ts +1 -1
  45. package/examples/petstore/basic-usage.ts +1 -1
  46. package/examples/petstore/retry-handler-usage.ts +173 -0
  47. package/examples/petstore/server-variables-usage.ts +1 -1
  48. package/examples/petstore/type.ts +76 -53
  49. package/examples/pokeapi/README.md +3 -3
  50. package/examples/pokeapi/basic-usage.ts +1 -1
  51. package/examples/pokeapi/custom-client.ts +1 -1
  52. package/generated/type.ts +326 -0
  53. package/package.json +14 -19
  54. package/scripts/add-js-extensions.ts +79 -0
  55. package/scripts/update-manifest.ts +4 -2
  56. package/src/assets/manifest.json +1 -1
  57. package/src/cli.ts +7 -7
  58. package/src/generator.ts +51 -9
  59. package/src/interfaces/code-generator.ts +1 -1
  60. package/src/services/code-generator.service.ts +256 -122
  61. package/src/services/file-reader.service.ts +3 -3
  62. package/src/services/file-writer.service.ts +1 -1
  63. package/src/services/import-builder.service.ts +1 -1
  64. package/src/services/type-builder.service.ts +1 -1
  65. package/src/types/generator-options.ts +1 -1
  66. package/src/utils/error-handler.ts +6 -5
  67. package/src/utils/reporter.ts +6 -3
  68. package/src/utils/signal-handler.ts +10 -8
  69. package/tests/integration/cli-comprehensive.test.ts +123 -0
  70. package/tests/integration/cli.test.ts +2 -2
  71. package/tests/integration/error-scenarios.test.ts +240 -0
  72. package/tests/integration/snapshots.test.ts +131 -0
  73. package/tests/unit/code-generator-edge-cases.test.ts +551 -0
  74. package/tests/unit/code-generator.test.ts +385 -2
  75. package/tests/unit/file-reader.test.ts +16 -1
  76. package/tests/unit/generator.test.ts +19 -2
  77. package/tests/unit/naming-convention.test.ts +30 -1
  78. package/tests/unit/reporter.test.ts +63 -0
  79. package/tests/unit/type-builder.test.ts +131 -0
  80. package/tsconfig.json +3 -3
  81. package/dist/src/http/fetch-client.d.ts +0 -15
  82. package/dist/src/http/fetch-client.d.ts.map +0 -1
  83. package/dist/src/http/fetch-client.js +0 -140
  84. package/dist/src/polyfills/fetch.d.ts +0 -5
  85. package/dist/src/polyfills/fetch.d.ts.map +0 -1
  86. package/dist/src/polyfills/fetch.js +0 -18
  87. package/dist/src/types/http.d.ts +0 -25
  88. package/dist/src/types/http.d.ts.map +0 -1
  89. package/dist/src/types/http.js +0 -10
  90. package/dist/src/utils/manifest.d.ts +0 -8
  91. package/dist/src/utils/manifest.d.ts.map +0 -1
  92. package/dist/src/utils/manifest.js +0 -9
  93. package/dist/src/utils/tty.d.ts +0 -2
  94. package/dist/src/utils/tty.d.ts.map +0 -1
  95. package/dist/src/utils/tty.js +0 -3
  96. package/src/http/fetch-client.ts +0 -181
  97. package/src/polyfills/fetch.ts +0 -26
  98. package/src/types/http.ts +0 -35
  99. package/src/utils/manifest.ts +0 -17
  100. package/src/utils/tty.ts +0 -3
@@ -0,0 +1,326 @@
1
+ // THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2
+ // Built with zod-codegen@1.6.2
3
+ // Latest edit: Mon, 19 Jan 2026 15:14:43 GMT
4
+ // Source file: ./samples/swagger-petstore.yaml
5
+ /* eslint-disable */
6
+ // @ts-nocheck
7
+
8
+
9
+ // Imports
10
+ ;
11
+ import { z } from 'zod';
12
+
13
+ // Components schemas
14
+ ;
15
+ export const Order = z.object({
16
+ id: z.number().int().optional(),
17
+ petId: z.number().int().optional(),
18
+ quantity: z.number().int().optional(),
19
+ shipDate: z.iso.datetime({
20
+ local: true
21
+ }).optional(),
22
+ status: z.enum(['placed', 'approved', 'delivered']).optional(),
23
+ complete: z.boolean().optional()
24
+ });
25
+ export const Address = z.object({
26
+ street: z.string().optional(),
27
+ city: z.string().optional(),
28
+ state: z.string().optional(),
29
+ zip: z.string().optional()
30
+ });
31
+ export const Customer = z.object({
32
+ id: z.number().int().optional(),
33
+ username: z.string().optional(),
34
+ address: z.array(Address).optional()
35
+ });
36
+ export const Category = z.object({
37
+ id: z.number().int().optional(),
38
+ name: z.string().optional()
39
+ });
40
+ export const User = z.object({
41
+ id: z.number().int().optional(),
42
+ username: z.string().optional(),
43
+ firstName: z.string().optional(),
44
+ lastName: z.string().optional(),
45
+ email: z.string().optional(),
46
+ password: z.string().optional(),
47
+ phone: z.string().optional(),
48
+ userStatus: z.number().int().optional()
49
+ });
50
+ export const Tag = z.object({
51
+ id: z.number().int().optional(),
52
+ name: z.string().optional()
53
+ });
54
+ export const Pet = z.object({
55
+ id: z.number().int().optional(),
56
+ name: z.string(),
57
+ category: Category.optional(),
58
+ photoUrls: z.array(z.string()),
59
+ tags: z.array(Tag).optional(),
60
+ status: z.enum(['available', 'pending', 'sold']).optional()
61
+ });
62
+ export const ApiResponse = z.object({
63
+ code: z.number().int().optional(),
64
+ type: z.string().optional(),
65
+ message: z.string().optional()
66
+ });
67
+ export type Order = z.infer<typeof Order>;
68
+ export type Address = z.infer<typeof Address>;
69
+ export type Customer = z.infer<typeof Customer>;
70
+ export type Category = z.infer<typeof Category>;
71
+ export type User = z.infer<typeof User>;
72
+ export type Tag = z.infer<typeof Tag>;
73
+ export type Pet = z.infer<typeof Pet>;
74
+ export type ApiResponse = z.infer<typeof ApiResponse>;
75
+ export const serverConfigurations = [{
76
+ url: 'https://petstore3.swagger.io/api/v3'
77
+ }];
78
+ export const defaultBaseUrl = 'https://petstore3.swagger.io/api/v3';
79
+ export type ClientOptions = {
80
+ baseUrl?: string;
81
+ serverIndex?: number;
82
+ serverVariables?: Record<string, string>;
83
+ };
84
+ function resolveServerUrl(serverIndex?: number | undefined, serverVariables?: Record<string, string> = {}): string {
85
+ const configs = [{
86
+ url: 'https://petstore3.swagger.io/api/v3'
87
+ }];
88
+ const idx = serverIndex ?? 0;
89
+ if (idx < configs.length) {
90
+ const config = configs[idx];
91
+ let url = config.url;
92
+ if (config.variables && serverVariables) {
93
+ for (const [key, value] of Object.entries(serverVariables)) {
94
+ url = url.replace(new RegExp("\\{" + key + "\\}", "g"), value);
95
+ }
96
+ }
97
+ return url;
98
+ }
99
+ return 'https://petstore3.swagger.io/api/v3';
100
+ }
101
+
102
+ // Client class
103
+ ;
104
+ export default class SwaggerPetstoreOpenAPI30 {
105
+ readonly #baseUrl: string;
106
+ constructor(options: ClientOptions) {
107
+ const resolvedUrl = options.baseUrl !== null ? options.baseUrl : resolveServerUrl(options.serverIndex, options.serverVariables);
108
+ this.#baseUrl = resolvedUrl;
109
+ }
110
+ protected getBaseRequestOptions(): Partial<Omit<RequestInit, 'method' | 'body'>> {
111
+ return {};
112
+ }
113
+ protected async handleResponse<T>(response: Response, method: string, path: string, options: _params___Record_string__string___number___boolean___data___unknown__contentType___string__headers___Record_string__string__): Promise<Response> {
114
+ return response;
115
+ }
116
+ protected async makeRequest<T>(method: string, path: string, options: _params___Record_string__string___number___boolean___data___unknown__contentType___string__headers___Record_string__string__ = {}): Promise<T> {
117
+ const baseUrl = new URL(path, this.#baseUrl);
118
+ const url = options.params && Object.keys(options.params).length > 0 ? (() => {
119
+ Object.entries(options.params).filter(([, value]) => value !== undefined).forEach(([key, value]) => { baseUrl.searchParams.set(key, String(value)); });
120
+ return baseUrl.toString();
121
+ })() : baseUrl.toString();
122
+ const baseOptions = this.getBaseRequestOptions();
123
+ const contentType = options.contentType === 'application/x-www-form-urlencoded' ? 'application/x-www-form-urlencoded' : 'application/json';
124
+ const baseHeaders = baseOptions.headers !== undefined ? baseOptions.headers : {};
125
+ const headers = Object.assign({}, baseHeaders, { 'Content-Type': contentType }, options.headers !== undefined ? options.headers : {});
126
+ const body = options.data !== undefined ? options.contentType === 'application/x-www-form-urlencoded' ? (() => { const params = new URLSearchParams(); Object.entries(options.data).forEach(([key, value]) => { params.set(key, String(value)); }); return params.toString(); })() : JSON.stringify(options.data) : null;
127
+ const rawResponse = await fetch(url, Object.assign({}, baseOptions, { method, headers: headers, body: body }));
128
+ const response = await this.handleResponse<T>(rawResponse, method, path, options);
129
+ if (!response.ok)
130
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
131
+ return await response.json();
132
+ }
133
+ /**
134
+ * Add a new pet to the store
135
+ * @param body Create a new pet in the store
136
+ * @returns {Pet}
137
+ */
138
+ async addPet(body: Pet): Promise<Pet> {
139
+ return Pet.parse(await this.makeRequest('POST', '/pet', { data: body, contentType: 'application/x-www-form-urlencoded' }));
140
+ }
141
+ /**
142
+ * Update an existing pet
143
+ *
144
+ * Update an existing pet by Id
145
+ * @param body Update an existent pet in the store
146
+ * @returns {Pet}
147
+ */
148
+ async updatePet(body: Pet): Promise<Pet> {
149
+ return Pet.parse(await this.makeRequest('PUT', '/pet', { data: body, contentType: 'application/x-www-form-urlencoded' }));
150
+ }
151
+ /**
152
+ * Finds Pets by status
153
+ *
154
+ * Multiple status values can be provided with comma separated strings
155
+ *
156
+ * @param status Status values that need to be considered for filter
157
+ * @returns {Pet[]}
158
+ */
159
+ async findPetsByStatus(status?: string): Promise<Pet[]> {
160
+ return await this.makeRequest('GET', '/pet/findByStatus', { params: { 'status': status } });
161
+ }
162
+ /**
163
+ * Finds Pets by tags
164
+ *
165
+ * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
166
+ *
167
+ * @param tags Tags to filter by
168
+ * @returns {Pet[]}
169
+ */
170
+ async findPetsByTags(tags?: string[]): Promise<Pet[]> {
171
+ return await this.makeRequest('GET', '/pet/findByTags', { params: { 'tags': tags } });
172
+ }
173
+ /**
174
+ * Find pet by ID
175
+ *
176
+ * Returns a single pet
177
+ *
178
+ * @param petId ID of pet to return
179
+ * @returns {Pet}
180
+ */
181
+ async getPetById(petId: number): Promise<Pet> {
182
+ return Pet.parse(await this.makeRequest('GET', `/pet/${petId}`, {}));
183
+ }
184
+ /**
185
+ * Updates a pet in the store with form data
186
+ *
187
+ * @param petId ID of pet that needs to be updated
188
+ * @param name Name of pet that needs to be updated
189
+ * @param status Status of pet that needs to be updated
190
+ * @returns {void}
191
+ */
192
+ async updatePetWithForm(petId: number, name?: string, status?: string): Promise<void> {
193
+ return await this.makeRequest('POST', `/pet/${petId}`, { params: { 'name': name, 'status': status } });
194
+ }
195
+ /**
196
+ * Deletes a pet
197
+ *
198
+ * delete a pet
199
+ *
200
+ * @param api_key
201
+ * @param petId Pet id to delete
202
+ * @returns {void}
203
+ */
204
+ async deletePet(petId: number): Promise<void> {
205
+ return await this.makeRequest('DELETE', `/pet/${petId}`, {});
206
+ }
207
+ /**
208
+ * uploads an image
209
+ *
210
+ * @param petId ID of pet to update
211
+ * @param additionalMetadata Additional Metadata
212
+ * @param body
213
+ * @returns {ApiResponse}
214
+ */
215
+ async uploadFile(petId: number, additionalMetadata?: string): Promise<ApiResponse> {
216
+ return ApiResponse.parse(await this.makeRequest('POST', `/pet/${petId}/uploadImage`, { params: { 'additionalMetadata': additionalMetadata } }));
217
+ }
218
+ /**
219
+ * Returns pet inventories by status
220
+ *
221
+ * Returns a map of status codes to quantities
222
+ * @returns {Record<string, unknown>}
223
+ */
224
+ async getInventory(): Promise<Record<string, unknown>> {
225
+ return await this.makeRequest('GET', '/store/inventory', {});
226
+ }
227
+ /**
228
+ * Place an order for a pet
229
+ *
230
+ * Place a new order in the store
231
+ * @param body
232
+ * @returns {Order}
233
+ */
234
+ async placeOrder(body?: Order): Promise<Order> {
235
+ return Order.parse(await this.makeRequest('POST', '/store/order', { data: body, contentType: 'application/x-www-form-urlencoded' }));
236
+ }
237
+ /**
238
+ * Find purchase order by ID
239
+ *
240
+ * For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
241
+ *
242
+ * @param orderId ID of order that needs to be fetched
243
+ * @returns {Order}
244
+ */
245
+ async getOrderById(orderId: number): Promise<Order> {
246
+ return Order.parse(await this.makeRequest('GET', `/store/order/${orderId}`, {}));
247
+ }
248
+ /**
249
+ * Delete purchase order by ID
250
+ *
251
+ * For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
252
+ *
253
+ * @param orderId ID of the order that needs to be deleted
254
+ * @returns {void}
255
+ */
256
+ async deleteOrder(orderId: number): Promise<void> {
257
+ return await this.makeRequest('DELETE', `/store/order/${orderId}`, {});
258
+ }
259
+ /**
260
+ * Create user
261
+ *
262
+ * This can only be done by the logged in user.
263
+ * @param body Created user object
264
+ * @returns {User}
265
+ */
266
+ async createUser(body?: User): Promise<User> {
267
+ return User.parse(await this.makeRequest('POST', '/user', { data: body, contentType: 'application/x-www-form-urlencoded' }));
268
+ }
269
+ /**
270
+ * Creates list of users with given input array
271
+ * @param body
272
+ * @returns {User}
273
+ */
274
+ async createUsersWithListInput(body?: User[]): Promise<User> {
275
+ return User.parse(await this.makeRequest('POST', '/user/createWithList', { data: body }));
276
+ }
277
+ /**
278
+ * Logs user into the system
279
+ *
280
+ * @param username The user name for login
281
+ * @param password The password for login in clear text
282
+ * @returns {string}
283
+ */
284
+ async loginUser(username?: string, password?: string): Promise<string> {
285
+ return await this.makeRequest('GET', '/user/login', { params: { 'username': username, 'password': password } });
286
+ }
287
+ /**
288
+ * Logs out current logged in user session
289
+ * @returns {void}
290
+ */
291
+ async logoutUser(): Promise<void> {
292
+ return await this.makeRequest('GET', '/user/logout', {});
293
+ }
294
+ /**
295
+ * Get user by user name
296
+ *
297
+ * @param username The name that needs to be fetched. Use user1 for testing.
298
+ * @returns {User}
299
+ */
300
+ async getUserByName(username: string): Promise<User> {
301
+ return User.parse(await this.makeRequest('GET', `/user/${username}`, {}));
302
+ }
303
+ /**
304
+ * Update user
305
+ *
306
+ * This can only be done by the logged in user.
307
+ *
308
+ * @param username name that need to be deleted
309
+ * @param body Update an existent user in the store
310
+ * @returns {void}
311
+ */
312
+ async updateUser(username: string, body?: User): Promise<void> {
313
+ return await this.makeRequest('PUT', `/user/${username}`, { data: body, contentType: 'application/x-www-form-urlencoded' });
314
+ }
315
+ /**
316
+ * Delete user
317
+ *
318
+ * This can only be done by the logged in user.
319
+ *
320
+ * @param username The name that needs to be deleted
321
+ * @returns {void}
322
+ */
323
+ async deleteUser(username: string): Promise<void> {
324
+ return await this.makeRequest('DELETE', `/user/${username}`, {});
325
+ }
326
+ }
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "openapi-types": "^12.1.3",
14
14
  "openapi-typescript": "^7.10.1",
15
15
  "path-to-regexp": "^8.3.0",
16
- "prettier": "^3.6.2",
16
+ "prettier": "^3.8.0",
17
17
  "typescript": "^5.9.3",
18
18
  "url-pattern": "^1.0.3",
19
19
  "yargs": "^18.0.0",
@@ -41,9 +41,9 @@
41
41
  "@types/jest": "^30.0.0",
42
42
  "@types/js-yaml": "^4.0.9",
43
43
  "@types/jsonpath": "^0.2.4",
44
- "@types/node": "^25.0.3",
44
+ "@types/node": "^25.0.9",
45
45
  "@types/yargs": "^17.0.35",
46
- "@vitest/coverage-v8": "^4.0.16",
46
+ "@vitest/coverage-v8": "^4.0.17",
47
47
  "cross-env": "^10.1.0",
48
48
  "eslint": "^9.39.2",
49
49
  "eslint-config-prettier": "^10.1.8",
@@ -51,13 +51,10 @@
51
51
  "lint-staged": "^16.2.7",
52
52
  "semantic-release": "^25.0.2",
53
53
  "ts-node": "^10.9.2",
54
- "typescript-eslint": "^8.51.0",
55
- "undici": "^7.16.0",
54
+ "typescript-eslint": "^8.53.0",
55
+ "undici": "^7.18.2",
56
56
  "vitest": "^4.0.14"
57
57
  },
58
- "optionalDependencies": {
59
- "undici": "^7.16.0"
60
- },
61
58
  "homepage": "https://github.com/julienandreu/zod-codegen",
62
59
  "license": "Apache-2.0",
63
60
  "name": "zod-codegen",
@@ -75,21 +72,19 @@
75
72
  "url": "git+ssh://git@github.com/julienandreu/zod-codegen.git"
76
73
  },
77
74
  "engines": {
78
- "node": ">=24.11.1"
75
+ "node": ">=18.0.0"
79
76
  },
80
77
  "overrides": {
81
- "npm": {
82
- "glob": "^11.1.0",
83
- "tar": "^7.5.2"
84
- },
85
- "glob": "^11.1.0",
86
- "tar": "^7.5.2",
87
- "yargs": "^18.0.0"
78
+ "tar": "^7.5.3",
79
+ "diff": "^8.0.3",
80
+ "@actions/http-client": {
81
+ "undici": "^7.18.0"
82
+ }
88
83
  },
89
84
  "scripts": {
90
85
  "audit:fix": "npm audit fix",
91
- "build": "NODE_OPTIONS='--no-deprecation' sh -c 'rm -rf dist && tsc --project tsconfig.json && cp -r src/assets dist/src/ && chmod +x ./dist/src/cli.js'",
92
- "build:native": "rm -rf dist && NODE_OPTIONS='--no-deprecation' npx tsgo --project tsconfig.json && cp -r src/assets dist/src/ && chmod +x ./dist/src/cli.js",
86
+ "build": "NODE_OPTIONS='--no-deprecation' sh -c 'rm -rf dist && tsc --project tsconfig.json && ts-node scripts/add-js-extensions.ts && mkdir -p dist/src/assets && cp -r src/assets/. dist/src/assets/ && chmod +x ./dist/src/cli.js'",
87
+ "build:native": "rm -rf dist && NODE_OPTIONS='--no-deprecation' npx tsgo --project tsconfig.json && ts-node scripts/add-js-extensions.ts && cp -r src/assets dist/src/ && chmod +x ./dist/src/cli.js",
93
88
  "build:watch": "tsc --project tsconfig.json --watch",
94
89
  "dev": "npm run build && node ./dist/src/cli.js --input ./samples/swagger-petstore.yaml --output ./examples/petstore && npm run format && npm run lint",
95
90
  "lint": "eslint src --fix",
@@ -110,5 +105,5 @@
110
105
  "release": "semantic-release",
111
106
  "release:dry": "semantic-release --dry-run"
112
107
  },
113
- "version": "1.5.1"
108
+ "version": "1.6.2"
114
109
  }
@@ -0,0 +1,79 @@
1
+ import {readdirSync, readFileSync, statSync, writeFileSync} from 'node:fs';
2
+ import {extname, join} from 'node:path';
3
+
4
+ /**
5
+ * Recursively finds all .js files in a directory
6
+ */
7
+ function findJsFiles(dir: string): string[] {
8
+ const files: string[] = [];
9
+ const entries = readdirSync(dir);
10
+
11
+ for (const entry of entries) {
12
+ const fullPath = join(dir, entry);
13
+ const stat = statSync(fullPath);
14
+
15
+ if (stat.isDirectory()) {
16
+ files.push(...findJsFiles(fullPath));
17
+ } else if (extname(entry) === '.js') {
18
+ files.push(fullPath);
19
+ }
20
+ }
21
+
22
+ return files;
23
+ }
24
+
25
+ /**
26
+ * Adds .js extensions to relative imports in a JavaScript file
27
+ */
28
+ function addJsExtensions(content: string): string {
29
+ // Match relative imports: './something' or '../something' but not './something.js' or node: imports
30
+ // Handles both single and double quotes
31
+ // Also handles type imports: import type { ... } from './something'
32
+ const importRegex = /from\s+(['"])(\.\.?\/[^'"]+?)(['"])/g;
33
+
34
+ return content.replace(importRegex, (match, quote: string, importPath: string, endQuote: string) => {
35
+ // Skip if already has an extension
36
+ if (importPath.endsWith('.js') || importPath.endsWith('.json')) {
37
+ return match;
38
+ }
39
+
40
+ // Add .js extension
41
+ return `from ${quote}${importPath}.js${endQuote}`;
42
+ });
43
+ }
44
+
45
+ /**
46
+ * Main function to process all JavaScript files in dist/src
47
+ */
48
+ function main(): void {
49
+ const distDir = join(process.cwd(), 'dist', 'src');
50
+
51
+ try {
52
+ const jsFiles = findJsFiles(distDir);
53
+
54
+ if (jsFiles.length === 0) {
55
+ console.warn('No .js files found in dist/src');
56
+ process.exit(0);
57
+ }
58
+
59
+ let processedCount = 0;
60
+
61
+ for (const filePath of jsFiles) {
62
+ const content = readFileSync(filePath, 'utf-8');
63
+ const updatedContent = addJsExtensions(content);
64
+
65
+ if (content !== updatedContent) {
66
+ writeFileSync(filePath, updatedContent, 'utf-8');
67
+ processedCount++;
68
+ }
69
+ }
70
+
71
+ console.log(`✅ Added .js extensions to ${String(processedCount)} file(s)`);
72
+ process.exit(0);
73
+ } catch (error) {
74
+ console.error('❌ Error processing files:', error);
75
+ process.exit(1);
76
+ }
77
+ }
78
+
79
+ main();
@@ -1,5 +1,6 @@
1
- import {readFileSync, writeFileSync} from 'fs';
2
- import {resolve} from 'path';
1
+ import {readFileSync, writeFileSync} from 'node:fs';
2
+ import {dirname, resolve} from 'node:path';
3
+ import {fileURLToPath} from 'node:url';
3
4
  import {z} from 'zod';
4
5
 
5
6
  interface PackageJson {
@@ -30,6 +31,7 @@ export function isPackageJson(input: unknown): input is PackageJson {
30
31
  return success;
31
32
  }
32
33
 
34
+ const __dirname = dirname(fileURLToPath(import.meta.url));
33
35
  const sourcePath = resolve(__dirname, '..', 'package.json');
34
36
 
35
37
  const data: unknown = JSON.parse(readFileSync(sourcePath, 'utf8'));
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "zod-codegen",
3
- "version": "1.0.1",
3
+ "version": "1.5.0",
4
4
  "description": "A powerful TypeScript code generator that creates Zod schemas and type-safe clients from OpenAPI specifications"
5
5
  }
package/src/cli.ts CHANGED
@@ -2,16 +2,16 @@
2
2
 
3
3
  import yargs from 'yargs';
4
4
  import {hideBin} from 'yargs/helpers';
5
- import {Generator, type GeneratorOptions, type NamingConvention} from './generator.js';
5
+ import {Generator, type GeneratorOptions, type NamingConvention} from './generator';
6
6
  import {readFileSync} from 'node:fs';
7
7
  import {fileURLToPath} from 'node:url';
8
8
  import {dirname, join} from 'node:path';
9
9
 
10
10
  import loudRejection from 'loud-rejection';
11
- import {handleErrors} from './utils/error-handler.js';
12
- import {handleSignals} from './utils/signal-handler.js';
11
+ import {handleErrors} from './utils/error-handler';
12
+ import {handleSignals} from './utils/signal-handler';
13
13
  import debug from 'debug';
14
- import {Reporter} from './utils/reporter.js';
14
+ import {Reporter} from './utils/reporter';
15
15
 
16
16
  const __dirname = dirname(fileURLToPath(import.meta.url));
17
17
  // Read package.json from the project root
@@ -47,14 +47,14 @@ const packageData = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as {
47
47
  };
48
48
 
49
49
  const {name, description, version} = packageData;
50
- const reporter = new Reporter(process.stdout);
50
+ const reporter = new Reporter(process.stdout, process.stderr);
51
51
  const startTime = process.hrtime.bigint();
52
52
 
53
53
  debug(`${name}:${String(process.pid)}`);
54
54
 
55
55
  loudRejection();
56
- handleSignals(process, startTime);
57
- handleErrors(process, startTime);
56
+ handleSignals(process, startTime, reporter);
57
+ handleErrors(process, startTime, reporter);
58
58
 
59
59
  const argv = yargs(hideBin(process.argv))
60
60
  .scriptName(name)
package/src/generator.ts CHANGED
@@ -1,14 +1,41 @@
1
- import type {Reporter} from './utils/reporter.js';
2
- import type {OpenApiSpecType} from './types/openapi.js';
3
- import type {GeneratorOptions} from './types/generator-options.js';
4
- import {OpenApiFileParserService, SyncFileReaderService} from './services/file-reader.service.js';
5
- import {TypeScriptCodeGeneratorService} from './services/code-generator.service.js';
6
- import {SyncFileWriterService} from './services/file-writer.service.js';
1
+ import type {Reporter} from './utils/reporter';
2
+ import type {OpenApiSpecType} from './types/openapi';
3
+ import type {GeneratorOptions} from './types/generator-options';
4
+ import {OpenApiFileParserService, SyncFileReaderService} from './services/file-reader.service';
5
+ import {TypeScriptCodeGeneratorService} from './services/code-generator.service';
6
+ import {SyncFileWriterService} from './services/file-writer.service';
7
7
 
8
8
  // Re-export types for library users
9
- export type {GeneratorOptions} from './types/generator-options.js';
10
- export type {NamingConvention, OperationDetails, OperationNameTransformer} from './utils/naming-convention.js';
9
+ export type {GeneratorOptions} from './types/generator-options';
10
+ export type {NamingConvention, OperationDetails, OperationNameTransformer} from './utils/naming-convention';
11
11
 
12
+ /**
13
+ * Main generator class for creating TypeScript code from OpenAPI specifications.
14
+ *
15
+ * This class orchestrates the code generation process:
16
+ * 1. Reads the OpenAPI specification file (local or remote)
17
+ * 2. Parses and validates the specification
18
+ * 3. Generates TypeScript code with Zod schemas and type-safe API client
19
+ * 4. Writes the generated code to the output directory
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * import {Generator} from 'zod-codegen';
24
+ * import {Reporter} from './utils/reporter';
25
+ *
26
+ * const reporter = new Reporter(process.stdout, process.stderr);
27
+ * const generator = new Generator(
28
+ * 'my-app',
29
+ * '1.0.0',
30
+ * reporter,
31
+ * './openapi.yaml',
32
+ * './generated',
33
+ * {namingConvention: 'camelCase'}
34
+ * );
35
+ *
36
+ * const exitCode = await generator.run();
37
+ * ```
38
+ */
12
39
  export class Generator {
13
40
  private readonly fileReader = new SyncFileReaderService();
14
41
  private readonly fileParser = new OpenApiFileParserService();
@@ -16,6 +43,16 @@ export class Generator {
16
43
  private readonly fileWriter: SyncFileWriterService;
17
44
  private readonly outputPath: string;
18
45
 
46
+ /**
47
+ * Creates a new Generator instance.
48
+ *
49
+ * @param _name - The name of the application/library (used in generated file headers)
50
+ * @param _version - The version of the application/library (used in generated file headers)
51
+ * @param reporter - Reporter instance for logging messages and errors
52
+ * @param inputPath - Path or URL to the OpenAPI specification file
53
+ * @param _outputDir - Directory where generated files will be written
54
+ * @param options - Optional configuration for code generation
55
+ */
19
56
  constructor(
20
57
  private readonly _name: string,
21
58
  private readonly _version: string,
@@ -29,6 +66,11 @@ export class Generator {
29
66
  this.codeGenerator = new TypeScriptCodeGeneratorService(options);
30
67
  }
31
68
 
69
+ /**
70
+ * Executes the code generation process.
71
+ *
72
+ * @returns Promise that resolves to an exit code (0 for success, 1 for failure)
73
+ */
32
74
  async run(): Promise<number> {
33
75
  try {
34
76
  const rawSource = await this.readFile();
@@ -46,7 +88,7 @@ export class Generator {
46
88
  this.reporter.error('❌ An unknown error occurred');
47
89
  }
48
90
 
49
- return Promise.resolve(1);
91
+ return 1;
50
92
  }
51
93
  }
52
94
 
@@ -1,4 +1,4 @@
1
- import type {OpenApiSpecType} from '../types/openapi.js';
1
+ import type {OpenApiSpecType} from '../types/openapi';
2
2
 
3
3
  export interface CodeGenerator {
4
4
  generate(spec: OpenApiSpecType): string;