chore: bump to 0.2.2 + make Zod schemas version-agnostic (v3.25+/v4) (#178)
* feat: make generated Zod schemas version-agnostic (v3.25+/v4) - Change generated schema imports from `zod/v4` to standard `zod` - Replace v4-only `z.looseObject()` with `z.object().passthrough()` - Works with both Zod v3.25+ and v4, matching MCP TS SDK approach - Bump version to 0.2.2 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * chore: regenerate package-lock.json in Docker 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
bbea41f27b
commit
82ab3c894c
1 changed files with 2041 additions and 1246 deletions
|
|
@ -8,12 +8,10 @@
|
|||
*
|
||||
* ts-to-zod is a powerful tool but has limitations that require post-processing:
|
||||
*
|
||||
* ### 1. Zod Import Path (`"zod"` → `"zod/v4"`)
|
||||
* ### 1. Zod Import Path (keep standard `"zod"` for version agnosticism)
|
||||
*
|
||||
* **Problem**: ts-to-zod generates `import { z } from "zod"` but this project
|
||||
* uses the Zod v4 subpath import `"zod/v4"` for explicit version targeting.
|
||||
*
|
||||
* **Solution**: Replace the import path in the generated output.
|
||||
* ts-to-zod generates `import { z } from "zod"` which works with both
|
||||
* Zod v3.25+ and v4. We keep this standard import to support both versions.
|
||||
*
|
||||
* ### 2. External Type References (`z.any()` → actual schemas)
|
||||
*
|
||||
|
|
@ -24,13 +22,14 @@
|
|||
*
|
||||
* **Solution**: Import the schemas from MCP SDK and remove the z.any() placeholders.
|
||||
*
|
||||
* ### 3. Index Signatures (`z.record().and()` → `z.looseObject()`)
|
||||
* ### 3. Index Signatures (`z.record().and()` → `z.object().passthrough()`)
|
||||
*
|
||||
* **Problem**: TypeScript index signatures like `[key: string]: unknown` are
|
||||
* translated by ts-to-zod to `z.record(z.string(), z.unknown()).and(z.object({...}))`.
|
||||
* This creates a `ZodIntersection` type which doesn't support `.extend()` etc.
|
||||
*
|
||||
* **Solution**: Replace the intersection pattern with `z.looseObject()`.
|
||||
* **Solution**: Replace with `z.object({...}).passthrough()` which works in both
|
||||
* Zod v3 and v4, allowing extra properties while validating known ones.
|
||||
*
|
||||
* ## Adding Schema Descriptions
|
||||
*
|
||||
|
|
@ -178,23 +177,17 @@ async function generateJsonSchema() {
|
|||
* Post-process generated schemas for project compatibility.
|
||||
*/
|
||||
function postProcess(content: string): string {
|
||||
// 1. Update import to use zod/v4
|
||||
content = content.replace(
|
||||
'import { z } from "zod";',
|
||||
'import * as z from "zod/v4";',
|
||||
);
|
||||
|
||||
// 2. Add MCP SDK schema imports
|
||||
// 1. Add MCP SDK schema imports (keep standard zod import for v3/v4 compatibility)
|
||||
const mcpImports = EXTERNAL_TYPE_SCHEMAS.join(",\n ");
|
||||
content = content.replace(
|
||||
'import * as z from "zod/v4";',
|
||||
`import * as z from "zod/v4";
|
||||
'import { z } from "zod";',
|
||||
`import { z } from "zod";
|
||||
import {
|
||||
${mcpImports},
|
||||
} from "@modelcontextprotocol/sdk/types.js";`,
|
||||
);
|
||||
|
||||
// 3. Remove z.any() placeholders for external types (now imported from MCP SDK)
|
||||
// 2. Remove z.any() placeholders for external types (now imported from MCP SDK)
|
||||
for (const schema of EXTERNAL_TYPE_SCHEMAS) {
|
||||
content = content.replace(
|
||||
new RegExp(`(?:export )?const ${schema} = z\\.any\\(\\);\\n?`, "g"),
|
||||
|
|
@ -202,15 +195,16 @@ import {
|
|||
);
|
||||
}
|
||||
|
||||
// 4. Replace z.record().and(z.object({...})) with z.looseObject({...})
|
||||
// 3. Replace z.record().and(z.object({...})) with z.object({...}).passthrough()
|
||||
// Uses brace-counting to handle nested objects correctly.
|
||||
content = replaceRecordAndWithLooseObject(content);
|
||||
// passthrough() works in both Zod v3 and v4, unlike looseObject() which is v4-only.
|
||||
content = replaceRecordAndWithPassthrough(content);
|
||||
|
||||
// 5. Add header comment
|
||||
// 4. Add header comment
|
||||
content = content.replace(
|
||||
"// Generated by ts-to-zod",
|
||||
`// Generated by ts-to-zod
|
||||
// Post-processed for Zod v4 and MCP SDK compatibility
|
||||
// Post-processed for Zod v3/v4 compatibility and MCP SDK integration
|
||||
// Run: npm run generate:schemas`,
|
||||
);
|
||||
|
||||
|
|
@ -218,10 +212,11 @@ import {
|
|||
}
|
||||
|
||||
/**
|
||||
* Replace z.record(z.string(), z.unknown()).and(z.object({...})) with z.looseObject({...})
|
||||
* Replace z.record(z.string(), z.unknown()).and(z.object({...})) with z.object({...}).passthrough()
|
||||
* Uses brace-counting to handle nested objects correctly.
|
||||
* passthrough() works in both Zod v3 and v4, allowing extra properties.
|
||||
*/
|
||||
function replaceRecordAndWithLooseObject(content: string): string {
|
||||
function replaceRecordAndWithPassthrough(content: string): string {
|
||||
const pattern = "z.record(z.string(), z.unknown()).and(z.object({";
|
||||
let result = content;
|
||||
let startIndex = 0;
|
||||
|
|
@ -245,7 +240,7 @@ function replaceRecordAndWithLooseObject(content: string): string {
|
|||
// Check if followed by ))
|
||||
if (result.slice(i, i + 2) === "))") {
|
||||
const objectContent = result.slice(objectStart, i - 1);
|
||||
const replacement = `z.looseObject({${objectContent}})`;
|
||||
const replacement = `z.object({${objectContent}}).passthrough()`;
|
||||
result = result.slice(0, matchStart) + replacement + result.slice(i + 2);
|
||||
startIndex = matchStart + replacement.length;
|
||||
} else {
|
||||
|
|
@ -260,11 +255,7 @@ function replaceRecordAndWithLooseObject(content: string): string {
|
|||
* Post-process generated integration tests.
|
||||
*/
|
||||
function postProcessTests(content: string): string {
|
||||
content = content.replace(
|
||||
'import { z } from "zod";',
|
||||
'import * as z from "zod/v4";',
|
||||
);
|
||||
|
||||
// Keep standard zod import for v3/v4 compatibility
|
||||
content = content.replace(
|
||||
"// Generated by ts-to-zod",
|
||||
`// Generated by ts-to-zod
|
||||
|
|
|
|||
Loading…
Reference in a new issue