codemod-zod-openapi-v5
v1.0.1
Published
Migration tool for zod-openapi v4 to v5
Maintainers
Readme
codemod-zod-openapi-v5
An almost entirely vibe coded codemod tool to help migrate codebases from zod-openapi v4 to v5. Please check the output.
Quick Start
# Migrate your entire src directory (dry run to preview changes)
pnpm dlx codemod-zod-openapi-v5 "src/**/*.ts" --dry-run
# Run the actual migration
pnpm dlx codemod-zod-openapi-v5 "src/**/*.ts"Features
This tool performs the following migrations automatically:
1. Removes extendZodWithOpenApi imports and calls
- Removes
extendZodWithOpenApifrom import statements - Removes calls to
extendZodWithOpenApi(z)
2. Migrates zod imports to zod/v4
- Changes
import { z } from 'zod'toimport * as z from 'zod/v4' - Changes
import z from 'zod'toimport * as z from 'zod/v4' - Changes
import * as z from 'zod'toimport * as z from 'zod/v4' - Only migrates imports that import
zspecifically
3. Converts .openapi() to .meta()
- Changes all
.openapi()method calls to.meta() - Works with all Zod types (string, object, array, union, etc.)
4. Converts ref to id in metadata
- Changes
ref: 'some-ref'toid: 'some-ref'within.meta()calls - Handles nested
refproperties inheaderandparamobjects - Recursively processes deeply nested objects
- Works with
ZodOpenApi*type objects andcreateDocumentcalls
5. Converts refType to unusedIO in metadata
- Changes
refType: 'input'tounusedIO: 'input'within.meta()calls - Supports all refType values: 'input', 'output', 'both'
6. Transforms unionOneOf: true to override function
- Converts
unionOneOf: truein.meta()calls to anoverridefunction - Converts
unionOneOf: trueincreateDocumentoptions to anoverridefunction - Converts
unionOneOf: trueincreateSchemaoptions to anopts.overridefunction - Merges with existing
defaultDateSchematransformations when both are present
7. Comments out effectType in .meta() calls
- Adds
// TODO: effectType is not supported in v5comment - Preserves the original property for manual review
8. Migrates createSchema options
- Changes
schemaTypetoio - Changes
componentRefPathtoschemaComponentRefPath - Changes
componentstoschemaComponents - Converts
defaultDateSchematoopts.overridefunction
9. Migrates createDocument options
- Converts
defaultDateSchematooverridefunction - Merges with
unionOneOftransformations when both are present
10. Adds return; statements to override functions
- Ensures all generated override functions have proper return statements
Installation
# Install dependencies
pnpm install
# Build the project
pnpm buildUsage
Using pnpm dlx (Recommended)
Run the migration tool directly without installing:
# Basic usage - migrate all TypeScript files in src directory
pnpm dlx codemod-zod-openapi-v5 "src/**/*.ts"
# Dry run (preview changes without modifying files)
pnpm dlx codemod-zod-openapi-v5 "src/**/*.ts" --dry-run
# Verbose output
pnpm dlx codemod-zod-openapi-v5 "src/**/*.ts" --verbose
# Ignore specific patterns
pnpm dlx codemod-zod-openapi-v5 "src/**/*.ts" --ignore "src/test/**,src/generated/**"
# Combine options
pnpm dlx codemod-zod-openapi-v5 "src/**/*.ts" --dry-run --verbose --ignore "*.test.ts"Using npx or yarn dlx
For npm or yarn users:
# Using npx
npx codemod-zod-openapi-v5 "src/**/*.ts" --dry-run
# Using yarn dlx
yarn dlx codemod-zod-openapi-v5 "src/**/*.ts" --dry-runUsing Local Installation
If you've cloned this repository:
# Basic usage - migrate all TypeScript files in src directory
node dist/index.js "src/**/*.ts"
# Dry run (preview changes without modifying files)
node dist/index.js "src/**/*.ts" --dry-run
# Verbose output
node dist/index.js "src/**/*.ts" --verbose
# Ignore specific patterns
node dist/index.js "src/**/*.ts" --ignore "src/test/**,src/generated/**"
# Combine options
node dist/index.js "src/**/*.ts" --dry-run --verbose --ignore "*.test.ts"Migration Examples
Before Migration
import { z } from "zod";
import {
extendZodWithOpenApi,
createDocument,
createSchema,
} from "zod-openapi";
extendZodWithOpenApi(z);
// Basic schema transformations
const UserSchema = z
.object({
name: z.string().openapi({
description: "User name",
refType: "input",
}),
age: z.number().openapi({
description: "User age",
effectType: "input",
}),
})
.openapi({
ref: "User",
unionOneOf: true,
});
// Nested refs in header/param objects
const headerSchema = z.string().openapi({
header: { ref: "registeredHeader" },
param: { ref: "registeredParam" },
description: "Complex nested refs",
});
// createSchema with various options
const schema = createSchema(z.date(), {
schemaType: "input",
componentRefPath: "#/components/schemas",
components: { schemas: {} },
defaultDateSchema: {
type: "string",
format: "date-time",
},
unionOneOf: true,
});
// createDocument with options
const document = createDocument(
{
openapi: "3.1.0",
info: { title: "API", version: "1.0.0" },
paths: {
"/users": {
ref: "registeredPath",
get: {
requestBody: {
ref: "registeredRequestBody",
},
},
},
},
},
{
unionOneOf: true,
defaultDateSchema: {
type: "string",
format: "date-time",
},
}
);After Migration
import * as z from "zod/v4";
import { createDocument, createSchema } from "zod-openapi";
// Basic schema transformations
const UserSchema = z
.object({
name: z.string().meta({
description: "User name",
unusedIO: "input",
}),
age: z.number().meta({
description: "User age",
// TODO: effectType is not supported in v5
// effectType: "input"
}),
})
.meta({
id: "User",
override: ({ jsonSchema, zodSchema }) => {
const def = zodSchema._zod.def;
if (def.type === "union") {
jsonSchema.oneOf = jsonSchema.anyOf;
delete jsonSchema.anyOf;
return;
}
},
});
// Nested refs in header/param objects
const headerSchema = z.string().meta({
header: { id: "registeredHeader" },
param: { id: "registeredParam" },
description: "Complex nested refs",
});
// createSchema with various options
const schema = createSchema(z.date(), {
io: "input",
schemaComponentRefPath: "#/components/schemas",
schemaComponents: { schemas: {} },
opts: {
override: ({ jsonSchema, zodSchema }) => {
const def = zodSchema._zod.def;
if (def.type === "union") {
jsonSchema.oneOf = jsonSchema.anyOf;
delete jsonSchema.anyOf;
return;
}
if (def.type === "date") {
jsonSchema.type = "string";
jsonSchema.format = "date-time";
return;
}
},
},
});
// createDocument with options
const document = createDocument(
{
openapi: "3.1.0",
info: { title: "API", version: "1.0.0" },
paths: {
"/users": {
id: "registeredPath",
get: {
requestBody: {
id: "registeredRequestBody",
},
},
},
},
},
{
override: ({ jsonSchema, zodSchema }) => {
const def = zodSchema._zod.def;
if (def.type === "union") {
jsonSchema.oneOf = jsonSchema.anyOf;
delete jsonSchema.anyOf;
return;
}
if (def.type === "date") {
jsonSchema.type = "string";
jsonSchema.format = "date-time";
return;
}
},
}
);Options
--dry-run,-d: Preview changes without modifying files--verbose,-v: Show detailed output for each file processed--ignore <patterns>: Comma-separated glob patterns to ignore
Default Ignored Directories
The tool automatically ignores these common directories:
node_modules/dist/build/.git/.next/.nuxt/coverage/
You can add additional patterns with the --ignore option.
Output Statistics
The tool provides detailed statistics on all transformations:
📊 Summary:
- Files processed: 5
- Files modified: 3
- Imports removed: 3
- Extend calls removed: 3
- Zod imports migrated: 3
- openapi() → meta(): 15
- ref → id changes: 8
- refType → unusedIO changes: 4
- unionOneOf → override changes: 2
- effectType commented: 1
- schemaType → io changes: 2
- componentRefPath → schemaComponentRefPath changes: 1
- components → schemaComponents changes: 1
- createSchema unionOneOf → opts.override changes: 1
- createSchema defaultDateSchema → opts.override changes: 2
- defaultDateSchema → override changes: 1Supported File Types
- TypeScript (
.ts,.tsx) - JavaScript (
.js,.jsx)
Requirements
- Node.js 20+
- pnpm (recommended) or npm
Development
# Install dependencies
pnpm install
# Run in development mode
pnpm dev "path/to/files/**/*.ts"
# Build
pnpm build
# Run tests
pnpm testEdge Cases Handled
- Merging transformations: When both
unionOneOfanddefaultDateSchemaare present, they're merged into a single override function - Nested objects: Recursively processes deeply nested
refproperties - Mixed scenarios: Handles files with multiple different transformation types
- Existing overrides: Preserves existing override functions when possible
- Return statements: Ensures all generated override functions have proper return statements
License
MIT
