zod-codegen 1.6.3 → 1.7.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 +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 +11 -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.map +1 -1
- package/dist/src/services/file-reader.service.js +1 -1
- 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 +110 -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 +125 -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 +6 -5
- 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 +38 -32
- 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 +14 -14
- 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 +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;IACvD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAgB9C;AAED,qBAAa,wBAAyB,YAAW,iBAAiB,CAAC,eAAe,CAAC;IACjF,KAAK,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe;CAgBvC"}
|
|
@@ -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,110 @@
|
|
|
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
|
+
});
|
|
110
|
+
});
|
|
@@ -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":""}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { Generator } from '../../src/generator';
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const testOutputDir = join(__dirname, '../../test-output-errors');
|
|
8
|
+
describe('Error Scenarios', () => {
|
|
9
|
+
let generator;
|
|
10
|
+
let mockReporter;
|
|
11
|
+
const logSpy = vi.fn();
|
|
12
|
+
const errorSpy = vi.fn();
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
if (existsSync(testOutputDir)) {
|
|
15
|
+
rmSync(testOutputDir, { recursive: true, force: true });
|
|
16
|
+
}
|
|
17
|
+
mkdirSync(testOutputDir, { recursive: true });
|
|
18
|
+
mockReporter = {
|
|
19
|
+
log: logSpy,
|
|
20
|
+
error: errorSpy
|
|
21
|
+
};
|
|
22
|
+
logSpy.mockClear();
|
|
23
|
+
errorSpy.mockClear();
|
|
24
|
+
});
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
if (existsSync(testOutputDir)) {
|
|
27
|
+
rmSync(testOutputDir, { recursive: true, force: true });
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
describe('File Reading Errors', () => {
|
|
31
|
+
it('should handle non-existent local file', async () => {
|
|
32
|
+
generator = new Generator('test-app', '1.0.0', mockReporter, './non-existent-file.yaml', testOutputDir);
|
|
33
|
+
const exitCode = await generator.run();
|
|
34
|
+
expect(exitCode).toBe(1);
|
|
35
|
+
expect(errorSpy).toHaveBeenCalled();
|
|
36
|
+
expect(String(errorSpy.mock.calls[0]?.[0] ?? '')).toContain('Error');
|
|
37
|
+
});
|
|
38
|
+
it('should handle network errors when fetching URL', async () => {
|
|
39
|
+
const originalFetch = global.fetch;
|
|
40
|
+
const mockFetch = vi.fn().mockRejectedValue(new Error('Network error'));
|
|
41
|
+
global.fetch = mockFetch;
|
|
42
|
+
generator = new Generator('test-app', '1.0.0', mockReporter, 'https://example.com/nonexistent.json', testOutputDir);
|
|
43
|
+
const exitCode = await generator.run();
|
|
44
|
+
expect(exitCode).toBe(1);
|
|
45
|
+
expect(errorSpy).toHaveBeenCalled();
|
|
46
|
+
expect(mockFetch).toHaveBeenCalled();
|
|
47
|
+
global.fetch = originalFetch;
|
|
48
|
+
});
|
|
49
|
+
it('should handle HTTP errors when fetching URL', async () => {
|
|
50
|
+
const originalFetch = global.fetch;
|
|
51
|
+
const mockFetch = vi.fn().mockResolvedValue({
|
|
52
|
+
ok: false,
|
|
53
|
+
status: 500,
|
|
54
|
+
statusText: 'Internal Server Error',
|
|
55
|
+
text: async () => 'Error response'
|
|
56
|
+
});
|
|
57
|
+
global.fetch = mockFetch;
|
|
58
|
+
generator = new Generator('test-app', '1.0.0', mockReporter, 'https://example.com/error.json', testOutputDir);
|
|
59
|
+
const exitCode = await generator.run();
|
|
60
|
+
expect(exitCode).toBe(1);
|
|
61
|
+
expect(errorSpy).toHaveBeenCalled();
|
|
62
|
+
// Error should mention the HTTP error or parsing failure
|
|
63
|
+
const errorMessage = (errorSpy.mock.calls[0]?.[0] ?? '');
|
|
64
|
+
expect(errorMessage).toMatch(/500|Error|Failed/);
|
|
65
|
+
global.fetch = originalFetch;
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
describe('Parsing Errors', () => {
|
|
69
|
+
it('should handle invalid JSON', async () => {
|
|
70
|
+
const invalidFile = join(testOutputDir, 'invalid.json');
|
|
71
|
+
writeFileSync(invalidFile, '{ invalid json }');
|
|
72
|
+
generator = new Generator('test-app', '1.0.0', mockReporter, invalidFile, testOutputDir);
|
|
73
|
+
const exitCode = await generator.run();
|
|
74
|
+
expect(exitCode).toBe(1);
|
|
75
|
+
expect(errorSpy).toHaveBeenCalled();
|
|
76
|
+
});
|
|
77
|
+
it('should handle invalid YAML', async () => {
|
|
78
|
+
const invalidFile = join(testOutputDir, 'invalid.yaml');
|
|
79
|
+
writeFileSync(invalidFile, 'invalid: yaml: [unclosed');
|
|
80
|
+
generator = new Generator('test-app', '1.0.0', mockReporter, invalidFile, testOutputDir);
|
|
81
|
+
const exitCode = await generator.run();
|
|
82
|
+
expect(exitCode).toBe(1);
|
|
83
|
+
expect(errorSpy).toHaveBeenCalled();
|
|
84
|
+
});
|
|
85
|
+
it('should handle invalid OpenAPI version', async () => {
|
|
86
|
+
const invalidFile = join(testOutputDir, 'invalid-version.yaml');
|
|
87
|
+
writeFileSync(invalidFile, `
|
|
88
|
+
openapi: 2.0.0
|
|
89
|
+
info:
|
|
90
|
+
title: Test API
|
|
91
|
+
version: 1.0.0
|
|
92
|
+
paths: {}
|
|
93
|
+
`);
|
|
94
|
+
generator = new Generator('test-app', '1.0.0', mockReporter, invalidFile, testOutputDir);
|
|
95
|
+
const exitCode = await generator.run();
|
|
96
|
+
expect(exitCode).toBe(1);
|
|
97
|
+
expect(errorSpy).toHaveBeenCalled();
|
|
98
|
+
});
|
|
99
|
+
it('should handle missing required fields', async () => {
|
|
100
|
+
const invalidFile = join(testOutputDir, 'missing-fields.yaml');
|
|
101
|
+
writeFileSync(invalidFile, `
|
|
102
|
+
openapi: 3.0.0
|
|
103
|
+
paths: {}
|
|
104
|
+
`);
|
|
105
|
+
generator = new Generator('test-app', '1.0.0', mockReporter, invalidFile, testOutputDir);
|
|
106
|
+
const exitCode = await generator.run();
|
|
107
|
+
expect(exitCode).toBe(1);
|
|
108
|
+
expect(errorSpy).toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
describe('File Writing Errors', () => {
|
|
112
|
+
it('should handle write permission errors gracefully', async () => {
|
|
113
|
+
// Create a read-only directory (on Unix systems)
|
|
114
|
+
const readOnlyDir = join(testOutputDir, 'readonly');
|
|
115
|
+
mkdirSync(readOnlyDir, { recursive: true });
|
|
116
|
+
// Note: chmod doesn't work the same way on all systems, so this test
|
|
117
|
+
// might need to be adjusted based on the environment
|
|
118
|
+
generator = new Generator('test-app', '1.0.0', mockReporter, './samples/swagger-petstore.yaml', readOnlyDir);
|
|
119
|
+
const exitCode = await generator.run();
|
|
120
|
+
// Should either succeed or fail gracefully
|
|
121
|
+
expect([0, 1]).toContain(exitCode);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
describe('Malformed OpenAPI Specs', () => {
|
|
125
|
+
it('should handle empty spec', async () => {
|
|
126
|
+
const emptyFile = join(testOutputDir, 'empty.yaml');
|
|
127
|
+
writeFileSync(emptyFile, '');
|
|
128
|
+
generator = new Generator('test-app', '1.0.0', mockReporter, emptyFile, testOutputDir);
|
|
129
|
+
const exitCode = await generator.run();
|
|
130
|
+
expect(exitCode).toBe(1);
|
|
131
|
+
expect(errorSpy).toHaveBeenCalled();
|
|
132
|
+
});
|
|
133
|
+
it('should handle spec with no paths', async () => {
|
|
134
|
+
const noPathsFile = join(testOutputDir, 'no-paths.yaml');
|
|
135
|
+
writeFileSync(noPathsFile, `
|
|
136
|
+
openapi: 3.0.0
|
|
137
|
+
info:
|
|
138
|
+
title: Test API
|
|
139
|
+
version: 1.0.0
|
|
140
|
+
paths: {}
|
|
141
|
+
`);
|
|
142
|
+
generator = new Generator('test-app', '1.0.0', mockReporter, noPathsFile, testOutputDir);
|
|
143
|
+
const exitCode = await generator.run();
|
|
144
|
+
// Should succeed even with no paths
|
|
145
|
+
expect(exitCode).toBe(0);
|
|
146
|
+
expect(logSpy).toHaveBeenCalled();
|
|
147
|
+
});
|
|
148
|
+
it('should handle spec with no components', async () => {
|
|
149
|
+
const noComponentsFile = join(testOutputDir, 'no-components.yaml');
|
|
150
|
+
writeFileSync(noComponentsFile, `
|
|
151
|
+
openapi: 3.0.0
|
|
152
|
+
info:
|
|
153
|
+
title: Test API
|
|
154
|
+
version: 1.0.0
|
|
155
|
+
paths:
|
|
156
|
+
/test:
|
|
157
|
+
get:
|
|
158
|
+
operationId: test
|
|
159
|
+
responses:
|
|
160
|
+
'200':
|
|
161
|
+
description: Success
|
|
162
|
+
`);
|
|
163
|
+
generator = new Generator('test-app', '1.0.0', mockReporter, noComponentsFile, testOutputDir);
|
|
164
|
+
const exitCode = await generator.run();
|
|
165
|
+
// Should succeed even with no components
|
|
166
|
+
expect(exitCode).toBe(0);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshots.test.d.ts","sourceRoot":"","sources":["../../../tests/integration/snapshots.test.ts"],"names":[],"mappings":""}
|