diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 2fe8300..4f1880a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -3,10 +3,10 @@ name: Tests on: push: branches: - - master + - main pull_request: - types: [ opened, synchronize, reopened ] - + types: [opened, synchronize, reopened] + jobs: test: runs-on: ubuntu-latest @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 with: - node-version: 12.16.1 + node-version: 16.13.1 - run: yarn - run: chmod +x ./bin/run - run: yarn test:ci diff --git a/example/heros.types.ts b/example/heros.types.ts new file mode 100644 index 0000000..0b39fb5 --- /dev/null +++ b/example/heros.types.ts @@ -0,0 +1,36 @@ +// Generated by ts-to-zod +import { z } from "zod"; + +import * as generated from "./heros.zod"; + +export type EnemyPower = z.infer; + +export type SkillsSpeedEnemy = z.infer; + +export type Enemy = z.infer; + +export type Superman = z.infer; + +export type Villain = z.infer; + +export type Story = z.infer; + +export type KrytonResponse = z.infer; + +export type KillSuperman = z.infer; + +export type WithDefaults = z.infer; + +export type Exported = z.infer; + +export type GetSupermanSkill = z.infer; + +export type HeroContact = z.infer; + +export type SupermanEnemy = z.infer; + +export type SupermanName = z.infer; + +export type SupermanInvinciblePower = z.infer< + typeof generated.supermanInvinciblePowerSchema +>; diff --git a/src/cli.ts b/src/cli.ts index 6870388..a915462 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -83,6 +83,9 @@ class TsToZod extends Command { default: false, description: "Skip the validation step (not recommended)", }), + inferredTypes: flags.string({ + description: "Path of z.infer<> types file", + }), watch: flags.boolean({ char: "w", default: false, @@ -239,12 +242,16 @@ See more help with --help`, if (typeof flags.skipParseJSDoc === "boolean") { generateOptions.skipParseJSDoc = flags.skipParseJSDoc; } + if (typeof flags.inferredTypes === "string") { + generateOptions.inferredTypes = flags.inferredTypes; + } const { errors, transformedSourceText, getZodSchemasFile, getIntegrationTestFile, + getInferredTypes, hasCircularDependencies, } = generate(generateOptions); @@ -295,6 +302,27 @@ See more help with --help`, const prettierConfig = await prettier.resolveConfig(process.cwd()); + if (generateOptions.inferredTypes) { + const zodInferredTypesFile = getInferredTypes( + getImportPath(generateOptions.inferredTypes, outputPath) + ); + await outputFile( + generateOptions.inferredTypes, + prettier.format( + hasExtensions(generateOptions.inferredTypes, javascriptExtensions) + ? ts.transpileModule(zodInferredTypesFile, { + compilerOptions: { + target: ts.ScriptTarget.Latest, + module: ts.ModuleKind.ESNext, + newLine: ts.NewLineKind.LineFeed, + }, + }).outputText + : zodInferredTypesFile, + { parser: "babel-ts", ...prettierConfig } + ) + ); + } + if (output && hasExtensions(output, javascriptExtensions)) { await outputFile( outputPath, diff --git a/src/config.ts b/src/config.ts index 9f34892..6c78770 100644 --- a/src/config.ts +++ b/src/config.ts @@ -61,6 +61,11 @@ export type Config = { * @default false */ skipParseJSDoc?: boolean; + + /** + * Path of z.infer<> types file. + */ + inferredTypes?: string; }; export type Configs = Array< diff --git a/src/config.zod.ts b/src/config.zod.ts index ec93181..6910164 100644 --- a/src/config.zod.ts +++ b/src/config.zod.ts @@ -30,6 +30,7 @@ export const configSchema = z.object({ getSchemaName: getSchemaNameSchema.optional(), keepComments: z.boolean().optional().default(false), skipParseJSDoc: z.boolean().optional().default(false), + inferredTypes: z.string().optional(), }); export const configsSchema = z.array( diff --git a/src/core/generate.ts b/src/core/generate.ts index 4d62136..4ac54ff 100644 --- a/src/core/generate.ts +++ b/src/core/generate.ts @@ -47,6 +47,11 @@ export interface GenerateProps { * @default false */ skipParseJSDoc?: boolean; + + /** + * Path of z.infer<> types file. + */ + inferredTypes?: string; } /** @@ -283,6 +288,27 @@ ${Array.from(statements.values()) ${testCases.map(print).join("\n")} `; + const getInferredTypes = ( + zodSchemasImportPath: string + ) => `// Generated by ts-to-zod +import { z } from "zod"; + +import * as generated from "${zodSchemasImportPath}"; + +${Array.from(statements.values()) + .filter(isExported) + .map((statement) => { + const zodInferredSchema = generateZodInferredType({ + aliasName: statement.typeName, + zodConstName: `generated.${getSchemaName(statement.typeName)}`, + zodImportValue: "z", + }); + + return print(zodInferredSchema); + }) + .join("\n\n")} +`; + return { /** * Source text with pre-process applied. @@ -304,6 +330,13 @@ ${testCases.map(print).join("\n")} */ getIntegrationTestFile, + /** + * Get the content of the zod inferred types files. + * + * @param zodSchemasImportPath Relative path of the zod schemas file + */ + getInferredTypes, + /** * List of generation errors. */ diff --git a/ts-to-zod.config.js b/ts-to-zod.config.js index 00380ed..d9b9759 100644 --- a/ts-to-zod.config.js +++ b/ts-to-zod.config.js @@ -8,6 +8,7 @@ module.exports = [ name: "example", input: "example/heros.ts", output: "example/heros.zod.ts", + inferredTypes: "example/heros.types.ts", }, { name: "config", input: "src/config.ts", output: "src/config.zod.ts" }, ];