zod-codegen 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +18 -18
- package/.github/workflows/release.yml +8 -8
- package/CHANGELOG.md +26 -0
- package/dist/src/services/code-generator.service.d.ts +15 -0
- package/dist/src/services/code-generator.service.d.ts.map +1 -1
- package/dist/src/services/code-generator.service.js +124 -6
- package/package.json +14 -19
- package/src/services/code-generator.service.ts +160 -11
- package/tests/unit/code-generator.test.ts +199 -3
- package/tests/unit/generator.test.ts +2 -2
- package/tsconfig.json +1 -1
- package/.claude/settings.local.json +0 -43
- package/dist/scripts/update-manifest.d.ts +0 -14
- package/dist/scripts/update-manifest.d.ts.map +0 -1
- package/dist/scripts/update-manifest.js +0 -31
- package/dist/tests/integration/cli.test.d.ts +0 -2
- package/dist/tests/integration/cli.test.d.ts.map +0 -1
- package/dist/tests/integration/cli.test.js +0 -25
- package/dist/tests/unit/code-generator.test.d.ts +0 -2
- package/dist/tests/unit/code-generator.test.d.ts.map +0 -1
- package/dist/tests/unit/code-generator.test.js +0 -459
- package/dist/tests/unit/file-reader.test.d.ts +0 -2
- package/dist/tests/unit/file-reader.test.d.ts.map +0 -1
- package/dist/tests/unit/file-reader.test.js +0 -110
- package/dist/tests/unit/generator.test.d.ts +0 -2
- package/dist/tests/unit/generator.test.d.ts.map +0 -1
- package/dist/tests/unit/generator.test.js +0 -100
- package/dist/tests/unit/naming-convention.test.d.ts +0 -2
- package/dist/tests/unit/naming-convention.test.d.ts.map +0 -1
- package/dist/tests/unit/naming-convention.test.js +0 -231
- package/scripts/republish-versions.sh +0 -94
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: 'npm'
|
|
33
33
|
|
|
34
34
|
- name: Install dependencies
|
|
35
|
-
run:
|
|
35
|
+
run: npm ci
|
|
36
36
|
|
|
37
37
|
- name: Run type check
|
|
38
|
-
run:
|
|
38
|
+
run: npm run type-check
|
|
39
39
|
|
|
40
40
|
- name: Run linter
|
|
41
|
-
run:
|
|
41
|
+
run: npm run lint:check
|
|
42
42
|
|
|
43
43
|
- name: Run formatter check
|
|
44
|
-
run:
|
|
44
|
+
run: npm run format:check
|
|
45
45
|
|
|
46
46
|
- name: Build project
|
|
47
|
-
run:
|
|
47
|
+
run: npm run build
|
|
48
48
|
|
|
49
49
|
- name: Run tests
|
|
50
|
-
run:
|
|
50
|
+
run: npm run 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: 'npm'
|
|
74
74
|
|
|
75
75
|
- name: Install dependencies
|
|
76
|
-
run:
|
|
76
|
+
run: npm ci
|
|
77
77
|
|
|
78
78
|
- name: Run security audit
|
|
79
|
-
run:
|
|
79
|
+
run: npm audit --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: 'npm'
|
|
95
95
|
|
|
96
96
|
- name: Install dependencies
|
|
97
|
-
run:
|
|
97
|
+
run: npm ci
|
|
98
98
|
|
|
99
99
|
- name: Build project
|
|
100
|
-
run:
|
|
100
|
+
run: npm run build
|
|
101
101
|
|
|
102
102
|
- name: Test CLI
|
|
103
103
|
run: |
|
|
@@ -107,7 +107,7 @@ jobs:
|
|
|
107
107
|
test -e "generated/type.ts"
|
|
108
108
|
|
|
109
109
|
- name: Upload build artifacts
|
|
110
|
-
uses: actions/upload-artifact@
|
|
110
|
+
uses: actions/upload-artifact@v6
|
|
111
111
|
with:
|
|
112
112
|
name: build-${{ github.sha }}
|
|
113
113
|
path: dist/
|
|
@@ -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: 'npm'
|
|
133
133
|
|
|
134
134
|
- name: Install dependencies
|
|
135
|
-
run:
|
|
135
|
+
run: npm ci
|
|
136
136
|
|
|
137
137
|
- name: Build project
|
|
138
|
-
run:
|
|
138
|
+
run: npm run build
|
|
139
139
|
|
|
140
140
|
- name: Preview semantic-release
|
|
141
141
|
env:
|
|
142
142
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
143
|
-
run:
|
|
143
|
+
run: npm run 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: 'npm'
|
|
33
33
|
registry-url: 'https://registry.npmjs.org'
|
|
34
34
|
|
|
35
35
|
- name: Install dependencies
|
|
36
|
-
run:
|
|
36
|
+
run: npm ci
|
|
37
37
|
|
|
38
38
|
- name: Run type check
|
|
39
|
-
run:
|
|
39
|
+
run: npm run type-check
|
|
40
40
|
|
|
41
41
|
- name: Run linter check
|
|
42
|
-
run:
|
|
42
|
+
run: npm run lint:check
|
|
43
43
|
|
|
44
44
|
- name: Run formatter check
|
|
45
|
-
run:
|
|
45
|
+
run: npm run format:check
|
|
46
46
|
|
|
47
47
|
- name: Run tests with coverage
|
|
48
|
-
run:
|
|
48
|
+
run: npm run test:coverage
|
|
49
49
|
|
|
50
50
|
- name: Build project
|
|
51
|
-
run:
|
|
51
|
+
run: npm run 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: npm run release
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,29 @@
|
|
|
1
|
+
## 1.5.0 (2025-12-22)
|
|
2
|
+
|
|
3
|
+
- Merge pull request #44 from julienandreu/dependabot/github_actions/actions/upload-artifact-6 ([cebfd55](https://github.com/julienandreu/zod-codegen/commit/cebfd55)), closes [#44](https://github.com/julienandreu/zod-codegen/issues/44)
|
|
4
|
+
- Merge pull request #45 from julienandreu/dependabot/npm_and_yarn/dev-dependencies-ab633d47b1 ([8663f40](https://github.com/julienandreu/zod-codegen/commit/8663f40)), closes [#45](https://github.com/julienandreu/zod-codegen/issues/45)
|
|
5
|
+
- Merge pull request #46 from julienandreu/dependabot/npm_and_yarn/production-dependencies-eefc12583a ([eac5279](https://github.com/julienandreu/zod-codegen/commit/eac5279)), closes [#46](https://github.com/julienandreu/zod-codegen/issues/46)
|
|
6
|
+
- Merge pull request #47 from julienandreu/dependabot/npm_and_yarn/types/node-25.0.2 ([687ce64](https://github.com/julienandreu/zod-codegen/commit/687ce64)), closes [#47](https://github.com/julienandreu/zod-codegen/issues/47)
|
|
7
|
+
- Merge pull request #49 from julienandreu/dependabot/npm_and_yarn/production-dependencies-5419ff3310 ([1d6485c](https://github.com/julienandreu/zod-codegen/commit/1d6485c)), closes [#49](https://github.com/julienandreu/zod-codegen/issues/49)
|
|
8
|
+
- Merge pull request #50 from julienandreu/feat/circular-dependency-detection ([527cbb6](https://github.com/julienandreu/zod-codegen/commit/527cbb6)), closes [#50](https://github.com/julienandreu/zod-codegen/issues/50)
|
|
9
|
+
- feat: detect circular dependencies and wrap with z.lazy() ([4f5f545](https://github.com/julienandreu/zod-codegen/commit/4f5f545))
|
|
10
|
+
- chore(deps-dev): bump @types/node from 24.10.1 to 25.0.2 ([da9bbc7](https://github.com/julienandreu/zod-codegen/commit/da9bbc7))
|
|
11
|
+
- chore(deps-dev): bump the dev-dependencies group with 2 updates ([29ad3ef](https://github.com/julienandreu/zod-codegen/commit/29ad3ef))
|
|
12
|
+
- chore(deps): bump zod in the production-dependencies group ([391d6ea](https://github.com/julienandreu/zod-codegen/commit/391d6ea))
|
|
13
|
+
- chore(deps): bump zod in the production-dependencies group ([41177c8](https://github.com/julienandreu/zod-codegen/commit/41177c8))
|
|
14
|
+
- ci(deps): bump actions/upload-artifact from 4 to 6 ([c19e254](https://github.com/julienandreu/zod-codegen/commit/c19e254))
|
|
15
|
+
|
|
16
|
+
## <small>1.4.1 (2025-12-08)</small>
|
|
17
|
+
|
|
18
|
+
- Merge branch 'refactor/build-config-and-class-exports' of github.com:julienandreu/zod-codegen into r ([bdd9bc5](https://github.com/julienandreu/zod-codegen/commit/bdd9bc5))
|
|
19
|
+
- Merge pull request #41 from julienandreu/dependabot/npm_and_yarn/tar-7.5.2 ([192cd47](https://github.com/julienandreu/zod-codegen/commit/192cd47)), closes [#41](https://github.com/julienandreu/zod-codegen/issues/41)
|
|
20
|
+
- Merge pull request #43 from julienandreu/refactor/build-config-and-class-exports ([61de21b](https://github.com/julienandreu/zod-codegen/commit/61de21b)), closes [#43](https://github.com/julienandreu/zod-codegen/issues/43)
|
|
21
|
+
- refactor: improve build configuration and class exports ([060f714](https://github.com/julienandreu/zod-codegen/commit/060f714))
|
|
22
|
+
- refactor: improve build configuration and class exports ([b8771ad](https://github.com/julienandreu/zod-codegen/commit/b8771ad))
|
|
23
|
+
- refactor: remove tsconfig.build.json and use tsconfig.json directly ([407c46a](https://github.com/julienandreu/zod-codegen/commit/407c46a))
|
|
24
|
+
- chore: restore yarn.lock for yarn 4 with corepack ([a93f018](https://github.com/julienandreu/zod-codegen/commit/a93f018))
|
|
25
|
+
- chore(deps): bump tar from 7.5.1 to 7.5.2 ([b867dce](https://github.com/julienandreu/zod-codegen/commit/b867dce))
|
|
26
|
+
|
|
1
27
|
## 1.4.0 (2025-12-01)
|
|
2
28
|
|
|
3
29
|
- Merge pull request #40 from julienandreu/feat/naming-conventions-and-improvements ([d7c8146](https://github.com/julienandreu/zod-codegen/commit/d7c8146)), closes [#40](https://github.com/julienandreu/zod-codegen/issues/40)
|
|
@@ -8,6 +8,8 @@ export declare class TypeScriptCodeGeneratorService implements CodeGenerator, Sc
|
|
|
8
8
|
private readonly printer;
|
|
9
9
|
private readonly namingConvention;
|
|
10
10
|
private readonly operationNameTransformer;
|
|
11
|
+
private circularSchemas;
|
|
12
|
+
private currentSchemaName;
|
|
11
13
|
constructor(options?: GeneratorOptions);
|
|
12
14
|
private readonly ZodAST;
|
|
13
15
|
generate(spec: OpenApiSpecType): string;
|
|
@@ -52,6 +54,19 @@ export declare class TypeScriptCodeGeneratorService implements CodeGenerator, Sc
|
|
|
52
54
|
private buildArrayTypeFromSchema;
|
|
53
55
|
private isReference;
|
|
54
56
|
private buildFromReference;
|
|
57
|
+
/**
|
|
58
|
+
* Determines if a reference creates a circular dependency that needs z.lazy().
|
|
59
|
+
* A reference is circular if:
|
|
60
|
+
* 1. It's a direct self-reference (schema references itself)
|
|
61
|
+
* 2. It's part of a circular dependency chain (A -> B -> A)
|
|
62
|
+
*/
|
|
63
|
+
private isCircularReference;
|
|
64
|
+
/**
|
|
65
|
+
* Detects schemas that are part of circular dependency chains.
|
|
66
|
+
* Uses a modified Tarjan's algorithm to find strongly connected components (SCCs).
|
|
67
|
+
* Schemas in SCCs with more than one node, or self-referencing schemas, are circular.
|
|
68
|
+
*/
|
|
69
|
+
private detectCircularDependencies;
|
|
55
70
|
private topologicalSort;
|
|
56
71
|
}
|
|
57
72
|
//# sourceMappingURL=code-generator.service.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"code-generator.service.d.ts","sourceRoot":"","sources":["../../../src/services/code-generator.service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,KAAK,EAAC,aAAa,EAAE,aAAa,EAAC,MAAM,iCAAiC,CAAC;AAClF,OAAO,KAAK,EAAmB,eAAe,EAAgB,MAAM,qBAAqB,CAAC;AAC1F,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,+BAA+B,CAAC;AAWpE,qBAAa,8BAA+B,YAAW,aAAa,EAAE,aAAa;IACjF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsC;IAClE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwC;IACtE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwD;IAChF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA+B;IAChE,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAuC;
|
|
1
|
+
{"version":3,"file":"code-generator.service.d.ts","sourceRoot":"","sources":["../../../src/services/code-generator.service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AAEjC,OAAO,KAAK,EAAC,aAAa,EAAE,aAAa,EAAC,MAAM,iCAAiC,CAAC;AAClF,OAAO,KAAK,EAAmB,eAAe,EAAgB,MAAM,qBAAqB,CAAC;AAC1F,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,+BAA+B,CAAC;AAWpE,qBAAa,8BAA+B,YAAW,aAAa,EAAE,aAAa;IACjF,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsC;IAClE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAwC;IACtE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAwD;IAChF,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAA+B;IAChE,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAuC;IAGhF,OAAO,CAAC,eAAe,CAAqB;IAC5C,OAAO,CAAC,iBAAiB,CAAuB;gBAEpC,OAAO,GAAE,gBAAqB;IAK1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAGpB;IAEH,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM;IAMvC,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,UAAO,GAAG,EAAE,CAAC,cAAc,GAAG,EAAE,CAAC,UAAU;IA2BhF,OAAO,CAAC,QAAQ;IAmBhB,OAAO,CAAC,YAAY;IA2CpB,OAAO,CAAC,sBAAsB;IAe9B,OAAO,CAAC,gBAAgB;IAsBxB,OAAO,CAAC,gBAAgB;IAmGxB,OAAO,CAAC,gCAAgC;IAwBxC,OAAO,CAAC,sBAAsB;IAqmB9B,OAAO,CAAC,kBAAkB;IAsB1B;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IA0B9B,OAAO,CAAC,mBAAmB;IAgH3B,OAAO,CAAC,mBAAmB;IA6D3B,OAAO,CAAC,qBAAqB;IAwF7B,OAAO,CAAC,gBAAgB;IA0CxB,OAAO,CAAC,iBAAiB;IAmCzB,OAAO,CAAC,iBAAiB;IA8BzB,OAAO,CAAC,eAAe;IAuDvB,OAAO,CAAC,wBAAwB;IA4IhC,OAAO,CAAC,gBAAgB;IAqBxB,OAAO,CAAC,6BAA6B;IAkPrC,OAAO,CAAC,kBAAkB;IAO1B,OAAO,CAAC,aAAa;IAMrB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAuFzB,OAAO,CAAC,WAAW;IA0CnB,OAAO,CAAC,aAAa;IAmjBrB,OAAO,CAAC,iBAAiB;IAuCzB,OAAO,CAAC,qBAAqB;IAe7B,OAAO,CAAC,oBAAoB;IAyE5B,OAAO,CAAC,8BAA8B;IActC,OAAO,CAAC,wBAAwB;IAyBhC,OAAO,CAAC,yBAAyB;IA8BjC,OAAO,CAAC,wBAAwB;IAYhC,OAAO,CAAC,WAAW;IAQnB,OAAO,CAAC,kBAAkB;IA8B1B;;;;;OAKG;IACH,OAAO,CAAC,mBAAmB;IAmB3B;;;;OAIG;IACH,OAAO,CAAC,0BAA0B;IAiFlC,OAAO,CAAC,eAAe;CAuCxB"}
|
|
@@ -11,6 +11,9 @@ export class TypeScriptCodeGeneratorService {
|
|
|
11
11
|
printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
|
|
12
12
|
namingConvention;
|
|
13
13
|
operationNameTransformer;
|
|
14
|
+
// Track circular dependencies for z.lazy() wrapping
|
|
15
|
+
circularSchemas = new Set();
|
|
16
|
+
currentSchemaName = null;
|
|
14
17
|
constructor(options = {}) {
|
|
15
18
|
this.namingConvention = options.namingConvention;
|
|
16
19
|
this.operationNameTransformer = options.operationNameTransformer;
|
|
@@ -63,13 +66,21 @@ export class TypeScriptCodeGeneratorService {
|
|
|
63
66
|
}
|
|
64
67
|
buildSchemas(openapi) {
|
|
65
68
|
const schemasEntries = Object.entries(openapi.components?.schemas ?? {});
|
|
66
|
-
const
|
|
69
|
+
const schemasMap = Object.fromEntries(schemasEntries);
|
|
70
|
+
// Detect circular dependencies before building schemas
|
|
71
|
+
this.circularSchemas = this.detectCircularDependencies(schemasMap);
|
|
72
|
+
const sortedSchemaNames = this.topologicalSort(schemasMap);
|
|
67
73
|
return sortedSchemaNames.reduce((schemaRegistered, name) => {
|
|
68
74
|
const schema = openapi.components?.schemas?.[name];
|
|
69
75
|
if (!schema)
|
|
70
76
|
return schemaRegistered;
|
|
77
|
+
// Set context for current schema being built
|
|
78
|
+
this.currentSchemaName = name;
|
|
79
|
+
const schemaExpression = this.buildSchema(schema);
|
|
80
|
+
// Clear context
|
|
81
|
+
this.currentSchemaName = null;
|
|
71
82
|
const variableStatement = ts.factory.createVariableStatement([ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
72
|
-
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(this.typeBuilder.sanitizeIdentifier(name)), undefined, undefined,
|
|
83
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(this.typeBuilder.sanitizeIdentifier(name)), undefined, undefined, schemaExpression),
|
|
73
84
|
], ts.NodeFlags.Const));
|
|
74
85
|
return {
|
|
75
86
|
...schemaRegistered,
|
|
@@ -86,7 +97,7 @@ export class TypeScriptCodeGeneratorService {
|
|
|
86
97
|
buildClientClass(openapi, schemas) {
|
|
87
98
|
const clientName = this.generateClientName(openapi.info.title);
|
|
88
99
|
const methods = this.buildClientMethods(openapi, schemas);
|
|
89
|
-
return ts.factory.createClassDeclaration([ts.factory.createToken(ts.SyntaxKind.ExportKeyword)], ts.factory.createIdentifier(clientName), undefined, undefined, [
|
|
100
|
+
return ts.factory.createClassDeclaration([ts.factory.createToken(ts.SyntaxKind.ExportKeyword), ts.factory.createToken(ts.SyntaxKind.DefaultKeyword)], ts.factory.createIdentifier(clientName), undefined, undefined, [
|
|
90
101
|
this.typeBuilder.createProperty('#baseUrl', 'string', true),
|
|
91
102
|
this.buildConstructor(openapi),
|
|
92
103
|
this.buildGetBaseRequestOptionsMethod(),
|
|
@@ -132,7 +143,7 @@ export class TypeScriptCodeGeneratorService {
|
|
|
132
143
|
]), ts.factory.createBlock([ts.factory.createReturnStatement(ts.factory.createObjectLiteralExpression([], false))], true));
|
|
133
144
|
}
|
|
134
145
|
buildHttpRequestMethod() {
|
|
135
|
-
return ts.factory.createMethodDeclaration([ts.factory.createToken(ts.SyntaxKind.AsyncKeyword)], undefined, ts.factory.
|
|
146
|
+
return ts.factory.createMethodDeclaration([ts.factory.createToken(ts.SyntaxKind.ProtectedKeyword), ts.factory.createToken(ts.SyntaxKind.AsyncKeyword)], undefined, ts.factory.createIdentifier('makeRequest'), undefined, [this.typeBuilder.createGenericType('T')], [
|
|
136
147
|
this.typeBuilder.createParameter('method', 'string'),
|
|
137
148
|
this.typeBuilder.createParameter('path', 'string'),
|
|
138
149
|
this.typeBuilder.createParameter('options', '{params?: Record<string, string | number | boolean>; data?: unknown; contentType?: string; headers?: Record<string, string>}', ts.factory.createObjectLiteralExpression([], false), false),
|
|
@@ -331,7 +342,7 @@ export class TypeScriptCodeGeneratorService {
|
|
|
331
342
|
}
|
|
332
343
|
const optionsExpression = ts.factory.createObjectLiteralExpression(optionsProps, false);
|
|
333
344
|
// Call makeRequest
|
|
334
|
-
const makeRequestCall = ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createThis(), ts.factory.
|
|
345
|
+
const makeRequestCall = ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createThis(), ts.factory.createIdentifier('makeRequest')), undefined, [ts.factory.createStringLiteral(method.toUpperCase(), true), pathExpression, optionsExpression]);
|
|
335
346
|
// Add Zod validation if we have a response schema
|
|
336
347
|
if (responseSchema) {
|
|
337
348
|
const validateCall = ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(responseSchema, ts.factory.createIdentifier('parse')), undefined, [ts.factory.createAwaitExpression(makeRequestCall)]);
|
|
@@ -1223,7 +1234,114 @@ export class TypeScriptCodeGeneratorService {
|
|
|
1223
1234
|
buildFromReference(reference) {
|
|
1224
1235
|
const { $ref = '' } = Reference.parse(reference);
|
|
1225
1236
|
const refName = $ref.split('/').pop() ?? 'never';
|
|
1226
|
-
|
|
1237
|
+
const sanitizedRefName = this.typeBuilder.sanitizeIdentifier(refName);
|
|
1238
|
+
// Check if this reference creates a circular dependency
|
|
1239
|
+
if (this.isCircularReference(refName)) {
|
|
1240
|
+
// Generate: z.lazy(() => RefSchema)
|
|
1241
|
+
return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier('z'), ts.factory.createIdentifier('lazy')), undefined, [
|
|
1242
|
+
ts.factory.createArrowFunction(undefined, undefined, [], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createIdentifier(sanitizedRefName)),
|
|
1243
|
+
]);
|
|
1244
|
+
}
|
|
1245
|
+
return ts.factory.createIdentifier(sanitizedRefName);
|
|
1246
|
+
}
|
|
1247
|
+
/**
|
|
1248
|
+
* Determines if a reference creates a circular dependency that needs z.lazy().
|
|
1249
|
+
* A reference is circular if:
|
|
1250
|
+
* 1. It's a direct self-reference (schema references itself)
|
|
1251
|
+
* 2. It's part of a circular dependency chain (A -> B -> A)
|
|
1252
|
+
*/
|
|
1253
|
+
isCircularReference(refName) {
|
|
1254
|
+
// Case 1: Direct self-reference
|
|
1255
|
+
if (refName === this.currentSchemaName) {
|
|
1256
|
+
return true;
|
|
1257
|
+
}
|
|
1258
|
+
// Case 2: Reference to a schema that's part of a circular dependency chain
|
|
1259
|
+
// and we're currently building a schema that's also in that chain
|
|
1260
|
+
if (this.circularSchemas.has(refName) &&
|
|
1261
|
+
this.currentSchemaName !== null &&
|
|
1262
|
+
this.circularSchemas.has(this.currentSchemaName)) {
|
|
1263
|
+
return true;
|
|
1264
|
+
}
|
|
1265
|
+
return false;
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1268
|
+
* Detects schemas that are part of circular dependency chains.
|
|
1269
|
+
* Uses a modified Tarjan's algorithm to find strongly connected components (SCCs).
|
|
1270
|
+
* Schemas in SCCs with more than one node, or self-referencing schemas, are circular.
|
|
1271
|
+
*/
|
|
1272
|
+
detectCircularDependencies(schemas) {
|
|
1273
|
+
const circularSchemas = new Set();
|
|
1274
|
+
// Build dependency graph
|
|
1275
|
+
const graph = new Map();
|
|
1276
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
1277
|
+
const dependencies = jp
|
|
1278
|
+
.query(schema, '$..["$ref"]')
|
|
1279
|
+
.filter((ref) => ref.startsWith('#/components/schemas/'))
|
|
1280
|
+
.map((ref) => ref.replace('#/components/schemas/', ''))
|
|
1281
|
+
.filter((dep) => dep in schemas);
|
|
1282
|
+
graph.set(name, dependencies);
|
|
1283
|
+
}
|
|
1284
|
+
// Tarjan's algorithm for finding SCCs
|
|
1285
|
+
let index = 0;
|
|
1286
|
+
const indices = new Map();
|
|
1287
|
+
const lowlinks = new Map();
|
|
1288
|
+
const onStack = new Set();
|
|
1289
|
+
const stack = [];
|
|
1290
|
+
const strongConnect = (node) => {
|
|
1291
|
+
indices.set(node, index);
|
|
1292
|
+
lowlinks.set(node, index);
|
|
1293
|
+
index++;
|
|
1294
|
+
stack.push(node);
|
|
1295
|
+
onStack.add(node);
|
|
1296
|
+
const neighbors = graph.get(node) ?? [];
|
|
1297
|
+
for (const neighbor of neighbors) {
|
|
1298
|
+
if (!indices.has(neighbor)) {
|
|
1299
|
+
strongConnect(neighbor);
|
|
1300
|
+
const currentLowlink = lowlinks.get(node) ?? 0;
|
|
1301
|
+
const neighborLowlink = lowlinks.get(neighbor) ?? 0;
|
|
1302
|
+
lowlinks.set(node, Math.min(currentLowlink, neighborLowlink));
|
|
1303
|
+
}
|
|
1304
|
+
else if (onStack.has(neighbor)) {
|
|
1305
|
+
const currentLowlink = lowlinks.get(node) ?? 0;
|
|
1306
|
+
const neighborIndex = indices.get(neighbor) ?? 0;
|
|
1307
|
+
lowlinks.set(node, Math.min(currentLowlink, neighborIndex));
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
// If node is a root of an SCC
|
|
1311
|
+
if (lowlinks.get(node) === indices.get(node)) {
|
|
1312
|
+
const scc = [];
|
|
1313
|
+
let w;
|
|
1314
|
+
do {
|
|
1315
|
+
w = stack.pop();
|
|
1316
|
+
if (w !== undefined) {
|
|
1317
|
+
onStack.delete(w);
|
|
1318
|
+
scc.push(w);
|
|
1319
|
+
}
|
|
1320
|
+
} while (w !== undefined && w !== node);
|
|
1321
|
+
// An SCC is circular if it has more than one node
|
|
1322
|
+
// or if it has one node that references itself
|
|
1323
|
+
if (scc.length > 1) {
|
|
1324
|
+
for (const schemaName of scc) {
|
|
1325
|
+
circularSchemas.add(schemaName);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
else if (scc.length === 1) {
|
|
1329
|
+
const schemaName = scc[0];
|
|
1330
|
+
if (schemaName !== undefined) {
|
|
1331
|
+
const deps = graph.get(schemaName) ?? [];
|
|
1332
|
+
if (deps.includes(schemaName)) {
|
|
1333
|
+
circularSchemas.add(schemaName);
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
};
|
|
1339
|
+
for (const node of graph.keys()) {
|
|
1340
|
+
if (!indices.has(node)) {
|
|
1341
|
+
strongConnect(node);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
return circularSchemas;
|
|
1227
1345
|
}
|
|
1228
1346
|
topologicalSort(schemas) {
|
|
1229
1347
|
const visited = new Set();
|
package/package.json
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "Julien Andreu <julienandreu@me.com>",
|
|
3
|
-
"bin":
|
|
4
|
-
"zod-codegen": "dist/src/cli.js"
|
|
5
|
-
},
|
|
3
|
+
"bin": "dist/src/cli.js",
|
|
6
4
|
"bugs": {
|
|
7
5
|
"url": "https://github.com/julienandreu/zod-codegen/issues"
|
|
8
6
|
},
|
|
@@ -19,7 +17,7 @@
|
|
|
19
17
|
"typescript": "^5.9.3",
|
|
20
18
|
"url-pattern": "^1.0.3",
|
|
21
19
|
"yargs": "^18.0.0",
|
|
22
|
-
"zod": "^4.1
|
|
20
|
+
"zod": "^4.2.1"
|
|
23
21
|
},
|
|
24
22
|
"description": "A powerful TypeScript code generator that creates Zod schemas and type-safe clients from OpenAPI specifications",
|
|
25
23
|
"keywords": [
|
|
@@ -36,17 +34,18 @@
|
|
|
36
34
|
"devDependencies": {
|
|
37
35
|
"@commitlint/cli": "^20.1.0",
|
|
38
36
|
"@commitlint/config-conventional": "^20.0.0",
|
|
39
|
-
"@eslint/js": "^9.39.
|
|
37
|
+
"@eslint/js": "^9.39.2",
|
|
40
38
|
"@semantic-release/changelog": "^6.0.3",
|
|
41
39
|
"@semantic-release/git": "^10.0.1",
|
|
42
40
|
"@types/debug": "^4.1.12",
|
|
43
41
|
"@types/jest": "^30.0.0",
|
|
44
42
|
"@types/js-yaml": "^4.0.9",
|
|
45
43
|
"@types/jsonpath": "^0.2.4",
|
|
46
|
-
"@types/node": "^
|
|
44
|
+
"@types/node": "^25.0.2",
|
|
47
45
|
"@types/yargs": "^17.0.35",
|
|
48
46
|
"@vitest/coverage-v8": "^4.0.14",
|
|
49
|
-
"
|
|
47
|
+
"cross-env": "^10.1.0",
|
|
48
|
+
"eslint": "^9.39.2",
|
|
50
49
|
"eslint-config-prettier": "^10.1.8",
|
|
51
50
|
"husky": "^9.1.7",
|
|
52
51
|
"lint-staged": "^16.2.7",
|
|
@@ -54,8 +53,7 @@
|
|
|
54
53
|
"ts-node": "^10.9.2",
|
|
55
54
|
"typescript-eslint": "^8.46.4",
|
|
56
55
|
"undici": "^7.16.0",
|
|
57
|
-
"vitest": "^4.0.14"
|
|
58
|
-
"yarn-audit-fix": "^10.1.1"
|
|
56
|
+
"vitest": "^4.0.14"
|
|
59
57
|
},
|
|
60
58
|
"optionalDependencies": {
|
|
61
59
|
"undici": "^7.16.0"
|
|
@@ -88,15 +86,12 @@
|
|
|
88
86
|
"tar": "^7.5.2",
|
|
89
87
|
"yargs": "^18.0.0"
|
|
90
88
|
},
|
|
91
|
-
"resolutions": {
|
|
92
|
-
"yargs": "^18.0.0"
|
|
93
|
-
},
|
|
94
89
|
"scripts": {
|
|
95
|
-
"audit:fix": "
|
|
96
|
-
"build": "rm -rf dist && tsc --project tsconfig.json && cp -r src/assets dist/src/ && chmod +x ./dist/src/cli.js",
|
|
97
|
-
"build:native": "rm -rf dist && npx tsgo --project tsconfig.json && cp -r src/assets dist/src/ && chmod +x ./dist/src/cli.js",
|
|
90
|
+
"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",
|
|
98
93
|
"build:watch": "tsc --project tsconfig.json --watch",
|
|
99
|
-
"dev": "
|
|
94
|
+
"dev": "npm run build && node ./dist/src/cli.js --input ./samples/swagger-petstore.yaml --output ./examples/petstore && npm run format && npm run lint",
|
|
100
95
|
"lint": "eslint src --fix",
|
|
101
96
|
"lint:check": "eslint src",
|
|
102
97
|
"format": "prettier src --write --log-level error",
|
|
@@ -108,12 +103,12 @@
|
|
|
108
103
|
"test:ui": "vitest --ui",
|
|
109
104
|
"manifest:update": "ts-node scripts/update-manifest.ts",
|
|
110
105
|
"prepare": "husky",
|
|
111
|
-
"prepublishOnly": "
|
|
106
|
+
"prepublishOnly": "npm run build && npm run test && npm run lint:check && npm run type-check",
|
|
112
107
|
"clean": "rm -rf dist coverage node_modules/.cache",
|
|
113
|
-
"validate": "
|
|
108
|
+
"validate": "npm run type-check && npm run lint:check && npm run format:check && npm run test",
|
|
114
109
|
"validate:examples": "tsc -p ./tsconfig.examples.json --noEmit",
|
|
115
110
|
"release": "semantic-release",
|
|
116
111
|
"release:dry": "semantic-release --dry-run"
|
|
117
112
|
},
|
|
118
|
-
"version": "1.
|
|
113
|
+
"version": "1.5.0"
|
|
119
114
|
}
|