zod-codegen 1.1.2 → 1.2.1

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.
@@ -29,25 +29,25 @@ jobs:
29
29
  uses: actions/setup-node@v5
30
30
  with:
31
31
  node-version: ${{ matrix.node-version }}
32
- cache: 'npm'
32
+ cache: 'yarn'
33
33
 
34
34
  - name: Install dependencies
35
- run: npm ci
35
+ run: yarn install --frozen-lockfile
36
36
 
37
37
  - name: Run type check
38
- run: npm run type-check
38
+ run: yarn type-check
39
39
 
40
40
  - name: Run linter
41
- run: npm run lint:check
41
+ run: yarn lint:check
42
42
 
43
43
  - name: Run formatter check
44
- run: npm run format:check
44
+ run: yarn format:check
45
45
 
46
46
  - name: Build project
47
- run: npm run build
47
+ run: yarn build
48
48
 
49
49
  - name: Run tests
50
- run: npm run test:coverage
50
+ run: yarn test:coverage
51
51
 
52
52
  - name: Upload coverage to Codecov
53
53
  if: matrix.node-version == '24.11.1' && matrix.os == 'ubuntu-latest'
@@ -70,13 +70,13 @@ jobs:
70
70
  uses: actions/setup-node@v5
71
71
  with:
72
72
  node-version: '24.11.1'
73
- cache: 'npm'
73
+ cache: 'yarn'
74
74
 
75
75
  - name: Install dependencies
76
- run: npm ci
76
+ run: yarn install --frozen-lockfile
77
77
 
78
78
  - name: Run security audit
79
- run: npm audit --audit-level high
79
+ run: yarn audit --level high
80
80
 
81
81
  build:
82
82
  name: Build & Package
@@ -91,13 +91,13 @@ jobs:
91
91
  uses: actions/setup-node@v5
92
92
  with:
93
93
  node-version: '24.11.1'
94
- cache: 'npm'
94
+ cache: 'yarn'
95
95
 
96
96
  - name: Install dependencies
97
- run: npm ci
97
+ run: yarn install --frozen-lockfile
98
98
 
99
99
  - name: Build project
100
- run: npm run build
100
+ run: yarn build
101
101
 
102
102
  - name: Test CLI
103
103
  run: |
@@ -129,15 +129,15 @@ jobs:
129
129
  uses: actions/setup-node@v5
130
130
  with:
131
131
  node-version: '24.11.1'
132
- cache: 'npm'
132
+ cache: 'yarn'
133
133
 
134
134
  - name: Install dependencies
135
- run: npm ci
135
+ run: yarn install --frozen-lockfile
136
136
 
137
137
  - name: Build project
138
- run: npm run build
138
+ run: yarn build
139
139
 
140
140
  - name: Preview semantic-release
141
141
  env:
142
142
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
143
- run: npm run release:dry
143
+ run: yarn release:dry
@@ -29,26 +29,26 @@ jobs:
29
29
  uses: actions/setup-node@v5
30
30
  with:
31
31
  node-version: '24.11.1'
32
- cache: 'npm'
32
+ cache: 'yarn'
33
33
  registry-url: 'https://registry.npmjs.org'
34
34
 
35
35
  - name: Install dependencies
36
- run: npm ci
36
+ run: yarn install --frozen-lockfile
37
37
 
38
38
  - name: Run type check
39
- run: npm run type-check
39
+ run: yarn type-check
40
40
 
41
41
  - name: Run linter check
42
- run: npm run lint:check
42
+ run: yarn lint:check
43
43
 
44
44
  - name: Run formatter check
45
- run: npm run format:check
45
+ run: yarn format:check
46
46
 
47
47
  - name: Run tests with coverage
48
- run: npm run test:coverage
48
+ run: yarn test:coverage
49
49
 
50
50
  - name: Build project
51
- run: npm run build
51
+ run: yarn build
52
52
 
53
53
  - name: Test CLI functionality
54
54
  run: |
@@ -62,4 +62,4 @@ jobs:
62
62
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
63
63
  NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
64
64
  NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
