-
Notifications
You must be signed in to change notification settings - Fork 70
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
Easy generation of multiple types inside same folder #151
Comments
It's a bit harder than it looks since you can have name conflicts in the files (like two Lines 249 to 256 in 8d3b297
(you can import |
Mmm... thinking loud, in my case we use the following naming convention: Model (entities) files, follow the convention: name.model.ts Zod schema files, follow the convention: name.zod.ts So in theory I could just implement some script that traverses all subfolders under Thanks for you great tool |
I actually did the same thing that @brauliodiez suggests, so I thought I'd add a comment here. I wrote a script for iterating through sub-directories, searching for typescript files and import { glob } from 'glob'
import { exec } from 'child_process'
import * as path from 'path'
const GLOB_PATTERN = 'dtos/*.ts' // Where to search for typescript definitions?
const GLOB_IGNORE_PATTERNS = ['node_modules/**', '**/*.zod.ts'] // What should be ignored? (*.zod.ts to prevent recursion)
const VERBOSE = false // Should native ts-to-zod logs be printed?
const FILES = await glob(GLOB_PATTERN, { ignore: GLOB_IGNORE_PATTERNS })
FILES.forEach((filePath) => {
const { base, dir, name } = path.parse(filePath)
const FILE_PATH_NORMAL = path.join(dir, base)
const FILE_NAME = name
console.log(`Generating Zod-Files for ${FILE_NAME}`)
exec(
`npx ts-to-zod ${FILE_PATH_NORMAL} ${FILE_PATH_NORMAL.replace('.ts', '.zod.ts')} --config=${FILE_NAME}`,
(error, stdout, stderr) => {
if (error && VERBOSE) {
console.error(error.message)
}
if (stdout && VERBOSE) {
console.log(stdout)
}
if (stderr && VERBOSE) {
console.error(stderr)
}
console.log(` ✔ Generated Zod-File for ${FILE_NAME}`)
}
)
}) This obviously also matches normal And you also need to map all your affected files in the correct import-dependency order inside of the /**
* ts-to-zod configuration.
*
* @type {import("ts-to-zod").TsToZodConfig}
*/
module.exports = [
{ name: 'Vector2DTO', input: './dtos/Vector2DTO.ts', output: './dtos/Vector2DTO.zod.ts' },
{ name: 'EntityDTO', input: './dtos/EntityDTO.ts', output: './dtos/EntityDTO.zod.ts' },
{ name: 'ProjectileDTO', input: './dtos/ProjectileDTO.ts', output: './dtos/ProjectileDTO.zod.ts' },
{ name: 'PlayerDTO', input: './dtos/PlayerDTO.ts', output: './dtos/PlayerDTO.zod.ts' },
] I'm pretty sure you could even, under the right circumstances and project structure, create the configuration on the fly by creating a preprocess step in which you try to figure out the order of the files based on whether or not they have imported another scanned file or something like that. Probably way easier said than done tho. :) Anyway, really cool project! |
Thanks for sharing @greenpixels ! Yes, I believe the complexity is in ordering the files based on their imports (and I was actually thinking about generating a config file on-the-fly based on the right ordering). |
@tvillaren any progress/updates on the ordering? |
@pkyeck Unfortunately not on my side, haven't had the personal time to put into it this month. |
ok, came up with a solution for my very special case where I know where to look for types/imports: Quick & dirty updated version of the aforementioned script import { execSync } from 'node:child_process'
import * as fs from 'node:fs'
import * as path from 'node:path'
import { glob } from 'glob'
const topologicalSort = (dependencies: Record<string, Array<string>>) => {
const visited = new Set()
const result: Array<string> = []
// Function to perform DFS
const dfs = (item) => {
if (visited.has(item)) {
return
}
visited.add(item)
for (const dep of dependencies[item]) {
dfs(dep)
}
result.push(item)
}
// Perform DFS for each item in the graph
for (const item in dependencies) {
if (!visited.has(item)) {
dfs(item)
}
}
// get the correct topological order
return result
}
const main = async () => {
const GLOB_PATTERN = '**/*.types.ts' // Where to search for typescript definitions?
const GLOB_IGNORE_PATTERNS = ['node_modules/**', '**/*.zod.ts'] // What should be ignored? (*.zod.ts to prevent recursion)
// const VERBOSE = true // Should native ts-to-zod logs be printed?
const FILES = await glob(GLOB_PATTERN, { ignore: GLOB_IGNORE_PATTERNS })
const dependencies: Record<string, Array<string>> = {}
const configEntries: Array<{ name: string; input: string; output: string }> = []
FILES.forEach((filePath) => {
const { base, dir, name } = path.parse(filePath)
const FILE_PATH_NORMAL = path.join(dir, base)
const FOLDER_PATH = FILE_PATH_NORMAL.split('/').slice(0, -1).join('/')
const FILE_NAME = name
console.log(`Generating Zod-Files for`, { FILE_PATH_NORMAL, FILE_NAME, name })
const data = fs.readFileSync(FILE_PATH_NORMAL)
const content = data.toString()
const regex = /import .+ from '(.+\.types)'/g
const imports = [...content.matchAll(regex)]
dependencies[FILE_PATH_NORMAL] = imports.map(([, importPath]) => `${path.join(FOLDER_PATH, importPath)}.ts`)
})
const orderedFiles = topologicalSort(dependencies)
orderedFiles.forEach((f) => {
configEntries.push({ name: f, input: f, output: f.replace('.types.ts', '.zod.ts') })
})
const contentOfConfig = `module.exports = [
${configEntries.map((i) => JSON.stringify(i)).join(',\n ')}
]`
fs.writeFileSync('ts-to-zod.config.js', contentOfConfig)
// build all configs at once
// const cmd = `npx ts-to-zod --all`
// console.log(`Running: ${cmd}`)
// try {
// const buf = execSync(cmd)
// console.log(` ✔ Generated Zod-Files`, buf.toString())
// } catch (error) {
// console.error(error)
// }
// build zod files one by one
orderedFiles.forEach((f) => {
const cmd = `npx ts-to-zod --config=${f}`
console.log(`Running: ${cmd}`)
try {
const buf = execSync(cmd)
console.log(` ✔ Generated Zod-File for ${f}`, buf.toString())
} catch (error) {
console.error(error)
}
})
console.log(`Found ${FILES.length} files`)
}
main()
.then(() => {
process.exit(0)
})
.catch((error) => {
console.error(error)
process.exit(1)
}) Don't really know if I should run these one by one or just with the |
Thanks for sharing @pkyeck 🙏 The |
Good to know. Then I should stick to the running them one by one with |
This should be a good starting point for sorting files (we're using it in our project): const dependencyTree = require("dependency-tree");
const glob = require("glob");
const path = require("node:path");
function sortFilesByGenerationOrder(filesToSort, srcFolder, tsConfig) {
// JS sets are ordered, so we can rely on it for iteration and deduplication
const dependencies = new Set();
const visited = {};
for (const file of filesToSort) {
const newEntries = dependencyTree.toList({
filename: file,
directory: srcFolder,
tsConfig,
visited,
});
newEntries.forEach((it) => dependencies.add(it));
}
return [...dependencies];
}
const srcFolder = path.join(__dirname, "src");
const files = glob.globSync(path.join(srcFolder, "*.ts"), { absolute: true });
const ordered = sortFilesByGenerationOrder(files, srcFolder, "./tsconfig.json");
/**
* ts-to-zod configuration.
*
* @type {import("ts-to-zod").TsToZodConfig}
*/
const config = ordered
.map((f) => f.replace(srcFolder + path.sep, "").replace(/.ts$/, ""))
.map((file) => ({
name: file,
input: `src/${file}.ts`,
output: `src/schemas/${file}.zod.ts`,
}));
module.exports = config;
/**
* This is a hack until https://github.com/fabien0102/ts-to-zod/issues/151 is implemented
* We're running this file to generate the config entries in order. When the issue has been
* closed, all code after this comment can be removed and the built-in mechanism can be used.
*/
const { argv } = require("node:process");
const { spawnSync } = require("node:child_process");
const isRunningAsScript = __filename === argv[1];
if (isRunningAsScript) {
for (const configEntry of config) {
console.log(`Generating ${configEntry.name}`);
spawnSync("npx", ["ts-to-zod", "-c", configEntry.name], { stdio: "inherit" });
}
} Generate using PS: When using |
Feature description
Imagine types are stored in files following this folder structure:
Input
yarn ts-to-zod src/parsers/* src/nowIcanValidateEverything.ts
Output
nowIcanValidateEverything.ts
has all generated zod types found in any of the files with the path src/parsers/The text was updated successfully, but these errors were encountered: