Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handling imports of generated types #148

Merged
merged 44 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
e2c10a4
test: failing test case
tvillaren May 17, 2023
1023023
test: additional test case
tvillaren May 18, 2023
7cd4009
feat: new util function to extract import identifiers
tvillaren May 16, 2023
b6a0e9d
feat: more import utils, in their own file
tvillaren May 17, 2023
b80c10a
feat: add new helper generating instanceof
tvillaren May 23, 2023
ec02a12
feat: adding non-relative import handling of classes
tvillaren May 23, 2023
c3ff379
doc: adding documentation
tvillaren May 23, 2023
834ef7b
test: adding extend test case
tvillaren May 23, 2023
a441dd1
clean: remove moved tests after rebase
tvillaren Jun 1, 2023
4d4f305
test: union case test
tvillaren Jun 1, 2023
ccbb8a5
fix: remove unused const
tvillaren Jun 1, 2023
a5f16d4
feat: allow any module import (relative & non-relative)
tvillaren Jul 17, 2023
493f894
test: add test to cover issue #140
tvillaren Jul 19, 2023
dccda71
fix: same behavior as #140 with 3rd party imports
tvillaren Jul 20, 2023
f908c27
feat: make arg optional
tvillaren Jul 25, 2023
25490f6
fix: update tests after Jest update
tvillaren Nov 8, 2023
dc69152
fix: typo in tests
tvillaren Nov 8, 2023
954bbcf
feat: enabling refs to extra files in post-generation validation
tvillaren Dec 4, 2023
e743af0
revert switch to generic fixOptional
tvillaren Dec 4, 2023
ae84ac4
fix: remove instanceof handling
tvillaren Feb 5, 2024
b36d828
clean: remove wrongly commited file
tvillaren Feb 5, 2024
40f1cbf
clean: remove unused isRelativeModuleImport
tvillaren Feb 5, 2024
ff098c9
doc: rework
tvillaren Feb 5, 2024
f99feec
fix: missing makePosixPath after rebase
tvillaren Feb 5, 2024
aa93c09
test: adding failing tests
tvillaren Sep 6, 2023
ae6c9ad
fix: typo
tvillaren Sep 6, 2023
e9da9e5
fix: complex array were not traversed
tvillaren Sep 8, 2023
de46bb0
fix: missing TS helpers in type extractor
tvillaren Sep 19, 2023
0436f2d
test: failing test case
tvillaren May 17, 2023
36ed45c
test: additional test case
tvillaren May 18, 2023
674f6c1
fix: same behavior as #140 with 3rd party imports
tvillaren Jul 20, 2023
f37cd8d
feat: add createImport function
tvillaren Jul 21, 2023
9d40b85
feat: handle input / output mapping as source for generate
tvillaren Jul 21, 2023
7a76814
feat: add zod import handling from config file
tvillaren Jul 23, 2023
8243ab1
doc: adding documentation for ZOD imports
tvillaren Jul 24, 2023
fae57e8
refacto test
tvillaren Jul 24, 2023
d2fa90e
test: adding hybrid import
tvillaren Sep 7, 2023
2b62ee7
test: adding new test
tvillaren Sep 8, 2023
7edeabc
fix: using relative to compare paths
tvillaren Dec 8, 2023
fa5bce3
fix: multiple fixes after rebase
tvillaren Feb 5, 2024
092aaea
fix: remove useless imports from output
tvillaren Feb 6, 2024
f638a8f
update documentation
tvillaren Feb 6, 2024
5247170
Merge branch 'main' into feat-handle-zod-imports
tvillaren Feb 6, 2024
0ccf5d8
fix: build issues after rebase
tvillaren Feb 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 54 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,9 @@ To resume, you can use all the primitive types and some the following typescript
- `Array<>`
- `Promise<>`

This utils is design to work with one file only, and will reference types from the same file:
## Type references

This utility is designed to work with one file at a time (it will not split one file into several), and will handle references to types from the same file:

```ts
// source.ts
Expand All @@ -388,7 +390,9 @@ export const heroSchema = z.object({
});
```

It can also reference imported types from any modules but will use an `any` validation as placeholder, as we cannot now
### Non-Zod imports

`ts-to-zod` can reference imported types from other modules but will use an `any` validation as placeholder, as we cannot now
their schema.

```ts
Expand All @@ -415,6 +419,54 @@ export const heroSchema = z.object({
});
```

### Zod Imports

If an imported type is referenced in the `ts-to-zod.config.js` config (as input), this utility will automatically replace the import with the given output from the file, resolving the relative paths between both.

```ts

//ts-to-zod.config.js
/**
* ts-to-zod configuration.
*
* @type {import("./src/config").TsToZodConfig}
*/
module.exports = [
{
name: "villain",
input: "src/villain.ts",
output: "src/generated/villain.zod.ts",
getSchemaName: (id) => `z${id}`
},
{
name: "hero",
input: "src/example/heros.ts",
output: "src/example/heros.zod.ts",
},
];

// heros.ts (input)
import { Person } from "@3rdparty/person";
import { Villain } from "../villain";

export interface Hero {
name: string;
realPerson: Person;
nemesis: Villain;
}

// heros.zod.ts (output)
import { zVillain } from "../generated/villain.zod";

const personSchema = z.any();

export const heroSchema = z.object({
name: z.string(),
realPerson: personSchema,
nemesis: zVillain;
});
```

## Programmatic API

You need more than one file? Want even more power? No problem, just use the tool as a library.
Expand Down
4 changes: 3 additions & 1 deletion example/heros.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { Person } from "./person";

