zod-codegen 1.0.1 → 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 +5 -5
- package/.github/workflows/release.yml +1 -1
- package/.nvmrc +1 -1
- package/CHANGELOG.md +7 -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/tests/unit/generator.test.js +1 -0
- package/eslint.config.mjs +18 -0
- package/package.json +22 -22
- 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/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/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);
|