zod-codegen 1.6.3 → 1.7.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 +50 -48
- package/.github/workflows/release.yml +13 -3
- package/.husky/commit-msg +1 -1
- package/.husky/pre-commit +1 -1
- package/.lintstagedrc.json +5 -1
- package/.nvmrc +1 -1
- package/.prettierrc.json +12 -5
- package/CHANGELOG.md +17 -0
- package/CONTRIBUTING.md +12 -12
- package/EXAMPLES.md +135 -57
- package/PERFORMANCE.md +4 -4
- package/README.md +87 -64
- package/SECURITY.md +1 -1
- package/dist/src/cli.js +11 -18
- package/dist/src/generator.d.ts +2 -2
- package/dist/src/generator.d.ts.map +1 -1
- package/dist/src/generator.js +5 -3
- package/dist/src/interfaces/code-generator.d.ts.map +1 -1
- package/dist/src/services/code-generator.service.d.ts +3 -1
- package/dist/src/services/code-generator.service.d.ts.map +1 -1
- package/dist/src/services/code-generator.service.js +236 -219
- package/dist/src/services/file-reader.service.d.ts +2 -0
- package/dist/src/services/file-reader.service.d.ts.map +1 -1
- package/dist/src/services/file-reader.service.js +25 -11
- package/dist/src/services/file-writer.service.d.ts.map +1 -1
- package/dist/src/services/file-writer.service.js +2 -2
- package/dist/src/services/import-builder.service.d.ts.map +1 -1
- package/dist/src/services/import-builder.service.js +3 -3
- package/dist/src/services/type-builder.service.d.ts.map +1 -1
- package/dist/src/types/generator-options.d.ts.map +1 -1
- package/dist/src/types/openapi.d.ts.map +1 -1
- package/dist/src/types/openapi.js +20 -20
- package/dist/src/utils/error-handler.d.ts.map +1 -1
- package/dist/src/utils/naming-convention.d.ts.map +1 -1
- package/dist/src/utils/naming-convention.js +6 -3
- package/dist/src/utils/signal-handler.d.ts.map +1 -1
- package/dist/tests/integration/cli-comprehensive.test.d.ts +2 -0
- package/dist/tests/integration/cli-comprehensive.test.d.ts.map +1 -0
- package/dist/tests/integration/cli-comprehensive.test.js +123 -0
- package/dist/tests/integration/cli.test.d.ts +2 -0
- package/dist/tests/integration/cli.test.d.ts.map +1 -0
- package/dist/tests/integration/cli.test.js +25 -0
- package/dist/tests/integration/error-scenarios.test.d.ts +2 -0
- package/dist/tests/integration/error-scenarios.test.d.ts.map +1 -0
- package/dist/tests/integration/error-scenarios.test.js +169 -0
- package/dist/tests/integration/snapshots.test.d.ts +2 -0
- package/dist/tests/integration/snapshots.test.d.ts.map +1 -0
- package/dist/tests/integration/snapshots.test.js +100 -0
- package/dist/tests/unit/code-generator-edge-cases.test.d.ts +2 -0
- package/dist/tests/unit/code-generator-edge-cases.test.d.ts.map +1 -0
- package/dist/tests/unit/code-generator-edge-cases.test.js +506 -0
- package/dist/tests/unit/code-generator.test.d.ts +2 -0
- package/dist/tests/unit/code-generator.test.d.ts.map +1 -0
- package/dist/tests/unit/code-generator.test.js +1364 -0
- package/dist/tests/unit/file-reader.test.d.ts +2 -0
- package/dist/tests/unit/file-reader.test.d.ts.map +1 -0
- package/dist/tests/unit/file-reader.test.js +153 -0
- package/dist/tests/unit/generator.test.d.ts +2 -0
- package/dist/tests/unit/generator.test.d.ts.map +1 -0
- package/dist/tests/unit/generator.test.js +119 -0
- package/dist/tests/unit/naming-convention.test.d.ts +2 -0
- package/dist/tests/unit/naming-convention.test.d.ts.map +1 -0
- package/dist/tests/unit/naming-convention.test.js +256 -0
- package/dist/tests/unit/reporter.test.d.ts +2 -0
- package/dist/tests/unit/reporter.test.d.ts.map +1 -0
- package/dist/tests/unit/reporter.test.js +44 -0
- package/dist/tests/unit/type-builder.test.d.ts +2 -0
- package/dist/tests/unit/type-builder.test.d.ts.map +1 -0
- package/dist/tests/unit/type-builder.test.js +108 -0
- package/dist/vitest.config.d.ts.map +1 -1
- package/dist/vitest.config.js +10 -20
- package/eslint.config.mjs +38 -28
- package/examples/.gitkeep +1 -1
- package/examples/README.md +4 -2
- package/examples/petstore/README.md +18 -17
- package/examples/petstore/{type.ts → api.ts} +158 -74
- package/examples/petstore/authenticated-usage.ts +6 -4
- package/examples/petstore/basic-usage.ts +4 -3
- package/examples/petstore/error-handling-usage.ts +84 -0
- package/examples/petstore/retry-handler-usage.ts +11 -18
- package/examples/petstore/server-variables-usage.ts +10 -10
- package/examples/pokeapi/README.md +8 -8
- package/examples/pokeapi/api.ts +218 -0
- package/examples/pokeapi/basic-usage.ts +3 -2
- package/examples/pokeapi/custom-client.ts +5 -4
- package/package.json +17 -21
- package/src/cli.ts +20 -25
- package/src/generator.ts +13 -11
- package/src/interfaces/code-generator.ts +1 -1
- package/src/services/code-generator.service.ts +799 -1120
- package/src/services/file-reader.service.ts +35 -15
- package/src/services/file-writer.service.ts +7 -7
- package/src/services/import-builder.service.ts +9 -13
- package/src/services/type-builder.service.ts +8 -19
- package/src/types/generator-options.ts +1 -1
- package/src/types/openapi.ts +22 -22
- package/src/utils/error-handler.ts +2 -2
- package/src/utils/naming-convention.ts +13 -10
- package/src/utils/reporter.ts +2 -2
- package/src/utils/signal-handler.ts +7 -8
- package/tests/integration/cli-comprehensive.test.ts +53 -31
- package/tests/integration/cli.test.ts +5 -5
- package/tests/integration/error-scenarios.test.ts +20 -26
- package/tests/integration/snapshots.test.ts +19 -23
- package/tests/unit/code-generator-edge-cases.test.ts +133 -133
- package/tests/unit/code-generator.test.ts +431 -330
- package/tests/unit/file-reader.test.ts +58 -18
- package/tests/unit/generator.test.ts +30 -18
- package/tests/unit/naming-convention.test.ts +27 -27
- package/tests/unit/type-builder.test.ts +2 -2
- package/tsconfig.json +5 -3
- package/vitest.config.ts +11 -21
- 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 -33
- package/dist/src/assets/manifest.json +0 -5
- package/examples/pokeapi/type.ts +0 -109
- package/generated/type.ts +0 -371
- package/scripts/update-manifest.ts +0 -49
- package/src/assets/manifest.json +0 -5
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { OpenApiFileParser, OpenApiFileReader } from '../interfaces/file-reader';
|
|
2
2
|
import type { OpenApiSpecType } from '../types/openapi';
|
|
3
3
|
export declare class SyncFileReaderService implements OpenApiFileReader {
|
|
4
|
+
private isUrl;
|
|
4
5
|
readFile(path: string): Promise<string>;
|
|
6
|
+
private fetchUrl;
|
|
5
7
|
}
|
|
6
8
|
export declare class OpenApiFileParserService implements OpenApiFileParser<OpenApiSpecType> {
|
|
7
9
|
parse(input: unknown): OpenApiSpecType;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-reader.service.d.ts","sourceRoot":"","sources":["../../../src/services/file-reader.service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"file-reader.service.d.ts","sourceRoot":"","sources":["../../../src/services/file-reader.service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AACtF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAGxD,qBAAa,qBAAsB,YAAW,iBAAiB;IAC7D,OAAO,CAAC,KAAK;IASP,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YAQ/B,QAAQ;CAkBvB;AAED,qBAAa,wBAAyB,YAAW,iBAAiB,CAAC,eAAe,CAAC;IACjF,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe;CAgBvC"}
|
|
@@ -1,22 +1,36 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
1
|
import { load } from 'js-yaml';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
3
|
import { OpenApiSpec } from '../types/openapi.js';
|
|
4
4
|
export class SyncFileReaderService {
|
|
5
|
-
|
|
6
|
-
// Check if path is a URL
|
|
5
|
+
isUrl(path) {
|
|
7
6
|
try {
|
|
8
7
|
const url = new URL(path);
|
|
9
|
-
|
|
10
|
-
const response = await fetch(url.toString());
|
|
11
|
-
if (!response.ok) {
|
|
12
|
-
throw new Error(`Failed to fetch ${path}: ${String(response.status)} ${response.statusText}`);
|
|
13
|
-
}
|
|
14
|
-
return await response.text();
|
|
8
|
+
return url.protocol === 'http:' || url.protocol === 'https:';
|
|
15
9
|
}
|
|
16
10
|
catch {
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
async readFile(path) {
|
|
15
|
+
if (this.isUrl(path)) {
|
|
16
|
+
return await this.fetchUrl(path);
|
|
17
|
+
}
|
|
18
|
+
return readFileSync(path, 'utf8');
|
|
19
|
+
}
|
|
20
|
+
async fetchUrl(path) {
|
|
21
|
+
const url = new URL(path);
|
|
22
|
+
const headers = {};
|
|
23
|
+
if (url.username || url.password) {
|
|
24
|
+
const credentials = `${decodeURIComponent(url.username)}:${decodeURIComponent(url.password)}`;
|
|
25
|
+
headers['Authorization'] = `Basic ${btoa(credentials)}`;
|
|
26
|
+
url.username = '';
|
|
27
|
+
url.password = '';
|
|
28
|
+
}
|
|
29
|
+
const response = await fetch(url.toString(), { headers });
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`Failed to fetch ${path}: ${String(response.status)} ${response.statusText}`);
|
|
19
32
|
}
|
|
33
|
+
return await response.text();
|
|
20
34
|
}
|
|
21
35
|
}
|
|
22
36
|
export class OpenApiFileParserService {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-writer.service.d.ts","sourceRoot":"","sources":["../../../src/services/file-writer.service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"file-writer.service.d.ts","sourceRoot":"","sources":["../../../src/services/file-writer.service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAC;AAE/D,qBAAa,qBAAsB,YAAW,UAAU;IAEpD,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAFT,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM;IAGpC,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;IAqBlD,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,SAAW,GAAG,MAAM;CAGlE"}
|
|
@@ -18,7 +18,7 @@ export class SyncFileWriterService {
|
|
|
18
18
|
'/* eslint-disable */',
|
|
19
19
|
'// @ts-nocheck',
|
|
20
20
|
'',
|
|
21
|
-
content
|
|
21
|
+
content
|
|
22
22
|
].join('\n');
|
|
23
23
|
const dirPath = dirname(filePath);
|
|
24
24
|
if (!existsSync(dirPath)) {
|
|
@@ -26,7 +26,7 @@ export class SyncFileWriterService {
|
|
|
26
26
|
}
|
|
27
27
|
writeFileSync(filePath, generatedContent);
|
|
28
28
|
}
|
|
29
|
-
resolveOutputPath(outputDir, fileName = '
|
|
29
|
+
resolveOutputPath(outputDir, fileName = 'api.ts') {
|
|
30
30
|
return resolve(outputDir, fileName);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"import-builder.service.d.ts","sourceRoot":"","sources":["../../../src/services/import-builder.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,
|
|
1
|
+
{"version":3,"file":"import-builder.service.d.ts","sourceRoot":"","sources":["../../../src/services/import-builder.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAKlE,QAAA,MAAM,aAAa;;;iBAGjB,CAAC;AAEH,KAAK,iBAAiB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEvD,qBAAa,8BAA+B,YAAW,aAAa;IAClE,YAAY,IAAI,EAAE,CAAC,iBAAiB,EAAE;IAQtC,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,GAAG,EAAE,CAAC,iBAAiB;CAiC/E"}
|
|
@@ -4,14 +4,14 @@ const IsTypeImport = z.boolean();
|
|
|
4
4
|
const ImportedElement = z.record(z.string(), IsTypeImport);
|
|
5
5
|
const ImportOptions = z.object({
|
|
6
6
|
defaultImport: ImportedElement.optional(),
|
|
7
|
-
namedImports: ImportedElement.optional()
|
|
7
|
+
namedImports: ImportedElement.optional()
|
|
8
8
|
});
|
|
9
9
|
export class TypeScriptImportBuilderService {
|
|
10
10
|
buildImports() {
|
|
11
11
|
return [
|
|
12
12
|
this.createImport('zod', {
|
|
13
|
-
namedImports: { z: false }
|
|
14
|
-
})
|
|
13
|
+
namedImports: { z: false }
|
|
14
|
+
})
|
|
15
15
|
];
|
|
16
16
|
}
|
|
17
17
|
createImport(target, options) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"type-builder.service.d.ts","sourceRoot":"","sources":["../../../src/services/type-builder.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"type-builder.service.d.ts","sourceRoot":"","sources":["../../../src/services/type-builder.service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAEhE,qBAAa,4BAA6B,YAAW,WAAW;IAC9D,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,CAAC,QAAQ;IA4BpC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,UAAQ,GAAG,EAAE,CAAC,mBAAmB;IAYtF,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,EAAE,CAAC,QAAQ,EAAE,YAAY,CAAC,EAAE,EAAE,CAAC,UAAU,EAAE,UAAU,UAAQ,GAAG,EAAE,CAAC,oBAAoB;IAWrI,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,EAAE,CAAC,wBAAwB;IAI5D,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAMxC,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAIjC,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAGnC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generator-options.d.ts","sourceRoot":"","sources":["../../../src/types/generator-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"generator-options.d.ts","sourceRoot":"","sources":["../../../src/types/generator-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAE7F;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;;;;;OAYG;IACH,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IAEpC;;;;;;;;;;;;;;;;;;OAkBG;IACH,wBAAwB,CAAC,EAAE,wBAAwB,CAAC;CACrD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../../../src/types/openapi.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"openapi.d.ts","sourceRoot":"","sources":["../../../src/types/openapi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
export const Reference = z.object({
|
|
3
|
-
$ref: z.string().optional()
|
|
3
|
+
$ref: z.string().optional()
|
|
4
4
|
});
|
|
5
5
|
const BaseSchemaProperties = z.object({
|
|
6
6
|
$ref: z.string().optional(),
|
|
@@ -36,27 +36,27 @@ const BaseSchemaProperties = z.object({
|
|
|
36
36
|
xml: z
|
|
37
37
|
.object({
|
|
38
38
|
name: z.string().optional(),
|
|
39
|
-
wrapped: z.boolean().optional()
|
|
39
|
+
wrapped: z.boolean().optional()
|
|
40
40
|
})
|
|
41
41
|
.optional(),
|
|
42
42
|
externalDocs: Reference.optional(),
|
|
43
43
|
example: z.unknown().optional(),
|
|
44
|
-
deprecated: z.boolean().optional()
|
|
44
|
+
deprecated: z.boolean().optional()
|
|
45
45
|
});
|
|
46
46
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
47
|
export const SchemaProperties = z.lazy(() => BaseSchemaProperties.extend({
|
|
48
48
|
properties: z.record(z.string(), SchemaProperties).optional(),
|
|
49
|
-
items: SchemaProperties.optional()
|
|
49
|
+
items: SchemaProperties.optional()
|
|
50
50
|
}));
|
|
51
51
|
const ServerVariable = z.object({
|
|
52
52
|
default: z.string(),
|
|
53
53
|
description: z.string().optional(),
|
|
54
|
-
enum: z.array(z.string()).optional()
|
|
54
|
+
enum: z.array(z.string()).optional()
|
|
55
55
|
});
|
|
56
56
|
const Server = z.object({
|
|
57
57
|
url: z.string(), // Allow templated URLs with {variables}
|
|
58
58
|
description: z.string().optional(),
|
|
59
|
-
variables: z.record(z.string(), ServerVariable).optional()
|
|
59
|
+
variables: z.record(z.string(), ServerVariable).optional()
|
|
60
60
|
});
|
|
61
61
|
export const Parameter = z.object({
|
|
62
62
|
$ref: z.string().optional(),
|
|
@@ -69,7 +69,7 @@ export const Parameter = z.object({
|
|
|
69
69
|
style: z.string().optional(),
|
|
70
70
|
explode: z.boolean().optional(),
|
|
71
71
|
allowReserved: z.boolean().optional(),
|
|
72
|
-
schema: SchemaProperties.optional()
|
|
72
|
+
schema: SchemaProperties.optional()
|
|
73
73
|
});
|
|
74
74
|
const ResponseHeader = z.object({
|
|
75
75
|
$ref: z.string().optional(),
|
|
@@ -80,22 +80,22 @@ const ResponseHeader = z.object({
|
|
|
80
80
|
style: z.string().optional(),
|
|
81
81
|
explode: z.boolean().optional(),
|
|
82
82
|
allowReserved: z.boolean().optional(),
|
|
83
|
-
schema: Reference.optional()
|
|
83
|
+
schema: Reference.optional()
|
|
84
84
|
});
|
|
85
85
|
const MediaType = z.object({
|
|
86
|
-
schema: z.unknown().optional()
|
|
86
|
+
schema: z.unknown().optional()
|
|
87
87
|
});
|
|
88
88
|
export const Response = z.object({
|
|
89
89
|
$ref: z.string().optional(),
|
|
90
90
|
description: z.string(),
|
|
91
91
|
headers: z.record(z.string(), ResponseHeader).optional(),
|
|
92
|
-
content: z.record(z.string(), MediaType).optional()
|
|
92
|
+
content: z.record(z.string(), MediaType).optional()
|
|
93
93
|
});
|
|
94
94
|
export const RequestBody = z.object({
|
|
95
95
|
$ref: z.string().optional(),
|
|
96
96
|
description: z.string().optional(),
|
|
97
97
|
required: z.boolean().optional(),
|
|
98
|
-
content: z.record(z.string(), MediaType).optional()
|
|
98
|
+
content: z.record(z.string(), MediaType).optional()
|
|
99
99
|
});
|
|
100
100
|
export const MethodSchema = z.object({
|
|
101
101
|
summary: z.string().optional(),
|
|
@@ -105,7 +105,7 @@ export const MethodSchema = z.object({
|
|
|
105
105
|
requestBody: RequestBody.optional(),
|
|
106
106
|
responses: z.record(z.string(), Response).optional(),
|
|
107
107
|
tags: z.array(z.string()).optional(),
|
|
108
|
-
deprecated: z.boolean().optional()
|
|
108
|
+
deprecated: z.boolean().optional()
|
|
109
109
|
});
|
|
110
110
|
export const PathItem = z.object({
|
|
111
111
|
$ref: z.string().optional(),
|
|
@@ -119,7 +119,7 @@ export const PathItem = z.object({
|
|
|
119
119
|
head: MethodSchema.optional(),
|
|
120
120
|
options: MethodSchema.optional(),
|
|
121
121
|
trace: MethodSchema.optional(),
|
|
122
|
-
parameters: z.array(Parameter).optional()
|
|
122
|
+
parameters: z.array(Parameter).optional()
|
|
123
123
|
});
|
|
124
124
|
const Info = z.object({
|
|
125
125
|
title: z.string().min(1),
|
|
@@ -130,25 +130,25 @@ const Info = z.object({
|
|
|
130
130
|
.object({
|
|
131
131
|
name: z.string().optional(),
|
|
132
132
|
email: z.email().optional(),
|
|
133
|
-
url: z.url().optional()
|
|
133
|
+
url: z.url().optional()
|
|
134
134
|
})
|
|
135
135
|
.optional(),
|
|
136
136
|
license: z
|
|
137
137
|
.object({
|
|
138
138
|
name: z.string().min(1),
|
|
139
|
-
url: z.url().optional()
|
|
139
|
+
url: z.url().optional()
|
|
140
140
|
})
|
|
141
|
-
.optional()
|
|
141
|
+
.optional()
|
|
142
142
|
});
|
|
143
143
|
const SecurityRequirement = z.record(z.string(), z.array(z.string()));
|
|
144
144
|
const Tag = z.object({
|
|
145
145
|
name: z.string().min(1),
|
|
146
146
|
description: z.string().optional(),
|
|
147
|
-
externalDocs: Reference.optional()
|
|
147
|
+
externalDocs: Reference.optional()
|
|
148
148
|
});
|
|
149
149
|
const ExternalDocumentation = z.object({
|
|
150
150
|
description: z.string().optional(),
|
|
151
|
-
url: z.url()
|
|
151
|
+
url: z.url()
|
|
152
152
|
});
|
|
153
153
|
const Components = z.object({
|
|
154
154
|
schemas: z.record(z.string(), SchemaProperties).optional(),
|
|
@@ -159,7 +159,7 @@ const Components = z.object({
|
|
|
159
159
|
headers: z.record(z.string(), ResponseHeader).optional(),
|
|
160
160
|
securitySchemes: z.record(z.string(), Reference).optional(),
|
|
161
161
|
links: z.record(z.string(), Reference).optional(),
|
|
162
|
-
callbacks: z.record(z.string(), Reference).optional()
|
|
162
|
+
callbacks: z.record(z.string(), Reference).optional()
|
|
163
163
|
});
|
|
164
164
|
export const OpenApiSpec = z.object({
|
|
165
165
|
openapi: z.string().regex(/^3\.\d+\.\d+$/, 'OpenAPI version must be in format 3.x.x'),
|
|
@@ -169,5 +169,5 @@ export const OpenApiSpec = z.object({
|
|
|
169
169
|
components: Components.optional(),
|
|
170
170
|
security: z.array(SecurityRequirement).optional(),
|
|
171
171
|
tags: z.array(Tag).optional(),
|
|
172
|
-
externalDocs: ExternalDocumentation.optional()
|
|
172
|
+
externalDocs: ExternalDocumentation.optional()
|
|
173
173
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../../src/utils/error-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../../src/utils/error-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,CAAC,OAAO,EAAE,WAAW,MAAM,EAAE,UAAU,QAAQ,WAAS,IAGpG,CAAC;AAEF,eAAO,MAAM,YAAY,GAAI,SAAS,MAAM,CAAC,OAAO,EAAE,WAAW,MAAM,EAAE,UAAU,QAAQ,KAAG,IAK7F,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"naming-convention.d.ts","sourceRoot":"","sources":["../../../src/utils/naming-convention.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"naming-convention.d.ts","sourceRoot":"","sources":["../../../src/utils/naming-convention.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,sBAAsB,GAAG,sBAAsB,CAAC;AAE1I;;;;;;;;;;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;AAmH7E;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,gBAAgB,GAAG,MAAM,CAsB7F"}
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Capitalizes the first letter of a word
|
|
3
3
|
*/
|
|
4
4
|
function capitalize(word) {
|
|
5
|
-
if (word.length === 0)
|
|
5
|
+
if (word.length === 0) {
|
|
6
6
|
return word;
|
|
7
|
+
}
|
|
7
8
|
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
8
9
|
}
|
|
9
10
|
/**
|
|
@@ -52,11 +53,13 @@ function normalizeToWords(input) {
|
|
|
52
53
|
* Converts words array to camelCase
|
|
53
54
|
*/
|
|
54
55
|
function toCamelCase(words) {
|
|
55
|
-
if (words.length === 0)
|
|
56
|
+
if (words.length === 0) {
|
|
56
57
|
return '';
|
|
58
|
+
}
|
|
57
59
|
const [first, ...rest] = words;
|
|
58
|
-
if (!first)
|
|
60
|
+
if (!first) {
|
|
59
61
|
return '';
|
|
62
|
+
}
|
|
60
63
|
return first + rest.map((w) => capitalize(w)).join('');
|
|
61
64
|
}
|
|
62
65
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signal-handler.d.ts","sourceRoot":"","sources":["../../../src/utils/signal-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"signal-handler.d.ts","sourceRoot":"","sources":["../../../src/utils/signal-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,eAAO,MAAM,cAAc,GAAI,SAAS,MAAM,CAAC,OAAO,EAAE,WAAW,MAAM,EAAE,OAAO,MAAM,CAAC,OAAO,EAAE,UAAU,QAAQ,WAAS,IAI5H,CAAC;AAEF,eAAO,MAAM,aAAa,GAAI,SAAS,MAAM,CAAC,OAAO,EAAE,WAAW,MAAM,EAAE,UAAU,QAAQ,KAAG,IAK9F,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-comprehensive.test.d.ts","sourceRoot":"","sources":["../../../tests/integration/cli-comprehensive.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { resolve } from 'node:path';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
describe('CLI Comprehensive Integration', () => {
|
|
6
|
+
const cwd = resolve(__dirname, '../..');
|
|
7
|
+
const testOutputDir = resolve(cwd, 'test-output-cli');
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
if (existsSync(testOutputDir)) {
|
|
10
|
+
rmSync(testOutputDir, { recursive: true, force: true });
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
if (existsSync(testOutputDir)) {
|
|
15
|
+
rmSync(testOutputDir, { recursive: true, force: true });
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
describe('Basic CLI Usage', () => {
|
|
19
|
+
it('should generate code with default output directory', () => {
|
|
20
|
+
execSync('node ./dist/src/cli.js --input ./samples/swagger-petstore.yaml --output generated', {
|
|
21
|
+
encoding: 'utf-8',
|
|
22
|
+
cwd
|
|
23
|
+
});
|
|
24
|
+
const outputFile = resolve(cwd, 'generated/api.ts');
|
|
25
|
+
expect(existsSync(outputFile)).toBe(true);
|
|
26
|
+
const content = readFileSync(outputFile, 'utf-8');
|
|
27
|
+
expect(content).toContain('SwaggerPetstoreOpenAPI30');
|
|
28
|
+
});
|
|
29
|
+
it('should generate code with custom output directory', () => {
|
|
30
|
+
execSync(`node ./dist/src/cli.js --input ./samples/swagger-petstore.yaml --output ${testOutputDir}`, {
|
|
31
|
+
encoding: 'utf-8',
|
|
32
|
+
cwd
|
|
33
|
+
});
|
|
34
|
+
const outputFile = resolve(testOutputDir, 'api.ts');
|
|
35
|
+
expect(existsSync(outputFile)).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
it('should accept naming convention option', () => {
|
|
38
|
+
execSync(`node ./dist/src/cli.js --input ./samples/swagger-petstore.yaml --output ${testOutputDir} --naming-convention camelCase`, {
|
|
39
|
+
encoding: 'utf-8',
|
|
40
|
+
cwd
|
|
41
|
+
});
|
|
42
|
+
const outputFile = resolve(testOutputDir, 'api.ts');
|
|
43
|
+
const content = readFileSync(outputFile, 'utf-8');
|
|
44
|
+
// Verify camelCase is applied (operation IDs should be camelCase)
|
|
45
|
+
expect(content).toMatch(/async \w+\(/);
|
|
46
|
+
});
|
|
47
|
+
it('should reject invalid naming convention', () => {
|
|
48
|
+
expect(() => {
|
|
49
|
+
execSync(`node ./dist/src/cli.js --input ./samples/swagger-petstore.yaml --output ${testOutputDir} --naming-convention invalid`, {
|
|
50
|
+
encoding: 'utf-8',
|
|
51
|
+
cwd,
|
|
52
|
+
stdio: 'pipe'
|
|
53
|
+
});
|
|
54
|
+
}).toThrow();
|
|
55
|
+
});
|
|
56
|
+
it('should write to custom file when output is a .ts path', () => {
|
|
57
|
+
const outputFile = resolve(testOutputDir, 'client.ts');
|
|
58
|
+
execSync(`node ./dist/src/cli.js --input ./samples/swagger-petstore.yaml --output "${outputFile}"`, {
|
|
59
|
+
encoding: 'utf-8',
|
|
60
|
+
cwd
|
|
61
|
+
});
|
|
62
|
+
expect(existsSync(outputFile)).toBe(true);
|
|
63
|
+
const content = readFileSync(outputFile, 'utf-8');
|
|
64
|
+
expect(content).toContain('SwaggerPetstoreOpenAPI30');
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
describe('CLI Error Handling', () => {
|
|
68
|
+
it('should exit with code 1 on invalid input file', () => {
|
|
69
|
+
try {
|
|
70
|
+
execSync('node ./dist/src/cli.js --input ./nonexistent.yaml --output generated', {
|
|
71
|
+
encoding: 'utf-8',
|
|
72
|
+
cwd,
|
|
73
|
+
stdio: 'pipe'
|
|
74
|
+
});
|
|
75
|
+
expect.fail('Should have thrown an error');
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
// Command should fail
|
|
79
|
+
expect(error).toBeDefined();
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
it('should require input option', () => {
|
|
83
|
+
expect(() => {
|
|
84
|
+
execSync('node ./dist/src/cli.js --output generated', {
|
|
85
|
+
encoding: 'utf-8',
|
|
86
|
+
cwd,
|
|
87
|
+
stdio: 'pipe'
|
|
88
|
+
});
|
|
89
|
+
}).toThrow();
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
describe('CLI with Different Spec Formats', () => {
|
|
93
|
+
it('should handle JSON format', () => {
|
|
94
|
+
execSync(`node ./dist/src/cli.js --input ./samples/pokeapi-openapi.json --output ${testOutputDir}`, {
|
|
95
|
+
encoding: 'utf-8',
|
|
96
|
+
cwd
|
|
97
|
+
});
|
|
98
|
+
const outputFile = resolve(testOutputDir, 'api.ts');
|
|
99
|
+
expect(existsSync(outputFile)).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
it('should handle YAML format', () => {
|
|
102
|
+
execSync(`node ./dist/src/cli.js --input ./samples/swagger-petstore.yaml --output ${testOutputDir}`, {
|
|
103
|
+
encoding: 'utf-8',
|
|
104
|
+
cwd
|
|
105
|
+
});
|
|
106
|
+
const outputFile = resolve(testOutputDir, 'api.ts');
|
|
107
|
+
expect(existsSync(outputFile)).toBe(true);
|
|
108
|
+
});
|
|
109
|
+
it('should generate code from a remote URL', () => {
|
|
110
|
+
execSync(`node ./dist/src/cli.js --input https://petstore3.swagger.io/api/v3/openapi.json --output ${testOutputDir}`, {
|
|
111
|
+
encoding: 'utf-8',
|
|
112
|
+
cwd,
|
|
113
|
+
timeout: 30000
|
|
114
|
+
});
|
|
115
|
+
const outputFile = resolve(testOutputDir, 'api.ts');
|
|
116
|
+
expect(existsSync(outputFile)).toBe(true);
|
|
117
|
+
const content = readFileSync(outputFile, 'utf-8');
|
|
118
|
+
expect(content).toContain('export default class');
|
|
119
|
+
expect(content).toContain('class ResponseValidationError<T> extends Error');
|
|
120
|
+
expect(content).toContain('import { z }');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.test.d.ts","sourceRoot":"","sources":["../../../tests/integration/cli.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
describe('CLI Integration', () => {
|
|
5
|
+
describe('--help', () => {
|
|
6
|
+
it('should display help information', () => {
|
|
7
|
+
const result = execSync('node ./dist/src/cli.js --help', {
|
|
8
|
+
encoding: 'utf-8',
|
|
9
|
+
cwd: resolve(__dirname, '../..')
|
|
10
|
+
});
|
|
11
|
+
expect(result).toContain('Usage:');
|
|
12
|
+
expect(result).toContain('--input');
|
|
13
|
+
expect(result).toContain('--output');
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
describe('--version', () => {
|
|
17
|
+
it('should display version information', () => {
|
|
18
|
+
const result = execSync('node ./dist/src/cli.js --version', {
|
|
19
|
+
encoding: 'utf-8',
|
|
20
|
+
cwd: resolve(__dirname, '../..')
|
|
21
|
+
});
|
|
22
|
+
expect(result).toMatch(/\d+\.\d+\.\d+/);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-scenarios.test.d.ts","sourceRoot":"","sources":["../../../tests/integration/error-scenarios.test.ts"],"names":[],"mappings":""}
|