zod-codegen 1.3.0 → 1.4.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 +17 -0
- package/README.md +61 -9
- package/dist/src/cli.d.ts +3 -0
- package/dist/src/cli.d.ts.map +1 -0
- package/dist/src/cli.js +31 -2
- package/dist/src/generator.d.ts +23 -0
- package/dist/src/generator.d.ts.map +1 -0
- package/dist/src/generator.js +3 -2
- package/dist/src/http/fetch-client.d.ts +15 -0
- package/dist/src/http/fetch-client.d.ts.map +1 -0
- package/dist/src/interfaces/code-generator.d.ts +20 -0
- package/dist/src/interfaces/code-generator.d.ts.map +1 -0
- package/dist/src/interfaces/file-reader.d.ts +13 -0
- package/dist/src/interfaces/file-reader.d.ts.map +1 -0
- package/dist/src/polyfills/fetch.d.ts +5 -0
- package/dist/src/polyfills/fetch.d.ts.map +1 -0
- package/dist/src/services/code-generator.service.d.ts +57 -0
- package/dist/src/services/code-generator.service.d.ts.map +1 -0
- package/dist/src/services/code-generator.service.js +41 -4
- package/dist/src/services/file-reader.service.d.ts +9 -0
- package/dist/src/services/file-reader.service.d.ts.map +1 -0
- package/dist/src/services/file-writer.service.d.ts +10 -0
- package/dist/src/services/file-writer.service.d.ts.map +1 -0
- package/dist/src/services/import-builder.service.d.ts +14 -0
- package/dist/src/services/import-builder.service.d.ts.map +1 -0
- package/dist/src/services/type-builder.service.d.ts +12 -0
- package/dist/src/services/type-builder.service.d.ts.map +1 -0
- package/dist/src/types/generator-options.d.ts +59 -0
- package/dist/src/types/generator-options.d.ts.map +1 -0
- package/dist/src/types/generator-options.js +1 -0
- package/dist/src/types/http.d.ts +25 -0
- package/dist/src/types/http.d.ts.map +1 -0
- package/dist/src/types/openapi.d.ts +1120 -0
- package/dist/src/types/openapi.d.ts.map +1 -0
- package/dist/src/utils/error-handler.d.ts +3 -0
- package/dist/src/utils/error-handler.d.ts.map +1 -0
- package/dist/src/utils/error-handler.js +2 -2
- package/dist/src/utils/execution-time.d.ts +2 -0
- package/dist/src/utils/execution-time.d.ts.map +1 -0
- package/dist/src/utils/manifest.d.ts +8 -0
- package/dist/src/utils/manifest.d.ts.map +1 -0
- package/dist/src/utils/naming-convention.d.ts +80 -0
- package/dist/src/utils/naming-convention.d.ts.map +1 -0
- package/dist/src/utils/naming-convention.js +135 -0
- package/dist/src/utils/reporter.d.ts +7 -0
- package/dist/src/utils/reporter.d.ts.map +1 -0
- package/dist/src/utils/signal-handler.d.ts +3 -0
- package/dist/src/utils/signal-handler.d.ts.map +1 -0
- package/dist/src/utils/signal-handler.js +2 -2
- package/dist/src/utils/tty.d.ts +2 -0
- package/dist/src/utils/tty.d.ts.map +1 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/package.json +15 -15
- package/src/cli.ts +34 -3
- package/src/generator.ts +8 -1
- package/src/services/code-generator.service.ts +51 -8
- package/src/types/generator-options.ts +60 -0
- package/src/utils/error-handler.ts +2 -2
- package/src/utils/naming-convention.ts +214 -0
- package/src/utils/signal-handler.ts +2 -2
- package/tests/integration/cli.test.ts +2 -2
- package/tests/unit/code-generator.test.ts +192 -4
- package/tests/unit/generator.test.ts +2 -2
- package/tests/unit/naming-convention.test.ts +263 -0
- package/tsconfig.json +3 -1
- package/.claude/settings.local.json +0 -43
- package/dist/scripts/update-manifest.js +0 -31
- package/dist/tests/integration/cli.test.js +0 -25
- package/dist/tests/unit/code-generator.test.js +0 -290
- package/dist/tests/unit/file-reader.test.js +0 -110
- package/dist/tests/unit/generator.test.js +0 -100
- package/scripts/republish-versions.sh +0 -94
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../../../src/types/openapi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB,eAAO,MAAM,SAAS;;iBAEpB,CAAC;AA6CH,eAAO,MAAM,gBAAgB,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAKxD,CAAC;AAcF,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;iBAYpB,CAAC;AAkBH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;iBAKnB,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;;;iBAKtB,CAAC;AAEH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBASvB,CAAC;AAEH,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAanB,CAAC;AA+CH,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAStB,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAC1D,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AACpE,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;AACtD,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AACpD,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC;AAC1D,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAC5D,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,QAAQ,CAAC,CAAC;AACpD,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../../src/utils/error-handler.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,CAAC,OAAO,EAAE,WAAW,MAAM,WAAS,IAGhF,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,CAAC,OAAO,EAAE,WAAW,MAAM,KAAG,IAKzE,CAAC"}
|
|
@@ -5,7 +5,7 @@ export const errorReceived = (process, startTime) => () => {
|
|
|
5
5
|
};
|
|
6
6
|
export const handleErrors = (process, startTime) => {
|
|
7
7
|
const catchErrors = ['unhandledRejection', 'uncaughtException'];
|
|
8
|
-
catchErrors.
|
|
9
|
-
|
|
8
|
+
catchErrors.forEach((event) => {
|
|
9
|
+
process.on(event, errorReceived(process, startTime));
|
|
10
10
|
});
|
|
11
11
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"execution-time.d.ts","sourceRoot":"","sources":["../../../src/utils/execution-time.ts"],"names":[],"mappings":"AAAA,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1D"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/utils/manifest.ts"],"names":[],"mappings":"AAEA,UAAU,QAAQ;IAChB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAQD,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAE5D"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported naming conventions for operation IDs.
|
|
3
|
+
* These conventions transform operation IDs to match common programming language styles.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```typescript
|
|
7
|
+
* transformNamingConvention('get_user_by_id', 'camelCase') // 'getUserById'
|
|
8
|
+
* transformNamingConvention('getUserById', 'snake_case') // 'get_user_by_id'
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export type NamingConvention = 'camelCase' | 'PascalCase' | 'snake_case' | 'kebab-case' | 'SCREAMING_SNAKE_CASE' | 'SCREAMING-KEBAB-CASE';
|
|
12
|
+
/**
|
|
13
|
+
* Operation details provided to custom transformers.
|
|
14
|
+
* Contains all available information about an OpenAPI operation.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* const transformer: OperationNameTransformer = (details) => {
|
|
19
|
+
* return `${details.method.toUpperCase()}_${details.operationId}`;
|
|
20
|
+
* };
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export interface OperationDetails {
|
|
24
|
+
/** Original operationId from OpenAPI spec */
|
|
25
|
+
operationId: string;
|
|
26
|
+
/** HTTP method (get, post, put, patch, delete, head, options) */
|
|
27
|
+
method: string;
|
|
28
|
+
/** API path (e.g., /users/{id}) */
|
|
29
|
+
path: string;
|
|
30
|
+
/** Tags associated with the operation */
|
|
31
|
+
tags?: string[];
|
|
32
|
+
/** Summary of the operation */
|
|
33
|
+
summary?: string;
|
|
34
|
+
/** Description of the operation */
|
|
35
|
+
description?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Custom transformer function for operation names.
|
|
39
|
+
* Receives operation details and returns the transformed name.
|
|
40
|
+
*
|
|
41
|
+
* **Note:** The returned name will be sanitized to ensure it's a valid TypeScript identifier.
|
|
42
|
+
* Invalid characters will be replaced with underscores, and names starting with digits will be prefixed.
|
|
43
|
+
*
|
|
44
|
+
* @param details - Complete operation details from OpenAPI spec
|
|
45
|
+
* @returns The transformed operation name
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* const transformer: OperationNameTransformer = (details) => {
|
|
50
|
+
* const tag = details.tags?.[0] || 'default';
|
|
51
|
+
* return `${details.method}_${tag}_${details.operationId}`;
|
|
52
|
+
* };
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export type OperationNameTransformer = (details: OperationDetails) => string;
|
|
56
|
+
/**
|
|
57
|
+
* Transforms a string to the specified naming convention.
|
|
58
|
+
*
|
|
59
|
+
* Handles various input formats including camelCase, PascalCase, snake_case, kebab-case,
|
|
60
|
+
* and mixed delimiters. The function normalizes the input by splitting on common delimiters
|
|
61
|
+
* (underscores, hyphens, spaces, dots) and then applies the target convention.
|
|
62
|
+
*
|
|
63
|
+
* **Note:** This function does not sanitize the output. The caller should ensure the result
|
|
64
|
+
* is a valid identifier for the target language (e.g., using `sanitizeIdentifier`).
|
|
65
|
+
*
|
|
66
|
+
* @param input - The input string to transform (can be in any naming convention)
|
|
67
|
+
* @param convention - The target naming convention to apply
|
|
68
|
+
* @returns The transformed string in the target convention
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* ```typescript
|
|
72
|
+
* transformNamingConvention('get_user_by_id', 'camelCase') // 'getUserById'
|
|
73
|
+
* transformNamingConvention('getUserById', 'snake_case') // 'get_user_by_id'
|
|
74
|
+
* transformNamingConvention('get-user-by-id', 'PascalCase') // 'GetUserById'
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @throws Will return empty string if input is empty (preserves empty input)
|
|
78
|
+
*/
|
|
79
|
+
export declare function transformNamingConvention(input: string, convention: NamingConvention): string;
|
|
80
|
+
//# sourceMappingURL=naming-convention.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"naming-convention.d.ts","sourceRoot":"","sources":["../../../src/utils/naming-convention.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,MAAM,gBAAgB,GACxB,WAAW,GACX,YAAY,GACZ,YAAY,GACZ,YAAY,GACZ,sBAAsB,GACtB,sBAAsB,CAAC;AAE3B;;;;;;;;;;GAUG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6CAA6C;IAC7C,WAAW,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,MAAM,EAAE,MAAM,CAAC;IACf,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,yCAAyC;IACzC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mCAAmC;IACnC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,EAAE,gBAAgB,KAAK,MAAM,CAAC;AA0G7E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,gBAAgB,GAAG,MAAM,CAsB7F"}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capitalizes the first letter of a word
|
|
3
|
+
*/
|
|
4
|
+
function capitalize(word) {
|
|
5
|
+
if (word.length === 0)
|
|
6
|
+
return word;
|
|
7
|
+
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Splits camelCase or PascalCase strings into words
|
|
11
|
+
*/
|
|
12
|
+
function splitCamelCase(input) {
|
|
13
|
+
const words = [];
|
|
14
|
+
let currentWord = '';
|
|
15
|
+
for (const char of input) {
|
|
16
|
+
const isUpperCase = char === char.toUpperCase() && char !== char.toLowerCase();
|
|
17
|
+
const isDigit = /\d/.test(char);
|
|
18
|
+
const lastChar = currentWord[currentWord.length - 1];
|
|
19
|
+
// Start a new word if:
|
|
20
|
+
// 1. Current char is uppercase and we have a previous word
|
|
21
|
+
// 2. Current char is a digit and previous was not
|
|
22
|
+
if (currentWord.length > 0 && (isUpperCase || (isDigit && lastChar && !/\d/.test(lastChar)))) {
|
|
23
|
+
words.push(currentWord);
|
|
24
|
+
currentWord = '';
|
|
25
|
+
}
|
|
26
|
+
currentWord += char;
|
|
27
|
+
}
|
|
28
|
+
if (currentWord.length > 0) {
|
|
29
|
+
words.push(currentWord);
|
|
30
|
+
}
|
|
31
|
+
return words.length > 0 ? words : [input];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Normalizes input string into an array of words
|
|
35
|
+
* Handles camelCase, PascalCase, snake_case, kebab-case, SCREAMING_SNAKE_CASE, etc.
|
|
36
|
+
*/
|
|
37
|
+
function normalizeToWords(input) {
|
|
38
|
+
// Handle empty or single character
|
|
39
|
+
if (input.length <= 1) {
|
|
40
|
+
return [input.toLowerCase()];
|
|
41
|
+
}
|
|
42
|
+
// Split by common delimiters: underscore, hyphen, space, dot
|
|
43
|
+
let words = input.split(/[-_\s.]+/).filter((w) => w.length > 0);
|
|
44
|
+
// If no delimiters found, try to split camelCase/PascalCase
|
|
45
|
+
if (words.length === 1) {
|
|
46
|
+
words = splitCamelCase(input);
|
|
47
|
+
}
|
|
48
|
+
// Normalize all words to lowercase
|
|
49
|
+
return words.map((w) => w.toLowerCase());
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Converts words array to camelCase
|
|
53
|
+
*/
|
|
54
|
+
function toCamelCase(words) {
|
|
55
|
+
if (words.length === 0)
|
|
56
|
+
return '';
|
|
57
|
+
const [first, ...rest] = words;
|
|
58
|
+
if (!first)
|
|
59
|
+
return '';
|
|
60
|
+
return first + rest.map((w) => capitalize(w)).join('');
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Converts words array to PascalCase
|
|
64
|
+
*/
|
|
65
|
+
function toPascalCase(words) {
|
|
66
|
+
return words.map((w) => capitalize(w)).join('');
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Converts words array to snake_case
|
|
70
|
+
*/
|
|
71
|
+
function toSnakeCase(words) {
|
|
72
|
+
return words.join('_');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Converts words array to kebab-case
|
|
76
|
+
*/
|
|
77
|
+
function toKebabCase(words) {
|
|
78
|
+
return words.join('-');
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Converts words array to SCREAMING_SNAKE_CASE
|
|
82
|
+
*/
|
|
83
|
+
function toScreamingSnakeCase(words) {
|
|
84
|
+
return words.map((w) => w.toUpperCase()).join('_');
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Converts words array to SCREAMING-KEBAB-CASE
|
|
88
|
+
*/
|
|
89
|
+
function toScreamingKebabCase(words) {
|
|
90
|
+
return words.map((w) => w.toUpperCase()).join('-');
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Transforms a string to the specified naming convention.
|
|
94
|
+
*
|
|
95
|
+
* Handles various input formats including camelCase, PascalCase, snake_case, kebab-case,
|
|
96
|
+
* and mixed delimiters. The function normalizes the input by splitting on common delimiters
|
|
97
|
+
* (underscores, hyphens, spaces, dots) and then applies the target convention.
|
|
98
|
+
*
|
|
99
|
+
* **Note:** This function does not sanitize the output. The caller should ensure the result
|
|
100
|
+
* is a valid identifier for the target language (e.g., using `sanitizeIdentifier`).
|
|
101
|
+
*
|
|
102
|
+
* @param input - The input string to transform (can be in any naming convention)
|
|
103
|
+
* @param convention - The target naming convention to apply
|
|
104
|
+
* @returns The transformed string in the target convention
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```typescript
|
|
108
|
+
* transformNamingConvention('get_user_by_id', 'camelCase') // 'getUserById'
|
|
109
|
+
* transformNamingConvention('getUserById', 'snake_case') // 'get_user_by_id'
|
|
110
|
+
* transformNamingConvention('get-user-by-id', 'PascalCase') // 'GetUserById'
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* @throws Will return empty string if input is empty (preserves empty input)
|
|
114
|
+
*/
|
|
115
|
+
export function transformNamingConvention(input, convention) {
|
|
116
|
+
if (!input || input.length === 0) {
|
|
117
|
+
return input;
|
|
118
|
+
}
|
|
119
|
+
// Normalize input: split by common delimiters and convert to words
|
|
120
|
+
const words = normalizeToWords(input);
|
|
121
|
+
switch (convention) {
|
|
122
|
+
case 'camelCase':
|
|
123
|
+
return toCamelCase(words);
|
|
124
|
+
case 'PascalCase':
|
|
125
|
+
return toPascalCase(words);
|
|
126
|
+
case 'snake_case':
|
|
127
|
+
return toSnakeCase(words);
|
|
128
|
+
case 'kebab-case':
|
|
129
|
+
return toKebabCase(words);
|
|
130
|
+
case 'SCREAMING_SNAKE_CASE':
|
|
131
|
+
return toScreamingSnakeCase(words);
|
|
132
|
+
case 'SCREAMING-KEBAB-CASE':
|
|
133
|
+
return toScreamingKebabCase(words);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../../../src/utils/reporter.ts"],"names":[],"mappings":"AAEA,qBAAa,QAAQ;IACP,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,MAAM,CAAC,WAAW;IAKxD,GAAG,CAAC,GAAG,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI;IAItC,KAAK,CAAC,GAAG,IAAI,EAAE,SAAS,OAAO,EAAE,GAAG,IAAI;CAGzC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signal-handler.d.ts","sourceRoot":"","sources":["../../../src/utils/signal-handler.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,CAAC,OAAO,EAAE,WAAW,MAAM,EAAE,OAAO,MAAM,CAAC,OAAO,WAAS,IAIxG,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,CAAC,OAAO,EAAE,WAAW,MAAM,KAAG,IAK1E,CAAC"}
|
|
@@ -6,7 +6,7 @@ export const signalReceived = (process, startTime, event) => () => {
|
|
|
6
6
|
};
|
|
7
7
|
export const handleSignals = (process, startTime) => {
|
|
8
8
|
const catchSignals = ['SIGTERM', 'SIGINT', 'SIGUSR2'];
|
|
9
|
-
catchSignals.
|
|
10
|
-
|
|
9
|
+
catchSignals.forEach((event) => {
|
|
10
|
+
process.once(event, signalReceived(process, startTime, event));
|
|
11
11
|
});
|
|
12
12
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tty.d.ts","sourceRoot":"","sources":["../../../src/utils/tty.ts"],"names":[],"mappings":"AAAA,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,GAAG,OAAO,CAEtD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vitest.config.d.ts","sourceRoot":"","sources":["../vitest.config.ts"],"names":[],"mappings":";AAGA,wBAmCG"}
|
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
|
},
|
|
@@ -45,7 +43,8 @@
|
|
|
45
43
|
"@types/jsonpath": "^0.2.4",
|
|
46
44
|
"@types/node": "^24.10.1",
|
|
47
45
|
"@types/yargs": "^17.0.35",
|
|
48
|
-
"@vitest/coverage-v8": "^4.0.
|
|
46
|
+
"@vitest/coverage-v8": "^4.0.14",
|
|
47
|
+
"cross-env": "^10.1.0",
|
|
49
48
|
"eslint": "^9.39.1",
|
|
50
49
|
"eslint-config-prettier": "^10.1.8",
|
|
51
50
|
"husky": "^9.1.7",
|
|
@@ -54,16 +53,16 @@
|
|
|
54
53
|
"ts-node": "^10.9.2",
|
|
55
54
|
"typescript-eslint": "^8.46.4",
|
|
56
55
|
"undici": "^7.16.0",
|
|
57
|
-
"vitest": "^4.0.
|
|
58
|
-
"yarn-audit-fix": "^10.1.1"
|
|
56
|
+
"vitest": "^4.0.14"
|
|
59
57
|
},
|
|
60
58
|
"optionalDependencies": {
|
|
61
59
|
"undici": "^7.16.0"
|
|
62
60
|
},
|
|
63
61
|
"homepage": "https://github.com/julienandreu/zod-codegen",
|
|
64
|
-
"license": "
|
|
62
|
+
"license": "Apache-2.0",
|
|
65
63
|
"name": "zod-codegen",
|
|
66
64
|
"type": "module",
|
|
65
|
+
"types": "./dist/src/generator.d.ts",
|
|
67
66
|
"exports": {
|
|
68
67
|
".": {
|
|
69
68
|
"types": "./dist/src/generator.d.ts",
|
|
@@ -84,14 +83,15 @@
|
|
|
84
83
|
"tar": "^7.5.2"
|
|
85
84
|
},
|
|
86
85
|
"glob": "^11.1.0",
|
|
87
|
-
"tar": "^7.5.2"
|
|
86
|
+
"tar": "^7.5.2",
|
|
87
|
+
"yargs": "^18.0.0"
|
|
88
88
|
},
|
|
89
89
|
"scripts": {
|
|
90
|
-
"audit:fix": "
|
|
91
|
-
"build": "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 && 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",
|
|
93
93
|
"build:watch": "tsc --project tsconfig.json --watch",
|
|
94
|
-
"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",
|
|
95
95
|
"lint": "eslint src --fix",
|
|
96
96
|
"lint:check": "eslint src",
|
|
97
97
|
"format": "prettier src --write --log-level error",
|
|
@@ -103,12 +103,12 @@
|
|
|
103
103
|
"test:ui": "vitest --ui",
|
|
104
104
|
"manifest:update": "ts-node scripts/update-manifest.ts",
|
|
105
105
|
"prepare": "husky",
|
|
106
|
-
"prepublishOnly": "
|
|
106
|
+
"prepublishOnly": "npm run build && npm run test && npm run lint:check && npm run type-check",
|
|
107
107
|
"clean": "rm -rf dist coverage node_modules/.cache",
|
|
108
|
-
"validate": "
|
|
108
|
+
"validate": "npm run type-check && npm run lint:check && npm run format:check && npm run test",
|
|
109
109
|
"validate:examples": "tsc -p ./tsconfig.examples.json --noEmit",
|
|
110
110
|
"release": "semantic-release",
|
|
111
111
|
"release:dry": "semantic-release --dry-run"
|
|
112
112
|
},
|
|
113
|
-
"version": "1.
|
|
113
|
+
"version": "1.4.1"
|
|
114
114
|
}
|
package/src/cli.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import yargs from 'yargs';
|
|
4
4
|
import {hideBin} from 'yargs/helpers';
|
|
5
|
-
import {Generator} from './generator.js';
|
|
5
|
+
import {Generator, type GeneratorOptions, type NamingConvention} from './generator.js';
|
|
6
6
|
import {readFileSync} from 'node:fs';
|
|
7
7
|
import {fileURLToPath} from 'node:url';
|
|
8
8
|
import {dirname, join} from 'node:path';
|
|
@@ -72,15 +72,46 @@ const argv = yargs(hideBin(process.argv))
|
|
|
72
72
|
description: 'Directory to output the generated files',
|
|
73
73
|
default: 'generated',
|
|
74
74
|
})
|
|
75
|
+
.option('naming-convention', {
|
|
76
|
+
alias: 'n',
|
|
77
|
+
type: 'string',
|
|
78
|
+
description: 'Naming convention to apply to operation IDs',
|
|
79
|
+
choices: ['camelCase', 'PascalCase', 'snake_case', 'kebab-case', 'SCREAMING_SNAKE_CASE', 'SCREAMING-KEBAB-CASE'],
|
|
80
|
+
default: undefined,
|
|
81
|
+
})
|
|
75
82
|
.strict()
|
|
76
83
|
.help()
|
|
77
84
|
.parseSync();
|
|
78
85
|
|
|
79
|
-
const {input, output} = argv;
|
|
86
|
+
const {input, output, namingConvention} = argv;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Type guard to validate that a string is a valid naming convention.
|
|
90
|
+
* This ensures type safety when parsing CLI arguments.
|
|
91
|
+
*
|
|
92
|
+
* @param value - The value to check
|
|
93
|
+
* @returns True if the value is a valid NamingConvention
|
|
94
|
+
*/
|
|
95
|
+
function isValidNamingConvention(value: string | undefined): value is NamingConvention {
|
|
96
|
+
if (value === undefined) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
const validConventions: readonly NamingConvention[] = [
|
|
100
|
+
'camelCase',
|
|
101
|
+
'PascalCase',
|
|
102
|
+
'snake_case',
|
|
103
|
+
'kebab-case',
|
|
104
|
+
'SCREAMING_SNAKE_CASE',
|
|
105
|
+
'SCREAMING-KEBAB-CASE',
|
|
106
|
+
] as const;
|
|
107
|
+
return validConventions.includes(value as NamingConvention);
|
|
108
|
+
}
|
|
80
109
|
|
|
81
110
|
void (async () => {
|
|
82
111
|
try {
|
|
83
|
-
const
|
|
112
|
+
const options: GeneratorOptions = isValidNamingConvention(namingConvention) ? {namingConvention} : {};
|
|
113
|
+
|
|
114
|
+
const generator = new Generator(name, version, reporter, input, output, options);
|
|
84
115
|
const exitCode = await generator.run();
|
|
85
116
|
process.exit(exitCode);
|
|
86
117
|
} catch (error) {
|
package/src/generator.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import type {Reporter} from './utils/reporter.js';
|
|
2
2
|
import type {OpenApiSpecType} from './types/openapi.js';
|
|
3
|
+
import type {GeneratorOptions} from './types/generator-options.js';
|
|
3
4
|
import {OpenApiFileParserService, SyncFileReaderService} from './services/file-reader.service.js';
|
|
4
5
|
import {TypeScriptCodeGeneratorService} from './services/code-generator.service.js';
|
|
5
6
|
import {SyncFileWriterService} from './services/file-writer.service.js';
|
|
6
7
|
|
|
8
|
+
// Re-export types for library users
|
|
9
|
+
export type {GeneratorOptions} from './types/generator-options.js';
|
|
10
|
+
export type {NamingConvention, OperationDetails, OperationNameTransformer} from './utils/naming-convention.js';
|
|
11
|
+
|
|
7
12
|
export class Generator {
|
|
8
13
|
private readonly fileReader = new SyncFileReaderService();
|
|
9
14
|
private readonly fileParser = new OpenApiFileParserService();
|
|
10
|
-
private readonly codeGenerator
|
|
15
|
+
private readonly codeGenerator: TypeScriptCodeGeneratorService;
|
|
11
16
|
private readonly fileWriter: SyncFileWriterService;
|
|
12
17
|
private readonly outputPath: string;
|
|
13
18
|
|
|
@@ -17,9 +22,11 @@ export class Generator {
|
|
|
17
22
|
private readonly reporter: Reporter,
|
|
18
23
|
private readonly inputPath: string,
|
|
19
24
|
private readonly _outputDir: string,
|
|
25
|
+
options: GeneratorOptions = {},
|
|
20
26
|
) {
|
|
21
27
|
this.fileWriter = new SyncFileWriterService(this._name, this._version, inputPath);
|
|
22
28
|
this.outputPath = this.fileWriter.resolveOutputPath(this._outputDir);
|
|
29
|
+
this.codeGenerator = new TypeScriptCodeGeneratorService(options);
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
async run(): Promise<number> {
|
|
@@ -3,14 +3,28 @@ import * as ts from 'typescript';
|
|
|
3
3
|
import {z} from 'zod';
|
|
4
4
|
import type {CodeGenerator, SchemaBuilder} from '../interfaces/code-generator.js';
|
|
5
5
|
import type {MethodSchemaType, OpenApiSpecType, ReferenceType} from '../types/openapi.js';
|
|
6
|
+
import type {GeneratorOptions} from '../types/generator-options.js';
|
|
6
7
|
import {MethodSchema, Reference, SchemaProperties} from '../types/openapi.js';
|
|
7
8
|
import {TypeScriptImportBuilderService} from './import-builder.service.js';
|
|
8
9
|
import {TypeScriptTypeBuilderService} from './type-builder.service.js';
|
|
10
|
+
import {
|
|
11
|
+
type NamingConvention,
|
|
12
|
+
type OperationDetails,
|
|
13
|
+
type OperationNameTransformer,
|
|
14
|
+
transformNamingConvention,
|
|
15
|
+
} from '../utils/naming-convention.js';
|
|
9
16
|
|
|
10
17
|
export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuilder {
|
|
11
18
|
private readonly typeBuilder = new TypeScriptTypeBuilderService();
|
|
12
19
|
private readonly importBuilder = new TypeScriptImportBuilderService();
|
|
13
20
|
private readonly printer = ts.createPrinter({newLine: ts.NewLineKind.LineFeed});
|
|
21
|
+
private readonly namingConvention: NamingConvention | undefined;
|
|
22
|
+
private readonly operationNameTransformer: OperationNameTransformer | undefined;
|
|
23
|
+
|
|
24
|
+
constructor(options: GeneratorOptions = {}) {
|
|
25
|
+
this.namingConvention = options.namingConvention;
|
|
26
|
+
this.operationNameTransformer = options.operationNameTransformer;
|
|
27
|
+
}
|
|
14
28
|
|
|
15
29
|
private readonly ZodAST = z.object({
|
|
16
30
|
type: z.enum(['string', 'number', 'boolean', 'object', 'array', 'unknown', 'record']),
|
|
@@ -122,7 +136,7 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
|
|
|
122
136
|
const methods = this.buildClientMethods(openapi, schemas);
|
|
123
137
|
|
|
124
138
|
return ts.factory.createClassDeclaration(
|
|
125
|
-
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
139
|
+
[ts.factory.createToken(ts.SyntaxKind.ExportKeyword), ts.factory.createToken(ts.SyntaxKind.DefaultKeyword)],
|
|
126
140
|
ts.factory.createIdentifier(clientName),
|
|
127
141
|
undefined,
|
|
128
142
|
undefined,
|
|
@@ -261,9 +275,9 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
|
|
|
261
275
|
|
|
262
276
|
private buildHttpRequestMethod(): ts.MethodDeclaration {
|
|
263
277
|
return ts.factory.createMethodDeclaration(
|
|
264
|
-
[ts.factory.createToken(ts.SyntaxKind.AsyncKeyword)],
|
|
278
|
+
[ts.factory.createToken(ts.SyntaxKind.ProtectedKeyword), ts.factory.createToken(ts.SyntaxKind.AsyncKeyword)],
|
|
265
279
|
undefined,
|
|
266
|
-
ts.factory.
|
|
280
|
+
ts.factory.createIdentifier('makeRequest'),
|
|
267
281
|
undefined,
|
|
268
282
|
[this.typeBuilder.createGenericType('T')],
|
|
269
283
|
[
|
|
@@ -894,6 +908,36 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
|
|
|
894
908
|
}, []);
|
|
895
909
|
}
|
|
896
910
|
|
|
911
|
+
/**
|
|
912
|
+
* Transforms operation ID according to the configured naming convention or transformer
|
|
913
|
+
* Ensures the result is a valid TypeScript identifier
|
|
914
|
+
*/
|
|
915
|
+
private transformOperationName(operationId: string, method: string, path: string, schema: MethodSchemaType): string {
|
|
916
|
+
let transformed: string;
|
|
917
|
+
|
|
918
|
+
// Custom transformer takes precedence
|
|
919
|
+
if (this.operationNameTransformer) {
|
|
920
|
+
const details: OperationDetails = {
|
|
921
|
+
operationId,
|
|
922
|
+
method,
|
|
923
|
+
path,
|
|
924
|
+
...(schema.tags !== undefined && {tags: schema.tags}),
|
|
925
|
+
...(schema.summary !== undefined && {summary: schema.summary}),
|
|
926
|
+
...(schema.description !== undefined && {description: schema.description}),
|
|
927
|
+
};
|
|
928
|
+
transformed = this.operationNameTransformer(details);
|
|
929
|
+
} else if (this.namingConvention) {
|
|
930
|
+
// Apply naming convention if specified
|
|
931
|
+
transformed = transformNamingConvention(operationId, this.namingConvention);
|
|
932
|
+
} else {
|
|
933
|
+
// Return original operationId if no transformation is configured
|
|
934
|
+
transformed = operationId;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// Sanitize to ensure valid TypeScript identifier (handles edge cases from custom transformers)
|
|
938
|
+
return this.typeBuilder.sanitizeIdentifier(transformed);
|
|
939
|
+
}
|
|
940
|
+
|
|
897
941
|
private buildEndpointMethod(
|
|
898
942
|
method: string,
|
|
899
943
|
path: string,
|
|
@@ -958,10 +1002,7 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
|
|
|
958
1002
|
|
|
959
1003
|
// Call makeRequest
|
|
960
1004
|
const makeRequestCall = ts.factory.createCallExpression(
|
|
961
|
-
ts.factory.createPropertyAccessExpression(
|
|
962
|
-
ts.factory.createThis(),
|
|
963
|
-
ts.factory.createPrivateIdentifier('#makeRequest'),
|
|
964
|
-
),
|
|
1005
|
+
ts.factory.createPropertyAccessExpression(ts.factory.createThis(), ts.factory.createIdentifier('makeRequest')),
|
|
965
1006
|
undefined,
|
|
966
1007
|
[ts.factory.createStringLiteral(method.toUpperCase(), true), pathExpression, optionsExpression],
|
|
967
1008
|
);
|
|
@@ -979,10 +1020,12 @@ export class TypeScriptCodeGeneratorService implements CodeGenerator, SchemaBuil
|
|
|
979
1020
|
statements.push(ts.factory.createReturnStatement(ts.factory.createAwaitExpression(makeRequestCall)));
|
|
980
1021
|
}
|
|
981
1022
|
|
|
1023
|
+
const transformedOperationId = this.transformOperationName(String(schema.operationId), method, path, schema);
|
|
1024
|
+
|
|
982
1025
|
const methodDeclaration = ts.factory.createMethodDeclaration(
|
|
983
1026
|
[ts.factory.createToken(ts.SyntaxKind.AsyncKeyword)],
|
|
984
1027
|
undefined,
|
|
985
|
-
ts.factory.createIdentifier(
|
|
1028
|
+
ts.factory.createIdentifier(transformedOperationId),
|
|
986
1029
|
undefined,
|
|
987
1030
|
undefined,
|
|
988
1031
|
parameters,
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type {NamingConvention, OperationNameTransformer} from '../utils/naming-convention.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for the Generator class.
|
|
5
|
+
*
|
|
6
|
+
* These options control how operation IDs are transformed during code generation.
|
|
7
|
+
* You can either use a predefined naming convention or provide a custom transformer function.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* // Using a naming convention
|
|
12
|
+
* const generator = new Generator(..., {
|
|
13
|
+
* namingConvention: 'camelCase'
|
|
14
|
+
* });
|
|
15
|
+
*
|
|
16
|
+
* // Using a custom transformer
|
|
17
|
+
* const generator = new Generator(..., {
|
|
18
|
+
* operationNameTransformer: (details) => {
|
|
19
|
+
* return `${details.method}_${details.operationId}`;
|
|
20
|
+
* }
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export interface GeneratorOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Naming convention to apply to operation IDs.
|
|
27
|
+
*
|
|
28
|
+
* If provided, all operation IDs will be transformed according to the specified convention.
|
|
29
|
+
* This is useful when OpenAPI specs have inconsistent or poorly named operation IDs.
|
|
30
|
+
*
|
|
31
|
+
* **Note:** If `operationNameTransformer` is also provided, it takes precedence.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* { namingConvention: 'camelCase' } // Transforms 'get_user_by_id' → 'getUserById'
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
namingConvention?: NamingConvention;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Custom transformer function for operation names.
|
|
42
|
+
*
|
|
43
|
+
* If provided, this function will be called for each operation with full operation details.
|
|
44
|
+
* This allows for advanced customization based on HTTP method, path, tags, etc.
|
|
45
|
+
*
|
|
46
|
+
* **Note:** This takes precedence over `namingConvention` if both are provided.
|
|
47
|
+
* The returned name will be sanitized to ensure it's a valid TypeScript identifier.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* {
|
|
52
|
+
* operationNameTransformer: (details) => {
|
|
53
|
+
* const tag = details.tags?.[0] || 'default';
|
|
54
|
+
* return `${details.method.toUpperCase()}_${tag}_${details.operationId}`;
|
|
55
|
+
* }
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
operationNameTransformer?: OperationNameTransformer;
|
|
60
|
+
}
|