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.
- package/.github/workflows/ci.yml +7 -1
- package/.github/workflows/release.yml +3 -3
- package/CHANGELOG.md +34 -0
- package/CONTRIBUTING.md +1 -1
- package/EXAMPLES.md +91 -12
- package/README.md +11 -4
- package/dist/scripts/add-js-extensions.d.ts +2 -0
- package/dist/scripts/add-js-extensions.d.ts.map +1 -0
- package/dist/scripts/add-js-extensions.js +66 -0
- package/dist/scripts/update-manifest.d.ts +14 -0
- package/dist/scripts/update-manifest.d.ts.map +1 -0
- package/dist/scripts/update-manifest.js +33 -0
- package/dist/src/assets/manifest.json +1 -1
- package/dist/src/cli.js +3 -3
- package/dist/src/generator.d.ts +46 -4
- package/dist/src/generator.d.ts.map +1 -1
- package/dist/src/generator.js +43 -1
- package/dist/src/interfaces/code-generator.d.ts +1 -1
- package/dist/src/interfaces/code-generator.d.ts.map +1 -1
- package/dist/src/services/code-generator.service.d.ts +5 -3
- package/dist/src/services/code-generator.service.d.ts.map +1 -1
- package/dist/src/services/code-generator.service.js +86 -20
- package/dist/src/services/file-reader.service.d.ts +2 -2
- package/dist/src/services/file-reader.service.d.ts.map +1 -1
- package/dist/src/services/file-writer.service.d.ts +1 -1
- package/dist/src/services/file-writer.service.d.ts.map +1 -1
- package/dist/src/services/import-builder.service.d.ts +1 -1
- package/dist/src/services/import-builder.service.d.ts.map +1 -1
- package/dist/src/services/type-builder.service.d.ts +1 -1
- package/dist/src/services/type-builder.service.d.ts.map +1 -1
- package/dist/src/types/generator-options.d.ts +1 -1
- package/dist/src/types/generator-options.d.ts.map +1 -1
- package/dist/src/utils/error-handler.d.ts +3 -2
- package/dist/src/utils/error-handler.d.ts.map +1 -1
- package/dist/src/utils/error-handler.js +4 -4
- package/dist/src/utils/reporter.d.ts +3 -2
- package/dist/src/utils/reporter.d.ts.map +1 -1
- package/dist/src/utils/reporter.js +7 -5
- package/dist/src/utils/signal-handler.d.ts +3 -2
- package/dist/src/utils/signal-handler.d.ts.map +1 -1
- package/dist/src/utils/signal-handler.js +4 -4
- package/examples/README.md +10 -1
- package/examples/petstore/README.md +6 -6
- package/examples/petstore/authenticated-usage.ts +1 -1
- package/examples/petstore/basic-usage.ts +1 -1
- package/examples/petstore/retry-handler-usage.ts +173 -0
- package/examples/petstore/server-variables-usage.ts +1 -1
- package/examples/petstore/type.ts +76 -53
- package/examples/pokeapi/README.md +3 -3
- package/examples/pokeapi/basic-usage.ts +1 -1
- package/examples/pokeapi/custom-client.ts +1 -1
- package/generated/type.ts +326 -0
- package/package.json +14 -19
- package/scripts/add-js-extensions.ts +79 -0
- package/scripts/update-manifest.ts +4 -2
- package/src/assets/manifest.json +1 -1
- package/src/cli.ts +7 -7
- package/src/generator.ts +51 -9
- package/src/interfaces/code-generator.ts +1 -1
- package/src/services/code-generator.service.ts +256 -122
- package/src/services/file-reader.service.ts +3 -3
- package/src/services/file-writer.service.ts +1 -1
- package/src/services/import-builder.service.ts +1 -1
- package/src/services/type-builder.service.ts +1 -1
- package/src/types/generator-options.ts +1 -1
- package/src/utils/error-handler.ts +6 -5
- package/src/utils/reporter.ts +6 -3
- package/src/utils/signal-handler.ts +10 -8
- package/tests/integration/cli-comprehensive.test.ts +123 -0
- package/tests/integration/cli.test.ts +2 -2
- package/tests/integration/error-scenarios.test.ts +240 -0
- package/tests/integration/snapshots.test.ts +131 -0
- package/tests/unit/code-generator-edge-cases.test.ts +551 -0
- package/tests/unit/code-generator.test.ts +385 -2
- package/tests/unit/file-reader.test.ts +16 -1
- package/tests/unit/generator.test.ts +19 -2
- package/tests/unit/naming-convention.test.ts +30 -1
- package/tests/unit/reporter.test.ts +63 -0
- package/tests/unit/type-builder.test.ts +131 -0
- package/tsconfig.json +3 -3
- package/dist/src/http/fetch-client.d.ts +0 -15
- package/dist/src/http/fetch-client.d.ts.map +0 -1
- package/dist/src/http/fetch-client.js +0 -140
- package/dist/src/polyfills/fetch.d.ts +0 -5
- package/dist/src/polyfills/fetch.d.ts.map +0 -1
- package/dist/src/polyfills/fetch.js +0 -18
- package/dist/src/types/http.d.ts +0 -25
- package/dist/src/types/http.d.ts.map +0 -1
- package/dist/src/types/http.js +0 -10
- package/dist/src/utils/manifest.d.ts +0 -8
- package/dist/src/utils/manifest.d.ts.map +0 -1
- package/dist/src/utils/manifest.js +0 -9
- package/dist/src/utils/tty.d.ts +0 -2
- package/dist/src/utils/tty.d.ts.map +0 -1
- package/dist/src/utils/tty.js +0 -3
- package/src/http/fetch-client.ts +0 -181
- package/src/polyfills/fetch.ts +0 -26
- package/src/types/http.ts +0 -35
- package/src/utils/manifest.ts +0 -17
- 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.
|
|
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.
|
|
44
|
+
"@types/node": "^25.0.9",
|
|
45
45
|
"@types/yargs": "^17.0.35",
|
|
46
|
-
"@vitest/coverage-v8": "^4.0.
|
|
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.
|
|
55
|
-
"undici": "^7.
|
|
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": ">=
|
|
75
|
+
"node": ">=18.0.0"
|
|
79
76
|
},
|
|
80
77
|
"overrides": {
|
|
81
|
-
"
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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.
|
|
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'));
|
package/src/assets/manifest.json
CHANGED
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
|
|
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
|
|
12
|
-
import {handleSignals} from './utils/signal-handler
|
|
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
|
|
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
|
|
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
|
|
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
|
|
10
|
-
export type {NamingConvention, OperationDetails, OperationNameTransformer} from './utils/naming-convention
|
|
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
|
|
91
|
+
return 1;
|
|
50
92
|
}
|
|
51
93
|
}
|
|
52
94
|
|