zod-codegen 1.0.0 → 1.0.2
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 +14 -14
- package/.github/workflows/release.yml +3 -3
- package/.nvmrc +1 -1
- package/CHANGELOG.md +29 -0
- package/dist/src/assets/manifest.json +2 -2
- package/dist/src/generator.js +3 -3
- package/dist/src/http/fetch-client.js +0 -1
- package/dist/src/services/code-generator.service.js +549 -58
- package/dist/src/services/file-reader.service.js +27 -14
- package/dist/src/services/import-builder.service.js +4 -11
- package/dist/src/services/type-builder.service.js +16 -9
- package/dist/src/types/openapi.js +6 -6
- package/dist/tests/unit/generator.test.js +1 -0
- package/eslint.config.mjs +18 -0
- package/package.json +25 -25
- package/src/assets/manifest.json +2 -2
- package/src/generator.ts +3 -3
- package/src/http/fetch-client.ts +4 -4
- package/src/services/code-generator.service.ts +1212 -88
- package/src/services/file-reader.service.ts +26 -13
- package/src/services/import-builder.service.ts +8 -11
- package/src/services/type-builder.service.ts +19 -12
- package/src/types/openapi.ts +6 -6
- package/tsconfig.json +4 -4
- package/scripts/update-manifest.js +0 -31
|
@@ -5,24 +5,37 @@ import type {OpenApiSpecType} from '../types/openapi.js';
|
|
|
5
5
|
import {OpenApiSpec} from '../types/openapi.js';
|
|
6
6
|
|
|
7
7
|
export class SyncFileReaderService implements OpenApiFileReader {
|
|
8
|
-
readFile(path: string): string {
|
|
9
|
-
|
|
8
|
+
async readFile(path: string): Promise<string> {
|
|
9
|
+
// Check if path is a URL
|
|
10
|
+
try {
|
|
11
|
+
const url = new URL(path);
|
|
12
|
+
// If it's a valid URL, fetch it
|
|
13
|
+
const response = await fetch(url.toString());
|
|
14
|
+
if (!response.ok) {
|
|
15
|
+
throw new Error(`Failed to fetch ${path}: ${String(response.status)} ${response.statusText}`);
|
|
16
|
+
}
|
|
17
|
+
return await response.text();
|
|
18
|
+
} catch {
|
|
19
|
+
// If URL parsing fails, treat it as a local file path
|
|
20
|
+
return readFileSync(path, 'utf8');
|
|
21
|
+
}
|
|
10
22
|
}
|
|
11
23
|
}
|
|
12
24
|
|
|
13
25
|
export class OpenApiFileParserService implements OpenApiFileParser<OpenApiSpecType> {
|
|
14
26
|
parse(input: unknown): OpenApiSpecType {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
28
|
+
const parsedInput =
|
|
29
|
+
typeof input === 'string'
|
|
30
|
+
? (() => {
|
|
31
|
+
try {
|
|
32
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
33
|
+
return JSON.parse(input);
|
|
34
|
+
} catch {
|
|
35
|
+
return load(input);
|
|
36
|
+
}
|
|
37
|
+
})()
|
|
38
|
+
: input;
|
|
26
39
|
|
|
27
40
|
return OpenApiSpec.parse(parsedInput);
|
|
28
41
|
}
|
|
@@ -16,10 +16,7 @@ export class TypeScriptImportBuilderService implements ImportBuilder {
|
|
|
16
16
|
buildImports(): ts.ImportDeclaration[] {
|
|
17
17
|
return [
|
|
18
18
|
this.createImport('zod', {
|
|
19
|
-
|
|
20
|
-
}),
|
|
21
|
-
this.createImport('path-to-regexp', {
|
|
22
|
-
namedImports: {compile: false},
|
|
19
|
+
namedImports: {z: false},
|
|
23
20
|
}),
|
|
24
21
|
];
|
|
25
22
|
}
|
|
@@ -46,16 +43,16 @@ export class TypeScriptImportBuilderService implements ImportBuilder {
|
|
|
46
43
|
const hasAnyImports = hasDefaultImport || namedImports;
|
|
47
44
|
|
|
48
45
|
// For side effects imports, we can pass undefined as the import clause
|
|
49
|
-
// For imports with bindings, we need to create the clause
|
|
46
|
+
// For imports with bindings, we need to create the clause using the factory
|
|
50
47
|
return ts.factory.createImportDeclaration(
|
|
51
48
|
undefined,
|
|
52
49
|
hasAnyImports
|
|
53
|
-
?
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
? // eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
51
|
+
ts.factory.createImportClause(
|
|
52
|
+
false,
|
|
53
|
+
hasDefaultImport && defaultImport ? ts.factory.createIdentifier(defaultImport) : undefined,
|
|
54
|
+
namedImports ?? undefined,
|
|
55
|
+
)
|
|
59
56
|
: undefined,
|
|
60
57
|
ts.factory.createStringLiteral(target, true),
|
|
61
58
|
undefined,
|
|
@@ -11,8 +11,23 @@ export class TypeScriptTypeBuilderService implements TypeBuilder {
|
|
|
11
11
|
case 'boolean':
|
|
12
12
|
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
13
13
|
case 'unknown':
|
|
14
|
-
default:
|
|
15
14
|
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
15
|
+
default:
|
|
16
|
+
// Handle custom types (like Pet, Order, User, etc.) and array types
|
|
17
|
+
if (type.endsWith('[]')) {
|
|
18
|
+
const itemType = type.slice(0, -2);
|
|
19
|
+
return ts.factory.createArrayTypeNode(this.buildType(itemType));
|
|
20
|
+
}
|
|
21
|
+
// Handle Record types
|
|
22
|
+
if (type.startsWith('Record<')) {
|
|
23
|
+
// For now, return unknown for complex Record types
|
|
24
|
+
return ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
25
|
+
}
|
|
26
|
+
// Custom type name - create a type reference
|
|
27
|
+
return ts.factory.createTypeReferenceNode(
|
|
28
|
+
ts.factory.createIdentifier(this.sanitizeIdentifier(type)),
|
|
29
|
+
undefined,
|
|
30
|
+
);
|
|
16
31
|
}
|
|
17
32
|
}
|
|
18
33
|
|
|
@@ -54,17 +69,9 @@ export class TypeScriptTypeBuilderService implements TypeBuilder {
|
|
|
54
69
|
}
|
|
55
70
|
|
|
56
71
|
sanitizeIdentifier(name: string): string {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
sanitized = '_' + sanitized;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
if (sanitized.length === 0) {
|
|
64
|
-
sanitized = '_';
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return sanitized;
|
|
72
|
+
const sanitized = name.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
73
|
+
const withPrefix = /^[0-9]/.test(sanitized) ? '_' + sanitized : sanitized;
|
|
74
|
+
return withPrefix.length === 0 ? '_' : withPrefix;
|
|
68
75
|
}
|
|
69
76
|
|
|
70
77
|
toCamelCase(word: string): string {
|
package/src/types/openapi.ts
CHANGED
|
@@ -61,7 +61,7 @@ const ServerVariable = z.object({
|
|
|
61
61
|
});
|
|
62
62
|
|
|
63
63
|
const Server = z.object({
|
|
64
|
-
url: z.
|
|
64
|
+
url: z.url(),
|
|
65
65
|
description: z.string().optional(),
|
|
66
66
|
variables: z.record(z.string(), ServerVariable).optional(),
|
|
67
67
|
});
|
|
@@ -140,18 +140,18 @@ const Info = z.object({
|
|
|
140
140
|
title: z.string().min(1),
|
|
141
141
|
version: z.string().min(1),
|
|
142
142
|
description: z.string().optional(),
|
|
143
|
-
termsOfService: z.
|
|
143
|
+
termsOfService: z.url().optional(),
|
|
144
144
|
contact: z
|
|
145
145
|
.object({
|
|
146
146
|
name: z.string().optional(),
|
|
147
|
-
email: z.
|
|
148
|
-
url: z.
|
|
147
|
+
email: z.email().optional(),
|
|
148
|
+
url: z.url().optional(),
|
|
149
149
|
})
|
|
150
150
|
.optional(),
|
|
151
151
|
license: z
|
|
152
152
|
.object({
|
|
153
153
|
name: z.string().min(1),
|
|
154
|
-
url: z.
|
|
154
|
+
url: z.url().optional(),
|
|
155
155
|
})
|
|
156
156
|
.optional(),
|
|
157
157
|
});
|
|
@@ -166,7 +166,7 @@ const Tag = z.object({
|
|
|
166
166
|
|
|
167
167
|
const ExternalDocumentation = z.object({
|
|
168
168
|
description: z.string().optional(),
|
|
169
|
-
url: z.
|
|
169
|
+
url: z.url(),
|
|
170
170
|
});
|
|
171
171
|
|
|
172
172
|
const Components = z.object({
|
package/tsconfig.json
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
"checkJs": false,
|
|
7
7
|
"downlevelIteration": true,
|
|
8
8
|
"esModuleInterop": true,
|
|
9
|
-
"exactOptionalPropertyTypes":
|
|
9
|
+
"exactOptionalPropertyTypes": true,
|
|
10
10
|
"forceConsistentCasingInFileNames": true,
|
|
11
11
|
"incremental": true,
|
|
12
12
|
"isolatedModules": true,
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"noImplicitOverride": true,
|
|
20
20
|
"noImplicitReturns": true,
|
|
21
21
|
"noImplicitThis": true,
|
|
22
|
-
"noPropertyAccessFromIndexSignature":
|
|
22
|
+
"noPropertyAccessFromIndexSignature": true,
|
|
23
23
|
"noUncheckedIndexedAccess": true,
|
|
24
24
|
"noUnusedLocals": true,
|
|
25
25
|
"noUnusedParameters": true,
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"target": "ES2022",
|
|
38
38
|
"typeRoots": ["./node_modules/@types"],
|
|
39
39
|
"useUnknownInCatchVariables": true,
|
|
40
|
-
"verbatimModuleSyntax":
|
|
40
|
+
"verbatimModuleSyntax": true
|
|
41
41
|
},
|
|
42
42
|
"exclude": ["node_modules", "dist", "build"],
|
|
43
|
-
"include": ["src/**/*.ts", "
|
|
43
|
+
"include": ["src/**/*.ts", "scripts/**/*.ts", "tests/**/*.ts", "vitest.config.ts"]
|
|
44
44
|
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import {readFileSync, writeFileSync} from 'fs';
|
|
2
|
-
import {resolve} from 'path';
|
|
3
|
-
import {z} from 'zod';
|
|
4
|
-
/**
|
|
5
|
-
* Type guard for the package.json object
|
|
6
|
-
*
|
|
7
|
-
* @param input Unknown input
|
|
8
|
-
* @returns true if the input is an event object
|
|
9
|
-
*/
|
|
10
|
-
export function isPackageJson(input) {
|
|
11
|
-
const event = z
|
|
12
|
-
.object({
|
|
13
|
-
name: z.string(),
|
|
14
|
-
version: z.string(),
|
|
15
|
-
description: z.string(),
|
|
16
|
-
})
|
|
17
|
-
.strict()
|
|
18
|
-
.catchall(z.any())
|
|
19
|
-
.required();
|
|
20
|
-
const {success} = event.safeParse(input);
|
|
21
|
-
return success;
|
|
22
|
-
}
|
|
23
|
-
const sourcePath = resolve(__dirname, '..', 'package.json');
|
|
24
|
-
const data = JSON.parse(readFileSync(sourcePath, 'utf8'));
|
|
25
|
-
if (!isPackageJson(data)) {
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
const {name, version, description} = data;
|
|
29
|
-
const targetPath = resolve(__dirname, '..', 'src', 'assets', 'manifest.json');
|
|
30
|
-
writeFileSync(targetPath, JSON.stringify({name, version, description}, null, 2));
|
|
31
|
-
process.exit(0);
|