65
- run: npx semantic-release
65
+ run: yarn release
package/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## <small>1.2.1 (2025-11-19)</small>
2
+
3
+ - Merge pull request #33 from julienandreu/fix/add-z-infer-to-response-types ([a14d81c](https://github.com/julienandreu/zod-codegen/commit/a14d81c)), closes [#33](https://github.com/julienandreu/zod-codegen/issues/33)
4
+ - fix: add z.infer to response types in generated methods ([c45ac77](https://github.com/julienandreu/zod-codegen/commit/c45ac77))
5
+
6
+ ## 1.2.0 (2025-11-19)
7
+
8
+ - Merge pull request #31 from julienandreu/dependabot/npm_and_yarn/dev-dependencies-1dd8918b9f ([97ebb65](https://github.com/julienandreu/zod-codegen/commit/97ebb65)), closes [#31](https://github.com/julienandreu/zod-codegen/issues/31)
9
+ - Merge pull request #32 from julienandreu/feat/add-jsdoc-comments ([9e0d589](https://github.com/julienandreu/zod-codegen/commit/9e0d589)), closes [#32](https://github.com/julienandreu/zod-codegen/issues/32)
10
+ - feat: add JSDoc comments to generated API client methods ([4f42f97](https://github.com/julienandreu/zod-codegen/commit/4f42f97))
11
+ - chore(deps-dev): bump the dev-dependencies group with 3 updates ([43be1ab](https://github.com/julienandreu/zod-codegen/commit/43be1ab))
12
+
1
13
  ## <small>1.1.2 (2025-11-13)</small>
2
14
 
3
15
  - Merge pull request #30 from julienandreu/fix/cli-strict-mode ([d098ac7](https://github.com/julienandreu/zod-codegen/commit/d098ac7)), closes [#30](https://github.com/julienandreu/zod-codegen/issues/30)
package/CONTRIBUTING.md CHANGED
@@ -22,7 +22,7 @@ This project adheres to a code of conduct. By participating, you are expected to
22
22
  ### Prerequisites
23
23
 
24
24
  - Node.js >= 24.11.1
25
- - npm or yarn
25
+ - yarn
26
26
  - Git
27
27
 
28
28
  ### Development Setup
@@ -37,24 +37,24 @@ This project adheres to a code of conduct. By participating, you are expected to
37
37
  2. **Install Dependencies**
38
38
 
39
39
  ```bash
40
- npm install
40
+ yarn install
41
41
  ```
42
42
 
43
43
  3. **Build the Project**
44
44
 
45
45
  ```bash
46
- npm run build
46
+ yarn build
47
47
  ```
48
48
 
49
49
  4. **Run Tests**
50
50
 
51
51
  ```bash
52
- npm test
52
+ yarn test
53
53
  ```
54
54
 
55
55
  5. **Run Development Mode**
56
56
  ```bash
57
- npm run dev
57
+ yarn dev
58
58
  ```
59
59
 
60
60
  ## Making Changes
@@ -119,7 +119,7 @@ test: add integration tests for CLI
119
119
  3. **Validate Your Changes**
120
120
 
121
121
  ```bash
122
- npm run validate
122
+ yarn validate
123
123
  ```
124
124
 
125
125
  This runs:
@@ -171,10 +171,10 @@ We use ESLint and Prettier for consistent code style:
171
171
 
172
172
  ```bash
173
173
  # Auto-fix linting issues
174
- npm run lint
174
+ yarn lint
175
175
 
176
176
  # Format code
177
- npm run format
177
+ yarn format
178
178
  ```
179
179
 
180
180
  ### File Structure
@@ -218,16 +218,16 @@ tests/
218
218
 
219
219
  ```bash
220
220
  # Run all tests
221
- npm test
221
+ yarn test
222
222
 
223
223
  # Run tests in watch mode
224
- npm run test:watch
224
+ yarn test:watch
225
225
 
226
226
  # Run with coverage
227
- npm run test:coverage
227
+ yarn test:coverage
228
228
 
229
229
  # Run specific test file
230
- npx vitest run generator.test.ts
230
+ yarn vitest run generator.test.ts
231
231
  ```
232
232
 
233
233
  ## Documentation
package/PERFORMANCE.md CHANGED
@@ -12,14 +12,14 @@ For even faster builds, you can optionally use the TypeScript Native Preview (TS
12
12
  ### Installation
13
13
 
14
14
  ```bash
15
- npm install -D @typescript/native-preview
15
+ yarn add -D @typescript/native-preview
16
16
  ```
17
17
 
18
18
  ### Usage
19
19
 
20
20
  ```bash
21
21
  # Use native compiler for builds
22
- npm run build:native
22
+ yarn build:native
23
23
 
24
24
  # Or use directly
25
25
  npx tsgo --project tsconfig.json
@@ -52,8 +52,8 @@ To compare performance:
52
52
 
53
53
  ```bash
54
54
  # Standard TypeScript
55
- time npm run build
55
+ time yarn build
56
56
 
57
57
  # Native TypeScript
58
- time npm run build:native
58
+ time yarn build:native
59
59
  ```
package/README.md CHANGED
@@ -28,13 +28,13 @@ A powerful TypeScript code generator that creates **Zod schemas** and **type-saf
28
28
  ### Global Installation (CLI)
29
29
 
30
30
  ```bash
31
- npm install -g zod-codegen
31
+ yarn global add zod-codegen
32
32
  ```
33
33
 
34
34
  ### Project Installation
35
35
 
36
36
  ```bash
37
- npm install --save-dev zod-codegen
37
+ yarn add --dev zod-codegen
38
38
  ```
39
39
 
40
40
  ## 🔧 Usage
@@ -378,7 +378,7 @@ Each example includes:
378
378
  ### Prerequisites
379
379
 
380
380
  - Node.js ≥ 24.11.1
381
- - npm or yarn
381
+ - yarn
382
382
 
383
383
  ### Setup
384
384
 
@@ -388,52 +388,52 @@ git clone https://github.com/julienandreu/zod-codegen.git
388
388
  cd zod-codegen
389
389
 
390
390
  # Install dependencies
391
- npm install
391
+ yarn install
392
392
 
393
393
  # Build the project
394
- npm run build
394
+ yarn build
395
395
 
396
396
  # Run tests
397
- npm test
397
+ yarn test
398
398
 
399
399
  # Run linting
400
- npm run lint
400
+ yarn lint
401
401
 
402
402
  # Format code
403
- npm run format
403
+ yarn format
404
404
  ```
405
405
 
406
406
  ### Testing
407
407
 
408
408
  ```bash
409
409
  # Run all tests
410
- npm test
410
+ yarn test
411
411
 
412
412
  # Run tests in watch mode
413
- npm run test:watch
413
+ yarn test:watch
414
414
 
415
415
  # Run tests with coverage
416
- npm run test:coverage
416
+ yarn test:coverage
417
417
 
418
418
  # Run tests with UI
419
- npm run test:ui
419
+ yarn test:ui
420
420
  ```
421
421
 
422
422
  ### Available Scripts
423
423
 
424
- | Script | Description |
425
- | ----------------------- | ----------------------------------------------- |
426
- | `npm run build` | Build the project |
427
- | `npm run build:watch` | Build in watch mode |
428
- | `npm run dev` | Development mode with example |
429
- | `npm test` | Run tests |
430
- | `npm run test:watch` | Run tests in watch mode |
431
- | `npm run test:coverage` | Run tests with coverage |
432
- | `npm run lint` | Lint and fix code |
433
- | `npm run format` | Format code with Prettier |
434
- | `npm run type-check` | Type check without emitting |
435
- | `npm run validate` | Run all checks (lint, format, type-check, test) |
436
- | `npm run clean` | Clean build artifacts |
424
+ | Script | Description |
425
+ | -------------------- | ----------------------------------------------- |
426
+ | `yarn build` | Build the project |
427
+ | `yarn build:watch` | Build in watch mode |
428
+ | `yarn dev` | Development mode with example |
429
+ | `yarn test` | Run tests |
430
+ | `yarn test:watch` | Run tests in watch mode |
431
+ | `yarn test:coverage` | Run tests with coverage |
432
+ | `yarn lint` | Lint and fix code |
433
+ | `yarn format` | Format code with Prettier |
434
+ | `yarn type-check` | Type check without emitting |
435
+ | `yarn validate` | Run all checks (lint, format, type-check, test) |
436
+ | `yarn clean` | Clean build artifacts |
437
437
 
438
438
  ## 📋 Requirements
439
439
 
@@ -450,8 +450,8 @@ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) f
450
450
  1. Fork the repository
451
451
  2. Create your feature branch: `git checkout -b feature/amazing-feature`
452
452
  3. Make your changes
453
- 4. Run tests: `npm test`
454
- 5. Run validation: `npm run validate`
453
+ 4. Run tests: `yarn test`
454
+ 5. Run validation: `yarn validate`
455
455
  6. Commit your changes: `git commit -m 'feat: add amazing feature'`
456
456
  7. Push to the branch: `git push origin feature/amazing-feature`
457
457
  8. Open a Pull Request
package/SECURITY.md CHANGED
@@ -75,7 +75,7 @@ When using zod-codegen, please follow these security best practices:
75
75
 
76
76
  - Keep zod-codegen and its dependencies up to date
77
77
  - Regularly audit your dependency tree for known vulnerabilities
78
- - Use tools like `npm audit` to check for security issues
78
+ - Use tools like `yarn audit` or `npm audit` to check for security issues
79
79
 
80
80
  ## Known Security Considerations
81
81
 
@@ -296,7 +296,15 @@ export class TypeScriptCodeGeneratorService {
296
296
  else {
297
297
  statements.push(ts.factory.createReturnStatement(ts.factory.createAwaitExpression(makeRequestCall)));
298
298
  }
299
- return ts.factory.createMethodDeclaration([ts.factory.createToken(ts.SyntaxKind.AsyncKeyword)], undefined, ts.factory.createIdentifier(String(schema.operationId)), undefined, undefined, parameters, responseType, ts.factory.createBlock(statements, true));
299
+ const methodDeclaration = ts.factory.createMethodDeclaration([ts.factory.createToken(ts.SyntaxKind.AsyncKeyword)], undefined, ts.factory.createIdentifier(String(schema.operationId)), undefined, undefined, parameters, responseType, ts.factory.createBlock(statements, true));
300
+ // Add JSDoc comment if summary or description exists
301
+ const jsdocComment = this.buildJSDocComment(schema.summary, schema.description, schema, responseType);
302
+ if (jsdocComment) {
303
+ // addSyntheticLeadingComment expects the comment content without delimiters
304
+ // and will wrap it in /** */ for JSDoc-style comments
305
+ ts.addSyntheticLeadingComment(methodDeclaration, ts.SyntaxKind.MultiLineCommentTrivia, `*\n${jsdocComment}\n `, true);
306
+ }
307
+ return methodDeclaration;
300
308
  }
301
309
  buildPathExpression(path, pathParams) {
302
310
  // Replace {param} with ${param} for template literal
@@ -508,9 +516,36 @@ export class TypeScriptCodeGeneratorService {
508
516
  }
509
517
  const responseSchema = response.content['application/json'].schema;
510
518
  const typeName = this.getSchemaTypeName(responseSchema, schemas);
511
- return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Promise'), [
512
- ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(typeName), undefined),
513
- ]);
519
+ const inferredType = this.wrapTypeWithZInfer(typeName, schemas);
520
+ return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Promise'), [inferredType]);
521
+ }
522
+ wrapTypeWithZInfer(typeName, schemas) {
523
+ // Primitive types and Record types don't need z.infer
524
+ const primitiveTypes = ['string', 'number', 'boolean', 'unknown'];
525
+ if (primitiveTypes.includes(typeName) || typeName.startsWith('Record<')) {
526
+ return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(typeName), undefined);
527
+ }
528
+ // Handle array types like "Pet[]"
529
+ if (typeName.endsWith('[]')) {
530
+ const itemTypeName = typeName.slice(0, -2);
531
+ const sanitizedItemTypeName = this.typeBuilder.sanitizeIdentifier(itemTypeName);
532
+ // Check if the item type is a custom schema
533
+ if (schemas[sanitizedItemTypeName]) {
534
+ // Return z.infer<typeof ItemType>[]
535
+ const zInferType = ts.factory.createTypeReferenceNode(ts.factory.createQualifiedName(ts.factory.createIdentifier('z'), ts.factory.createIdentifier('infer')), [ts.factory.createTypeQueryNode(ts.factory.createIdentifier(sanitizedItemTypeName), undefined)]);
536
+ return ts.factory.createArrayTypeNode(zInferType);
537
+ }
538
+ // If it's a primitive array, return as-is
539
+ return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(typeName), undefined);
540
+ }
541
+ // Handle custom schema types
542
+ const sanitizedTypeName = this.typeBuilder.sanitizeIdentifier(typeName);
543
+ if (schemas[sanitizedTypeName]) {
544
+ // Return z.infer<typeof TypeName>
545
+ return ts.factory.createTypeReferenceNode(ts.factory.createQualifiedName(ts.factory.createIdentifier('z'), ts.factory.createIdentifier('infer')), [ts.factory.createTypeQueryNode(ts.factory.createIdentifier(sanitizedTypeName), undefined)]);
546
+ }
547
+ // Fallback: return as-is if we can't determine
548
+ return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(typeName), undefined);
514
549
  }
