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.
- package/.github/workflows/ci.yml +17 -17
- package/.github/workflows/release.yml +8 -8
- package/CHANGELOG.md +12 -0
- package/CONTRIBUTING.md +12 -12
- package/PERFORMANCE.md +4 -4
- package/README.md +27 -27
- package/SECURITY.md +1 -1
- package/dist/src/services/code-generator.service.js +108 -4
- package/dist/tests/integration/cli.test.js +2 -2
- package/examples/petstore/type.ts +149 -13
- package/package.json +19 -9
- package/src/services/code-generator.service.ts +148 -4
- package/tests/integration/cli.test.ts +2 -2
package/.github/workflows/ci.yml
CHANGED
|
@@ -29,25 +29,25 @@ jobs:
|
|
|
29
29
|
uses: actions/setup-node@v5
|
|
30
30
|
with:
|
|
31
31
|
node-version: ${{ matrix.node-version }}
|
|
32
|
-
cache: '
|
|
32
|
+
cache: 'yarn'
|
|
33
33
|
|
|
34
34
|
- name: Install dependencies
|
|
35
|
-
run:
|
|
35
|
+
run: yarn install --frozen-lockfile
|
|
36
36
|
|
|
37
37
|
- name: Run type check
|
|
38
|
-
run:
|
|
38
|
+
run: yarn type-check
|
|
39
39
|
|
|
40
40
|
- name: Run linter
|
|
41
|
-
run:
|
|
41
|
+
run: yarn lint:check
|
|
42
42
|
|
|
43
43
|
- name: Run formatter check
|
|
44
|
-
run:
|
|
44
|
+
run: yarn format:check
|
|
45
45
|
|
|
46
46
|
- name: Build project
|
|
47
|
-
run:
|
|
47
|
+
run: yarn build
|
|
48
48
|
|
|
49
49
|
- name: Run tests
|
|
50
|
-
run:
|
|
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: '
|
|
73
|
+
cache: 'yarn'
|
|
74
74
|
|
|
75
75
|
- name: Install dependencies
|
|
76
|
-
run:
|
|
76
|
+
run: yarn install --frozen-lockfile
|
|
77
77
|
|
|
78
78
|
- name: Run security audit
|
|
79
|
-
run:
|
|
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: '
|
|
94
|
+
cache: 'yarn'
|
|
95
95
|
|
|
96
96
|
- name: Install dependencies
|
|
97
|
-
run:
|
|
97
|
+
run: yarn install --frozen-lockfile
|
|
98
98
|
|
|
99
99
|
- name: Build project
|
|
100
|
-
run:
|
|
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: '
|
|
132
|
+
cache: 'yarn'
|
|
133
133
|
|
|
134
134
|
- name: Install dependencies
|
|
135
|
-
run:
|
|
135
|
+
run: yarn install --frozen-lockfile
|
|
136
136
|
|
|
137
137
|
- name: Build project
|
|
138
|
-
run:
|
|
138
|
+
run: yarn build
|
|
139
139
|
|
|
140
140
|
- name: Preview semantic-release
|
|
141
141
|
env:
|
|
142
142
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
143
|
-
run:
|
|
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: '
|
|
32
|
+
cache: 'yarn'
|
|
33
33
|
registry-url: 'https://registry.npmjs.org'
|
|
34
34
|
|
|
35
35
|
- name: Install dependencies
|
|
36
|
-
run:
|
|
36
|
+
run: yarn install --frozen-lockfile
|
|
37
37
|
|
|
38
38
|
- name: Run type check
|
|
39
|
-
run:
|
|
39
|
+
run: yarn type-check
|
|
40
40
|
|
|
41
41
|
- name: Run linter check
|
|
42
|
-
run:
|
|
42
|
+
run: yarn lint:check
|
|
43
43
|
|
|
44
44
|
- name: Run formatter check
|
|
45
|
-
run:
|
|
45
|
+
run: yarn format:check
|
|
46
46
|
|
|
47
47
|
- name: Run tests with coverage
|
|
48
|
-
run:
|
|
48
|
+
run: yarn test:coverage
|
|
49
49
|
|
|
50
50
|
- name: Build project
|
|
51
|
-
run:
|
|
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:
|
|
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
|
-
-
|
|
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
|
-
|
|
40
|
+
yarn install
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
3. **Build the Project**
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
|
-
|
|
46
|
+
yarn build
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
4. **Run Tests**
|
|
50
50
|
|
|
51
51
|
```bash
|
|
52
|
-
|
|
52
|
+
yarn test
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
5. **Run Development Mode**
|
|
56
56
|
```bash
|
|
57
|
-
|
|
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
|
-
|
|
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
|
-
|
|
174
|
+
yarn lint
|
|
175
175
|
|
|
176
176
|
# Format code
|
|
177
|
-
|
|
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
|
-
|
|
221
|
+
yarn test
|
|
222
222
|
|
|
223
223
|
# Run tests in watch mode
|
|
224
|
-
|
|
224
|
+
yarn test:watch
|
|
225
225
|
|
|
226
226
|
# Run with coverage
|
|
227
|
-
|
|
227
|
+
yarn test:coverage
|
|
228
228
|
|
|
229
229
|
# Run specific test file
|
|
230
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
55
|
+
time yarn build
|
|
56
56
|
|
|
57
57
|
# Native TypeScript
|
|
58
|
-
time
|
|
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
|
-
|
|
31
|
+
yarn global add zod-codegen
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
### Project Installation
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
|
|
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
|
-
-
|
|
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
|
-
|
|
391
|
+
yarn install
|
|
392
392
|
|
|
393
393
|
# Build the project
|
|
394
|
-
|
|
394
|
+
yarn build
|
|
395
395
|
|
|
396
396
|
# Run tests
|
|
397
|
-
|
|
397
|
+
yarn test
|
|
398
398
|
|
|
399
399
|
# Run linting
|
|
400
|
-
|
|
400
|
+
yarn lint
|
|
401
401
|
|
|
402
402
|
# Format code
|
|
403
|
-
|
|
403
|
+
yarn format
|
|
404
404
|
```
|
|
405
405
|
|
|
406
406
|
### Testing
|
|
407
407
|
|
|
408
408
|
```bash
|
|
409
409
|
# Run all tests
|
|
410
|
-
|
|
410
|
+
yarn test
|
|
411
411
|
|
|
412
412
|
# Run tests in watch mode
|
|
413
|
-
|
|
413
|
+
yarn test:watch
|
|
414
414
|
|
|
415
415
|
# Run tests with coverage
|
|
416
|
-
|
|
416
|
+
yarn test:coverage
|
|
417
417
|
|
|
418
418
|
# Run tests with UI
|
|
419
|
-
|
|
419
|
+
yarn test:ui
|
|
420
420
|
```
|
|
421
421
|
|
|
422
422
|
### Available Scripts
|
|
423
423
|
|
|
424
|
-
| Script
|
|
425
|
-
|
|
|
426
|
-
| `
|
|
427
|
-
| `
|
|
428
|
-
| `
|
|
429
|
-
| `
|
|
430
|
-
| `
|
|
431
|
-
| `
|
|
432
|
-
| `
|
|
433
|
-
| `
|
|
434
|
-
| `
|
|
435
|
-
| `
|
|
436
|
-
| `
|
|
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: `
|
|
454
|
-
5. Run validation: `
|
|
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
|
-
|
|
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
|
-
|
|
512
|
-
|
|
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('
|
|
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('
|
|
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.
|
|
3
|
-
// Latest edit:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
48
|
-
"@vitest/coverage-v8": "^4.0.
|
|
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": "
|
|
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": "
|
|
106
|
+
"prepublishOnly": "yarn build && yarn test && yarn lint:check && yarn type-check",
|
|
97
107
|
"clean": "rm -rf dist coverage node_modules/.cache",
|
|
98
|
-
"validate": "
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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('
|
|
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('
|
|
21
|
+
const result = execSync('yarn build && node ./dist/src/cli.js --version', {
|
|
22
22
|
encoding: 'utf-8',
|
|
23
23
|
cwd: resolve(__dirname, '../..'),
|
|
24
24
|
});
|