export enum EnemyPower {
Flight = "flight",
Strength = "strength",
Expand All @@ -11,7 +13,7 @@ export namespace Skills {
};
}

export interface Enemy {
export interface Enemy extends Person {
name: string;
powers: EnemyPower[];
inPrison: boolean;
Expand Down
4 changes: 3 additions & 1 deletion example/heros.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
import { z } from "zod";
import { EnemyPower, Villain, EvilPlan, EvilPlanDetails } from "./heros";

import { personSchema } from "./person.zod";

export const enemyPowerSchema = z.nativeEnum(EnemyPower);

export const skillsSpeedEnemySchema = z.object({
power: z.literal(EnemyPower.Speed),
});

export const enemySchema = z.object({
export const enemySchema = personSchema.extend({
name: z.string(),
powers: z.array(enemyPowerSchema),
inPrison: z.boolean(),
Expand Down
4 changes: 4 additions & 0 deletions example/person.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Used as an import in hero.ts
export interface Person {
realName: string;
}
6 changes: 6 additions & 0 deletions example/person.zod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Generated by ts-to-zod
import { z } from "zod";

export const personSchema = z.object({
realName: z.string(),
});
44 changes: 39 additions & 5 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { join, parse, relative } from "path";
import prettier from "prettier";
import slash from "slash";
import ts from "typescript";
import { Config, TsToZodConfig } from "./config";
import { Config, TsToZodConfig, InputOutputMapping } from "./config";
import {
getSchemaNameSchema,
nameFilterSchema,
Expand Down Expand Up @@ -133,6 +133,8 @@ class TsToZod extends Command {

const fileConfig = await this.loadFileConfig(config, flags);

const ioMappings = getInputOutputMappings(config);

if (Array.isArray(fileConfig)) {
if (args.input || args.output) {
this.error(`INPUT and OUTPUT arguments are not compatible with --all`);
Expand All @@ -141,7 +143,7 @@ class TsToZod extends Command {
await Promise.all(
fileConfig.map(async (config) => {
this.log(`Generating "${config.name}"`);
const result = await this.generate(args, config, flags);
const result = await this.generate(args, config, flags, ioMappings);
if (result.success) {
this.log(` 🎉 Zod schemas generated!`);
} else {
Expand All @@ -156,7 +158,7 @@ class TsToZod extends Command {
this.error(error);
}
} else {
const result = await this.generate(args, fileConfig, flags);
const result = await this.generate(args, fileConfig, flags, ioMappings);
if (result.success) {
this.log(`🎉 Zod schemas generated!`);
} else {
Expand All @@ -177,7 +179,7 @@ class TsToZod extends Command {
? fileConfig.find((i) => i.input === slash(path))
: fileConfig;

const result = await this.generate(args, config, flags);
const result = await this.generate(args, config, flags, ioMappings);
if (result.success) {
this.log(`🎉 Zod schemas generated!`);
} else {
Expand All @@ -193,11 +195,13 @@ class TsToZod extends Command {
* @param args
* @param fileConfig
* @param Flags
* @param inputOutputMappings
*/
async generate(
args: { input?: string; output?: string },
fileConfig: Config | undefined,
Flags: Interfaces.InferredFlags<typeof TsToZod.flags>
Flags: Interfaces.InferredFlags<typeof TsToZod.flags>,
inputOutputMappings: InputOutputMapping[]
): Promise<{ success: true } | { success: false; error: string }> {
const input = args.input || fileConfig?.input;
const output = args.output || fileConfig?.output;
Expand All @@ -214,6 +218,17 @@ See more help with --help`,
const inputPath = join(process.cwd(), input);
const outputPath = join(process.cwd(), output || input);

const relativeIOMappings = inputOutputMappings.map((io) => {
const relativeInput = getImportPath(inputPath, io.input);
const relativeOutput = getImportPath(outputPath, io.output);

return {
input: relativeInput,
output: relativeOutput,
getSchemaName: io.getSchemaName,
};
});

// Check args/flags file extensions
const extErrors: { path: string; expectedExtensions: string[] }[] = [];
if (!hasExtensions(input, typescriptExtensions)) {
Expand Down Expand Up @@ -250,6 +265,7 @@ See more help with --help`,

const generateOptions: GenerateProps = {
sourceText,
inputOutputMappings: relativeIOMappings,
...fileConfig,
};
if (typeof Flags.keepComments === "boolean") {
Expand Down Expand Up @@ -443,4 +459,22 @@ function hasExtensions(path: string, extensions: string[]) {
return extensions.includes(ext);
}

function getInputOutputMappings(
config: TsToZodConfig | undefined
): InputOutputMapping[] {
if (!config) {
return [];
}

if (Array.isArray(config)) {
return config.map((c) => {
const { input, output, getSchemaName } = c;
return { input, output, getSchemaName };
});
}

const { input, output, getSchemaName } = config as Config;
return [{ input, output, getSchemaName }];
}

export = TsToZod;
5 changes: 5 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,9 @@ export type Configs = Array<
}
>;

export type InputOutputMapping = Pick<
Config,
"input" | "output" | "getSchemaName"
>;

export type TsToZodConfig = Config | Configs;
6 changes: 6 additions & 0 deletions src/config.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,10 @@ export const configsSchema = z.array(
)
);

export const inputOutputMappingSchema = configSchema.pick({
input: true,
output: true,
getSchemaName: true,
});

export const tsToZodConfigSchema = z.union([configSchema, configsSchema]);
Loading