515
550
  buildServerConfiguration(openapi) {
516
551
  const servers = openapi.servers;
@@ -669,6 +704,75 @@ export class TypeScriptCodeGeneratorService {
669
704
  ts.addSyntheticTrailingComment(commentNode, ts.SyntaxKind.SingleLineCommentTrivia, ` ${text}`, true);
670
705
  return ts.factory.createExpressionStatement(commentNode);
671
706
  }
707
+ /**
708
+ * Builds a JSDoc comment string from operation metadata
709
+ */
710
+ buildJSDocComment(summary, description, schema, responseType) {
711
+ const lines = [];
712
+ // Add summary or description as the main comment
713
+ if (summary) {
714
+ lines.push(` * ${summary}`);
715
+ }
716
+ else if (description) {
717
+ // Use first line of description as summary if no summary exists
718
+ const firstLine = description.split('\n')[0]?.trim();
719
+ if (firstLine) {
720
+ lines.push(` * ${firstLine}`);
721
+ }
722
+ }
723
+ // Add full description if it exists and is different from summary
724
+ if (description && description !== summary) {
725
+ const descLines = description.split('\n');
726
+ if (descLines.length > 1 || descLines[0] !== summary) {
727
+ if (lines.length > 0) {
728
+ lines.push(' *');
729
+ }
730
+ descLines.forEach((line) => {
731
+ lines.push(` * ${line.trim() || ''}`);
732
+ });
733
+ }
734
+ }
735
+ // Add @param tags for each parameter
736
+ if (schema.parameters && schema.parameters.length > 0) {
737
+ if (lines.length > 0) {
738
+ lines.push(' *');
739
+ }
740
+ for (const param of schema.parameters) {
741
+ const paramName = this.typeBuilder.sanitizeIdentifier(param.name);
742
+ const paramDesc = param.description ? ` ${param.description}` : '';
743
+ lines.push(` * @param ${paramName}${paramDesc}`);
744
+ }
745
+ }
746
+ // Add @param tag for request body if present
747
+ if (schema.requestBody) {
748
+ const bodyDesc = schema.requestBody.description ? ` ${schema.requestBody.description}` : '';
749
+ lines.push(` * @param body${bodyDesc}`);
750
+ }
751
+ // Add @returns tag if we have a response type
752
+ if (responseType) {
753
+ // Extract the inner type from Promise<T> for JSDoc
754
+ let returnTypeText;
755
+ if (ts.isTypeReferenceNode(responseType) &&
756
+ ts.isIdentifier(responseType.typeName) &&
757
+ responseType.typeName.text === 'Promise' &&
758
+ responseType.typeArguments &&
759
+ responseType.typeArguments.length > 0 &&
760
+ responseType.typeArguments[0]) {
761
+ // Extract the inner type from Promise<T>
762
+ const innerType = responseType.typeArguments[0];
763
+ returnTypeText = this.printer.printNode(ts.EmitHint.Unspecified, innerType, ts.createSourceFile('', '', ts.ScriptTarget.Latest));
764
+ }
765
+ else {
766
+ returnTypeText = this.printer.printNode(ts.EmitHint.Unspecified, responseType, ts.createSourceFile('', '', ts.ScriptTarget.Latest));
767
+ }
768
+ lines.push(` * @returns {${returnTypeText}}`);
769
+ }
770
+ // Build the complete JSDoc comment (without delimiters, as addSyntheticLeadingComment adds them)
771
+ if (lines.length === 0) {
772
+ return '';
773
+ }
774
+ return lines.join('\n');
775
+ }
672
776
  buildZodAST(input) {
673
777
  const [initial, ...rest] = input;
674
778
  const safeInitial = this.ZodAST.safeParse(initial);
@@ -4,7 +4,7 @@ import { resolve } from 'node:path';
4
4
  describe('CLI Integration', () => {
5
5
  describe('--help', () => {
6
6
  it('should display help information', () => {
7
- const result = execSync('npm run build && node ./dist/src/cli.js --help', {
7
+ const result = execSync('yarn build && node ./dist/src/cli.js --help', {
8
8
  encoding: 'utf-8',
9
9
  cwd: resolve(__dirname, '../..'),
10
10
  });
@@ -15,7 +15,7 @@ describe('CLI Integration', () => {
15
15
  });
16
16
  describe('--version', () => {
17
17
  it('should display version information', () => {
18
- const result = execSync('npm run build && node ./dist/src/cli.js --version', {
18
+ const result = execSync('yarn build && node ./dist/src/cli.js --version', {
19
19
  encoding: 'utf-8',
20
20
  cwd: resolve(__dirname, '../..'),
21
21
  });
@@ -1,6 +1,6 @@
1
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:36:35 GMT
2
+ // Built with zod-codegen@1.1.2
3
+ // Latest edit: Wed, 19 Nov 2025 16:26:25 GMT
4
4
  // Source file: ./samples/swagger-petstore.yaml
5
5
  /* eslint-disable */
6
6
  // @ts-nocheck
@@ -144,73 +144,209 @@ export class SwaggerPetstoreOpenAPI30 {
144
144
  if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
145
145
  return await response.json();
146
146
  }
147
- async addPet(body: Pet): Promise<Pet> {
147
+ /**
148
+ * Add a new pet to the store
149
+ * @param body Create a new pet in the store
150
+ * @returns {z.infer<typeof Pet>}
151
+ */
152
+ async addPet(body: Pet): Promise<z.infer<typeof Pet>> {
148
153
  return Pet.parse(
149
154
  await this.#makeRequest('POST', '/pet', {data: body, contentType: 'application/x-www-form-urlencoded'}),
150
155
  );
151
156
  }
152
- async updatePet(body: Pet): Promise<Pet> {
157
+ /**
158
+ * Update an existing pet
159
+ *
160
+ * Update an existing pet by Id
161
+ * @param body Update an existent pet in the store
162
+ * @returns {z.infer<typeof Pet>}
163
+ */
164
+ async updatePet(body: Pet): Promise<z.infer<typeof Pet>> {
153
165
  return Pet.parse(
154
166
  await this.#makeRequest('PUT', '/pet', {data: body, contentType: 'application/x-www-form-urlencoded'}),
155
167
  );
156
168
  }
157
- async findPetsByStatus(status?: string): Promise<Pet[]> {
169
+ /**
170
+ * Finds Pets by status
171
+ *
172
+ * Multiple status values can be provided with comma separated strings
173
+ *
174
+ * @param status Status values that need to be considered for filter
175
+ * @returns {z.infer<typeof Pet>[]}
176
+ */
177
+ async findPetsByStatus(status?: string): Promise<z.infer<typeof Pet>[]> {
158
178
  return await this.#makeRequest('GET', '/pet/findByStatus', {params: {status: status}});
159
179
  }
160
- async findPetsByTags(tags?: string[]): Promise<Pet[]> {
180
+ /**
181
+ * Finds Pets by tags
182
+ *
183
+ * Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
184
+ *
185
+ * @param tags Tags to filter by
186
+ * @returns {z.infer<typeof Pet>[]}
187
+ */
188
+ async findPetsByTags(tags?: string[]): Promise<z.infer<typeof Pet>[]> {
161
189
  return await this.#makeRequest('GET', '/pet/findByTags', {params: {tags: tags}});
162
190
  }
163
- async getPetById(petId: number): Promise<Pet> {
191
+ /**
192
+ * Find pet by ID
193
+ *
194
+ * Returns a single pet
195
+ *
196
+ * @param petId ID of pet to return
197
+ * @returns {z.infer<typeof Pet>}
198
+ */
199
+ async getPetById(petId: number): Promise<z.infer<typeof Pet>> {
164
200
  return Pet.parse(await this.#makeRequest('GET', `/pet/${petId}`, {}));
165
201
  }
202
+ /**
203
+ * Updates a pet in the store with form data
204
+ *
205
+ * @param petId ID of pet that needs to be updated
206
+ * @param name Name of pet that needs to be updated
207
+ * @param status Status of pet that needs to be updated
208
+ * @returns {void}
209
+ */
166
210
  async updatePetWithForm(petId: number, name?: string, status?: string): Promise<void> {
167
211
  return await this.#makeRequest('POST', `/pet/${petId}`, {params: {name: name, status: status}});
168
212
  }
213
+ /**
214
+ * Deletes a pet
215
+ *
216
+ * delete a pet
217
+ *
218
+ * @param api_key
219
+ * @param petId Pet id to delete
220
+ * @returns {void}
221
+ */
169
222
  async deletePet(petId: number): Promise<void> {
170
223
  return await this.#makeRequest('DELETE', `/pet/${petId}`, {});
171
224
  }
172
- async uploadFile(petId: number, additionalMetadata?: string): Promise<ApiResponse> {
225
+ /**
226
+ * uploads an image
227
+ *
228
+ * @param petId ID of pet to update
229
+ * @param additionalMetadata Additional Metadata
230
+ * @param body
231
+ * @returns {z.infer<typeof ApiResponse>}
232
+ */
233
+ async uploadFile(petId: number, additionalMetadata?: string): Promise<z.infer<typeof ApiResponse>> {
173
234
  return ApiResponse.parse(
174
235
  await this.#makeRequest('POST', `/pet/${petId}/uploadImage`, {params: {additionalMetadata: additionalMetadata}}),
175
236
  );
176
237
  }
238
+ /**
239
+ * Returns pet inventories by status
240
+ *
241
+ * Returns a map of status codes to quantities
242
+ * @returns {Record<string, unknown>}
243
+ */
177
244
  async getInventory(): Promise<Record<string, unknown>> {
178
245
  return await this.#makeRequest('GET', '/store/inventory', {});
179
246
  }
180
- async placeOrder(body?: Order): Promise<Order> {
247
+ /**
248
+ * Place an order for a pet
249
+ *
250
+ * Place a new order in the store
251
+ * @param body
252
+ * @returns {z.infer<typeof Order>}
253
+ */
254
+ async placeOrder(body?: Order): Promise<z.infer<typeof Order>> {
181
255
  return Order.parse(
182
256
  await this.#makeRequest('POST', '/store/order', {data: body, contentType: 'application/x-www-form-urlencoded'}),
183
257
  );
184
258
  }
185
- async getOrderById(orderId: number): Promise<Order> {
259
+ /**
260
+ * Find purchase order by ID
261
+ *
262
+ * For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.
263
+ *
264
+ * @param orderId ID of order that needs to be fetched
265
+ * @returns {z.infer<typeof Order>}
266
+ */
267
+ async getOrderById(orderId: number): Promise<z.infer<typeof Order>> {
186
268
  return Order.parse(await this.#makeRequest('GET', `/store/order/${orderId}`, {}));
187
269
  }
270
+ /**
271
+ * Delete purchase order by ID
272
+ *
273
+ * For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
274
+ *
275
+ * @param orderId ID of the order that needs to be deleted
276
+ * @returns {void}
277
+ */
188
278
  async deleteOrder(orderId: number): Promise<void> {
189
279
  return await this.#makeRequest('DELETE', `/store/order/${orderId}`, {});
190
280
  }
191
- async createUser(body?: User): Promise<User> {
281
+ /**
282
+ * Create user
283
+ *
284
+ * This can only be done by the logged in user.
285
+ * @param body Created user object
286
+ * @returns {z.infer<typeof User>}
287
+ */
288
+ async createUser(body?: User): Promise<z.infer<typeof User>> {
192
289
  return User.parse(
193
290
  await this.#makeRequest('POST', '/user', {data: body, contentType: 'application/x-www-form-urlencoded'}),
194
291
  );
195
292
  }
196
- async createUsersWithListInput(body?: User[]): Promise<User> {
293
+ /**
294
+ * Creates list of users with given input array
295
+ * @param body
296
+ * @returns {z.infer<typeof User>}
297
+ */
298
+ async createUsersWithListInput(body?: User[]): Promise<z.infer<typeof User>> {
197
299
  return User.parse(await this.#makeRequest('POST', '/user/createWithList', {data: body}));
198
300
  }
301
+ /**
302
+ * Logs user into the system
303
+ *
304
+ * @param username The user name for login
305
+ * @param password The password for login in clear text
306
+ * @returns {string}
307
+ */
199
308
  async loginUser(username?: string, password?: string): Promise<string> {
200
309
  return await this.#makeRequest('GET', '/user/login', {params: {username: username, password: password}});
201
310
  }
311
+ /**
312
+ * Logs out current logged in user session
313
+ * @returns {void}
314
+ */
202
315
  async logoutUser(): Promise<void> {
203
316
  return await this.#makeRequest('GET', '/user/logout', {});
204
317
  }
205
- async getUserByName(username: string): Promise<User> {
318
+ /**
319
+ * Get user by user name
320
+ *
321
+ * @param username The name that needs to be fetched. Use user1 for testing.
322
+ * @returns {z.infer<typeof User>}
323
+ */
324
+ async getUserByName(username: string): Promise<z.infer<typeof User>> {
206
325
  return User.parse(await this.#makeRequest('GET', `/user/${username}`, {}));
207
326
  }
327
+ /**
328
+ * Update user
329
+ *
330
+ * This can only be done by the logged in user.
331
+ *
332
+ * @param username name that need to be deleted
333
+ * @param body Update an existent user in the store
334
+ * @returns {void}
335
+ */
208
336
  async updateUser(username: string, body?: User): Promise<void> {
209
337
  return await this.#makeRequest('PUT', `/user/${username}`, {
210
338
  data: body,
211
339
  contentType: 'application/x-www-form-urlencoded',
212
340
  });
213
341
  }
342
+ /**
343
+ * Delete user
344
+ *
345
+ * This can only be done by the logged in user.
346
+ *
347
+ * @param username The name that needs to be deleted
348
+ * @returns {void}
349
+ */
214
350
  async deleteUser(username: string): Promise<void> {
215
351
  return await this.#makeRequest('DELETE', `/user/${username}`, {});
216
352
  }
package/package.json CHANGED
@@ -8,13 +8,13 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@apidevtools/swagger-parser": "^12.1.0",
11
- "debug": "^4.3.7",
11
+ "debug": "^4.4.3",
12
12
  "js-yaml": "^4.1.1",
13
13
  "jsonpath": "^1.1.1",
14
14
  "loud-rejection": "^2.2.0",
15
15
  "openapi-types": "^12.1.3",
16
16
  "openapi-typescript": "^7.10.1",
17
- "path-to-regexp": "^8.2.0",
17
+ "path-to-regexp": "^8.3.0",
18
18
  "prettier": "^3.6.2",
19
19
  "typescript": "^5.9.3",
20
20
  "url-pattern": "^1.0.3",
@@ -44,8 +44,8 @@
44
44
  "@types/js-yaml": "^4.0.9",
45
45
  "@types/jsonpath": "^0.2.4",
46
46
  "@types/node": "^24.10.1",
47
- "@types/yargs": "^17.0.34",
48
- "@vitest/coverage-v8": "^4.0.8",
47
+ "@types/yargs": "^17.0.35",
48
+ "@vitest/coverage-v8": "^4.0.9",
49
49
  "eslint": "^9.39.1",
50
50
  "eslint-config-prettier": "^10.1.8",
51
51
  "husky": "^9.1.7",
@@ -54,7 +54,8 @@
54
54
  "ts-node": "^10.9.2",
55
55
  "typescript-eslint": "^8.46.4",
56
56
  "undici": "^7.16.0",
57
- "vitest": "^4.0.8"
57
+ "vitest": "^4.0.8",
58
+ "yarn-audit-fix": "^10.1.1"
58
59
  },
59
60
  "optionalDependencies": {
60
61
  "undici": "^7.16.0"
@@ -77,11 +78,20 @@
77
78
  "engines": {
78
79
  "node": ">=24.11.1"
79
80
  },
81
+ "overrides": {
82
+ "npm": {
83
+ "glob": "^11.1.0",
84
+ "tar": "^7.5.2"
85
+ },
86
+ "glob": "^11.1.0",
87
+ "tar": "^7.5.2"
88
+ },
80
89
  "scripts": {
90
+ "audit:fix": "yarn-audit-fix",
81
91
  "build": "rm -rf dist && tsc --project tsconfig.json && cp -r src/assets dist/src/ && chmod +x ./dist/src/cli.js",
82
92
  "build:native": "rm -rf dist && npx tsgo --project tsconfig.json && cp -r src/assets dist/src/ && chmod +x ./dist/src/cli.js",
83
93
  "build:watch": "tsc --project tsconfig.json --watch",
84
- "dev": "npm run build && node ./dist/src/cli.js --input ./samples/swagger-petstore.yaml --output ./examples/petstore && npm run format && npm run lint",
94
+ "dev": "yarn build && node ./dist/src/cli.js --input ./samples/swagger-petstore.yaml --output ./examples/petstore && yarn format && yarn lint",
85
95
  "lint": "eslint src --fix",
86
96
  "lint:check": "eslint src",
87
97
  "format": "prettier src --write --log-level error",
@@ -93,12 +103,12 @@
93
103
  "test:ui": "vitest --ui",
94
104
  "manifest:update": "ts-node scripts/update-manifest.ts",
95
105
  "prepare": "husky",
96
- "prepublishOnly": "npm run build && npm run test && npm run lint:check && npm run type-check",
106
+ "prepublishOnly": "yarn build && yarn test && yarn lint:check && yarn type-check",
97
107
  "clean": "rm -rf dist coverage node_modules/.cache",
98
- "validate": "npm run type-check && npm run lint:check && npm run format:check && npm run test",
108
+ "validate": "yarn type-check && yarn lint:check && yarn format:check && yarn test",
99
109
  "validate:examples": "tsc -p ./tsconfig.examples.json --noEmit",
100
110
  "release": "semantic-release",
101
111
  "release:dry": "semantic-release --dry-run"
102
112
  },
103
- "version": "1.1.2"
113
+ "version": "1.2.1"
104
114
  }
@@ -962,7 +962,7 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
962
962
  statements.push(ts.factory.createReturnStatement(ts.factory.createAwaitExpression(makeRequestCall)));
963
963
  }
964
964
 
965
- return ts.factory.createMethodDeclaration(
965
+ const methodDeclaration = ts.factory.createMethodDeclaration(
966
966
  [ts.factory.createToken(ts.SyntaxKind.AsyncKeyword)],
967
967
  undefined,
968
968
  ts.factory.createIdentifier(String(schema.operationId)),
@@ -972,6 +972,22 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
972
972
  responseType,
973
973
  ts.factory.createBlock(statements, true),
974
974
  );
975
+
976
+ // Add JSDoc comment if summary or description exists
977
+ const jsdocComment = this.buildJSDocComment(schema.summary, schema.description, schema, responseType);
978
+
979
+ if (jsdocComment) {
980
+ // addSyntheticLeadingComment expects the comment content without delimiters
981
+ // and will wrap it in /** */ for JSDoc-style comments
982
+ ts.addSyntheticLeadingComment(
983
+ methodDeclaration,
984
+ ts.SyntaxKind.MultiLineCommentTrivia,
985
+ `*\n${jsdocComment}\n `,
986
+ true,
987
+ );
988
+ }
989
+
990
+ return methodDeclaration;
975
991
  }
976
992
 
977
993
  private buildPathExpression(path: string, pathParams: {name: string; type: string}[]): ts.Expression {
@@ -1248,10 +1264,48 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
1248
1264
 
1249
1265
  const responseSchema = response.content['application/json'].schema;
1250
1266
  const typeName = this.getSchemaTypeName(responseSchema, schemas);
1267
+ const inferredType = this.wrapTypeWithZInfer(typeName, schemas);
1251
1268
 
1252
- return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Promise'), [
1253
- ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(typeName), undefined),
1254
- ]);
1269
+ return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier('Promise'), [inferredType]);
1270
+ }
1271
+
1272
+ private wrapTypeWithZInfer(typeName: string, schemas: Record<string, ts.VariableStatement>): ts.TypeNode {
1273
+ // Primitive types and Record types don't need z.infer
1274
+ const primitiveTypes = ['string', 'number', 'boolean', 'unknown'];
1275
+ if (primitiveTypes.includes(typeName) || typeName.startsWith('Record<')) {
1276
+ return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(typeName), undefined);
1277
+ }
1278
+
1279
+ // Handle array types like "Pet[]"
1280
+ if (typeName.endsWith('[]')) {
1281
+ const itemTypeName = typeName.slice(0, -2);
1282
+ const sanitizedItemTypeName = this.typeBuilder.sanitizeIdentifier(itemTypeName);
1283
+
1284
+ // Check if the item type is a custom schema
1285
+ if (schemas[sanitizedItemTypeName]) {
1286
+ // Return z.infer<typeof ItemType>[]
1287
+ const zInferType = ts.factory.createTypeReferenceNode(
1288
+ ts.factory.createQualifiedName(ts.factory.createIdentifier('z'), ts.factory.createIdentifier('infer')),
1289
+ [ts.factory.createTypeQueryNode(ts.factory.createIdentifier(sanitizedItemTypeName), undefined)],
1290
+ );
1291
+ return ts.factory.createArrayTypeNode(zInferType);
1292
+ }
1293
+ // If it's a primitive array, return as-is
1294
+ return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(typeName), undefined);
1295
+ }
1296
+
1297
+ // Handle custom schema types
1298
+ const sanitizedTypeName = this.typeBuilder.sanitizeIdentifier(typeName);
1299
+ if (schemas[sanitizedTypeName]) {
1300
+ // Return z.infer<typeof TypeName>
1301
+ return ts.factory.createTypeReferenceNode(
1302
+ ts.factory.createQualifiedName(ts.factory.createIdentifier('z'), ts.factory.createIdentifier('infer')),
1303
+ [ts.factory.createTypeQueryNode(ts.factory.createIdentifier(sanitizedTypeName), undefined)],
1304
+ );
1305
+ }
1306
+
1307
+ // Fallback: return as-is if we can't determine
1308
+ return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier(typeName), undefined);
1255
1309
  }
1256
1310
 
1257
1311
  private buildServerConfiguration(openapi: OpenApiSpecType): ts.Statement[] {
@@ -1670,6 +1724,96 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
1670
1724
  return ts.factory.createExpressionStatement(commentNode);
1671
1725
  }
1672
1726
 
1727
+ /**
1728
+ * Builds a JSDoc comment string from operation metadata
1729
+ */
1730
+ private buildJSDocComment(
1731
+ summary: string | undefined,
1732
+ description: string | undefined,
1733
+ schema: MethodSchemaType,
1734
+ responseType: ts.TypeNode | undefined,
1735
+ ): string {
1736
+ const lines: string[] = [];
1737
+
1738
+ // Add summary or description as the main comment
1739
+ if (summary) {
1740
+ lines.push(` * ${summary}`);
1741
+ } else if (description) {
1742
+ // Use first line of description as summary if no summary exists
1743
+ const firstLine = description.split('\n')[0]?.trim();
1744
+ if (firstLine) {
1745
+ lines.push(` * ${firstLine}`);
1746
+ }
1747
+ }
1748
+
1749
+ // Add full description if it exists and is different from summary
1750
+ if (description && description !== summary) {
1751
+ const descLines = description.split('\n');
1752
+ if (descLines.length > 1 || descLines[0] !== summary) {
1753
+ if (lines.length > 0) {
1754
+ lines.push(' *');
1755
+ }
1756
+ descLines.forEach((line) => {
1757
+ lines.push(` * ${line.trim() || ''}`);
1758
+ });
1759
+ }
1760
+ }
1761
+
1762
+ // Add @param tags for each parameter
1763
+ if (schema.parameters && schema.parameters.length > 0) {
1764
+ if (lines.length > 0) {
1765
+ lines.push(' *');
1766
+ }
1767
+ for (const param of schema.parameters) {
1768
+ const paramName = this.typeBuilder.sanitizeIdentifier(param.name);
1769
+ const paramDesc = param.description ? ` ${param.description}` : '';
1770
+ lines.push(` * @param ${paramName}${paramDesc}`);
1771
+ }
1772
+ }
1773
+
1774
+ // Add @param tag for request body if present
1775
+ if (schema.requestBody) {
1776
+ const bodyDesc = schema.requestBody.description ? ` ${schema.requestBody.description}` : '';
1777
+ lines.push(` * @param body${bodyDesc}`);
1778
+ }
1779
+
1780
+ // Add @returns tag if we have a response type
1781
+ if (responseType) {
1782
+ // Extract the inner type from Promise<T> for JSDoc
1783
+ let returnTypeText: string;
1784
+ if (
1785
+ ts.isTypeReferenceNode(responseType) &&
1786
+ ts.isIdentifier(responseType.typeName) &&
1787
+ responseType.typeName.text === 'Promise' &&
1788
+ responseType.typeArguments &&
1789
+ responseType.typeArguments.length > 0 &&
1790
+ responseType.typeArguments[0]
1791
+ ) {
1792
+ // Extract the inner type from Promise<T>
1793
+ const innerType = responseType.typeArguments[0];
1794
+ returnTypeText = this.printer.printNode(
1795
+ ts.EmitHint.Unspecified,
1796
+ innerType,
1797
+ ts.createSourceFile('', '', ts.ScriptTarget.Latest),
1798
+ );
1799
+ } else {
1800
+ returnTypeText = this.printer.printNode(
1801
+ ts.EmitHint.Unspecified,
1802
+ responseType,
1803
+ ts.createSourceFile('', '', ts.ScriptTarget.Latest),
1804
+ );
1805
+ }
1806
+ lines.push(` * @returns {${returnTypeText}}`);
1807
+ }
1808
+
1809
+ // Build the complete JSDoc comment (without delimiters, as addSyntheticLeadingComment adds them)
1810
+ if (lines.length === 0) {
1811
+ return '';
1812
+ }
1813
+
1814
+ return lines.join('\n');
1815
+ }
1816
+
1673
1817
  private buildZodAST(input: (string | z.infer<typeof this.ZodAST>)[]): ts.CallExpression {
1674
1818
  const [initial, ...rest] = input;
1675
1819
 
@@ -5,7 +5,7 @@ import {resolve} from 'node:path';
5
5
  describe('CLI Integration', () => {
6
6
  describe('--help', () => {
7
7
  it('should display help information', () => {
8
- const result = execSync('npm run build && node ./dist/src/cli.js --help', {
8
+ const result = execSync('yarn build && node ./dist/src/cli.js --help', {
9
9
  encoding: 'utf-8',
10
10
  cwd: resolve(__dirname, '../..'),
11
11
  });
@@ -18,7 +18,7 @@ describe('CLI Integration', () => {
18
18
 
19
19
  describe('--version', () => {
20
20
  it('should display version information', () => {
21
- const result = execSync('npm run build && node ./dist/src/cli.js --version', {
21
+ const result = execSync('yarn build && node ./dist/src/cli.js --version', {
22
22
  encoding: 'utf-8',
23
23
  cwd: resolve(__dirname, '../..'),
24
24
  });