Skip to content

Commit

Permalink
feat: Generate inferred types (#85)
Browse files Browse the repository at this point in the history
* Fix github flow

* Add `inferredTypes` option

* Add inferredTypes in the example

* Fix doc
  • Loading branch information
fabien0102 committed Mar 13, 2023
1 parent b0eb555 commit 250f64d
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 4 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/tests.yaml
Expand Up @@ -3,18 +3,18 @@ name: Tests
on:
push:
branches:
- master
- main
pull_request:
types: [ opened, synchronize, reopened ]
types: [opened, synchronize, reopened]

jobs:
test:
runs-on: ubuntu-latest
steps:
- 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
Expand Down
36 changes: 36 additions & 0 deletions 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<typeof generated.enemyPowerSchema>;

export type SkillsSpeedEnemy = z.infer<typeof generated.skillsSpeedEnemySchema>;

export type Enemy = z.infer<typeof generated.enemySchema>;

export type Superman = z.infer<typeof generated.supermanSchema>;

export type Villain = z.infer<typeof generated.villainSchema>;

export type Story = z.infer<typeof generated.storySchema>;

export type KrytonResponse = z.infer<typeof generated.krytonResponseSchema>;

export type KillSuperman = z.infer<typeof generated.killSupermanSchema>;

export type WithDefaults = z.infer<typeof generated.withDefaultsSchema>;

export type Exported = z.infer<typeof generated.exportedSchema>;

export type GetSupermanSkill = z.infer<typeof generated.getSupermanSkillSchema>;

export type HeroContact = z.infer<typeof generated.heroContactSchema>;

export type SupermanEnemy = z.infer<typeof generated.supermanEnemySchema>;

export type SupermanName = z.infer<typeof generated.supermanNameSchema>;

export type SupermanInvinciblePower = z.infer<
typeof generated.supermanInvinciblePowerSchema
>;
28 changes: 28 additions & 0 deletions src/cli.ts
Expand Up @@ -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,
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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,
Expand Down
5 changes: 5 additions & 0 deletions src/config.ts
Expand Up @@ -61,6 +61,11 @@ export type Config = {
* @default false
*/
skipParseJSDoc?: boolean;

/**
* Path of z.infer<> types file.
*/
inferredTypes?: string;
};

export type Configs = Array<
Expand Down
1 change: 1 addition & 0 deletions src/config.zod.ts
Expand Up @@ -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(
Expand Down
33 changes: 33 additions & 0 deletions src/core/generate.ts
Expand Up @@ -47,6 +47,11 @@ export interface GenerateProps {
* @default false
*/
skipParseJSDoc?: boolean;

/**
* Path of z.infer<> types file.
*/
inferredTypes?: string;
}

/**
Expand Down Expand Up @@ -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.
Expand All @@ -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.
*/
Expand Down
1 change: 1 addition & 0 deletions ts-to-zod.config.js
Expand Up @@ -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" },
];

0 comments on commit 250f64d

Please sign in to comment.