diff --git a/api.oas3.yaml b/api.oas3.yaml
index 85d8f46..c91d474 100644
--- a/api.oas3.yaml
+++ b/api.oas3.yaml
@@ -199,6 +199,69 @@ components:
TitleAsset:
$ref: "./schemas/titleasset.yaml#/TitleAsset"
+ SvgAsset:
+ $ref: "./schemas/svgasset.yaml#/SvgAsset"
+
+ SvgShape:
+ $ref: "./schemas/svgshapes.yaml#/SvgShape"
+
+ SvgRectangleShape:
+ $ref: "./schemas/svgshapes.yaml#/SvgRectangleShape"
+
+ SvgCircleShape:
+ $ref: "./schemas/svgshapes.yaml#/SvgCircleShape"
+
+ SvgEllipseShape:
+ $ref: "./schemas/svgshapes.yaml#/SvgEllipseShape"
+
+ SvgLineShape:
+ $ref: "./schemas/svgshapes.yaml#/SvgLineShape"
+
+ SvgPolygonShape:
+ $ref: "./schemas/svgshapes.yaml#/SvgPolygonShape"
+
+ SvgStarShape:
+ $ref: "./schemas/svgshapes.yaml#/SvgStarShape"
+
+ SvgArrowShape:
+ $ref: "./schemas/svgshapes.yaml#/SvgArrowShape"
+
+ SvgHeartShape:
+ $ref: "./schemas/svgshapes.yaml#/SvgHeartShape"
+
+ SvgCrossShape:
+ $ref: "./schemas/svgshapes.yaml#/SvgCrossShape"
+
+ SvgRingShape:
+ $ref: "./schemas/svgshapes.yaml#/SvgRingShape"
+
+ SvgPathShape:
+ $ref: "./schemas/svgshapes.yaml#/SvgPathShape"
+
+ SvgFill:
+ $ref: "./schemas/svgproperties.yaml#/SvgFill"
+
+ SvgSolidFill:
+ $ref: "./schemas/svgproperties.yaml#/SvgSolidFill"
+
+ SvgLinearGradientFill:
+ $ref: "./schemas/svgproperties.yaml#/SvgLinearGradientFill"
+
+ SvgRadialGradientFill:
+ $ref: "./schemas/svgproperties.yaml#/SvgRadialGradientFill"
+
+ SvgGradientStop:
+ $ref: "./schemas/svgproperties.yaml#/SvgGradientStop"
+
+ SvgStroke:
+ $ref: "./schemas/svgproperties.yaml#/SvgStroke"
+
+ SvgShadow:
+ $ref: "./schemas/svgproperties.yaml#/SvgShadow"
+
+ SvgTransform:
+ $ref: "./schemas/svgproperties.yaml#/SvgTransform"
+
Transition:
$ref: "./schemas/transition.yaml#/Transition"
diff --git a/package.json b/package.json
index b7ca647..d8d5c61 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@shotstack/schemas",
- "version": "1.3.5",
+ "version": "1.4.1",
"description": "Centralized OpenAPI schemas and TypeScript types for Shotstack API",
"type": "module",
"main": "dist/index.js",
diff --git a/schemas/asset.yaml b/schemas/asset.yaml
index adbae25..364ee10 100644
--- a/schemas/asset.yaml
+++ b/schemas/asset.yaml
@@ -16,6 +16,7 @@ Asset:
html: '#/components/schemas/HtmlAsset'
title: '#/components/schemas/TitleAsset'
shape: '#/components/schemas/ShapeAsset'
+ svg: '#/components/schemas/SvgAsset'
text-to-image: '#/components/schemas/TextToImageAsset'
image-to-video: '#/components/schemas/ImageToVideoAsset'
oneOf:
@@ -29,6 +30,7 @@ Asset:
- $ref: './htmlasset.yaml#/HtmlAsset'
- $ref: './titleasset.yaml#/TitleAsset'
- $ref: './shapeasset.yaml#/ShapeAsset'
+ - $ref: './svgasset.yaml#/SvgAsset'
- $ref: './texttoimageasset.yaml#/TextToImageAsset'
- $ref: './imagetovideoasset.yaml#/ImageToVideoAsset'
additionalProperties: false
diff --git a/schemas/ingest/responses/sourceresponseattributes.yaml b/schemas/ingest/responses/sourceresponseattributes.yaml
index e525865..5745453 100644
--- a/schemas/ingest/responses/sourceresponseattributes.yaml
+++ b/schemas/ingest/responses/sourceresponseattributes.yaml
@@ -51,7 +51,7 @@
example: 1920
height:
description: The height in pixels of the ingested source file, if a video or image.
- type: string
+ type: integer
example: 1080
duration:
description: The duration in seconds of the ingested source file, if a video or audio file.
diff --git a/schemas/rotatetransformation.yaml b/schemas/rotatetransformation.yaml
index 85a68ab..3fcfbd8 100644
--- a/schemas/rotatetransformation.yaml
+++ b/schemas/rotatetransformation.yaml
@@ -9,7 +9,7 @@
Rotate a clip by the specified angle in degrees. Use a number or an array of
[Tween](./#tocs_tween) objects to create a custom animation.
oneOf:
- - type: integer
+ - type: number
description: >-
The angle to rotate the clip. Can be 0 to 360, or 0 to -360. Using a
positive number rotates the clip clockwise, negative numbers
diff --git a/schemas/svgasset.yaml b/schemas/svgasset.yaml
new file mode 100644
index 0000000..eeccc15
--- /dev/null
+++ b/schemas/svgasset.yaml
@@ -0,0 +1,173 @@
+SvgAsset:
+ description: |
+ The SvgAsset is used to add scalable vector graphics (SVG) shapes to a video.
+ It provides two mutually exclusive ways to define shapes:
+
+ **Option 1: Import SVG markup using `src`**
+ ```json
+ {
+ "type": "svg",
+ "src": ""
+ }
+ ```
+ When using `src`, no other properties are allowed. The fill, stroke, and dimensions
+ are automatically extracted from the SVG markup.
+
+ **Option 2: Define shapes programmatically using `shape`**
+ ```json
+ {
+ "type": "svg",
+ "shape": { "type": "circle", "radius": 50 },
+ "fill": { "type": "solid", "color": "#FF0000" }
+ }
+ ```
+ When using `shape`, you can customize fill, stroke, shadow, transform, and other properties.
+ The `src` property is not allowed in this mode.
+
+ **Important:** You must provide either `src` OR `shape`, but not both.
+ These two modes are mutually exclusive.
+
+ **Available Shapes (Option 2 only):**
+ - `rectangle` - Rectangles with optional rounded corners
+ - `circle` - Perfect circles
+ - `ellipse` - Ellipses/ovals with separate x and y radii
+ - `line` - Straight lines with configurable thickness
+ - `polygon` - Regular polygons (triangle, pentagon, hexagon, etc.)
+ - `star` - Multi-pointed stars
+ - `arrow` - Directional arrows
+ - `heart` - Heart shapes
+ - `cross` - Plus/cross shapes
+ - `ring` - Donut/ring shapes
+ - `path` - Custom shapes using SVG path data
+
+ See [W3C SVG 2 Specification](https://www.w3.org/TR/SVG2/) for path data syntax.
+ type: object
+ properties:
+ type:
+ description: The asset type - set to `svg` for SVG shapes.
+ type: string
+ enum:
+ - svg
+ default: svg
+ example: svg
+ src:
+ description: |
+ Raw SVG markup string to import. When provided, the shape is extracted
+ automatically from the SVG content.
+
+ **Supported elements:** ``, ``, ``, ``,
+ ``, ``, ``
+
+ **Automatically extracted:**
+ - Path data (converted to a single combined path)
+ - Fill color (from `fill` attribute or `style`)
+ - Stroke color and width (from attributes or `style`)
+ - Dimensions (from `width`/`height` or `viewBox`)
+ - Opacity (from `opacity` attribute)
+
+ **Important:** When using `src`, no other properties (shape, fill, stroke, etc.)
+ are allowed. All styling must be defined within the SVG markup itself.
+ type: string
+ minLength: 1
+ maxLength: 500000
+ example: ''
+ shape:
+ description: |
+ The shape definition using primitives. The `type` property within determines
+ the shape kind and its specific properties.
+
+ **Important:** When using `shape`, the `src` property is not allowed.
+ $ref: "./svgshapes.yaml#/SvgShape"
+ fill:
+ description: |
+ Fill properties for the shape interior.
+ Can be a solid color or a gradient (linear/radial).
+ If omitted, the shape will have no fill (transparent interior).
+
+ **Note:** Only allowed when using `shape`, not with `src`.
+ $ref: "./svgproperties.yaml#/SvgFill"
+ stroke:
+ description: |
+ Stroke (outline) properties for the shape.
+ If omitted, the shape will have no stroke (no outline).
+
+ **Note:** Only allowed when using `shape`, not with `src`.
+ $ref: "./svgproperties.yaml#/SvgStroke"
+ shadow:
+ description: |
+ Drop shadow properties for the shape.
+ Creates a shadow effect behind the shape.
+
+ **Note:** Only allowed when using `shape`, not with `src`.
+ $ref: "./svgproperties.yaml#/SvgShadow"
+ transform:
+ description: |
+ Transform properties for positioning, rotating, and scaling the shape.
+ The transform is applied relative to the transformation origin.
+
+ **Note:** Only allowed when using `shape`, not with `src`.
+ $ref: "./svgproperties.yaml#/SvgTransform"
+ opacity:
+ description: |
+ The overall opacity of the entire shape (including fill, stroke, and shadow).
+ `1` is fully opaque, `0` is fully transparent.
+ This is applied on top of individual fill/stroke/shadow opacity values.
+
+ **Note:** Only allowed when using `shape`, not with `src`.
+ type: number
+ minimum: 0
+ maximum: 1
+ default: 1
+ example: 1
+ width:
+ description: |
+ The width of the bounding box in pixels.
+ If specified, the shape may be scaled to fit within this width.
+ If omitted, the shape uses its natural dimensions.
+
+ **Note:** Only allowed when using `shape`, not with `src`.
+ type: integer
+ minimum: 1
+ maximum: 4096
+ example: 400
+ height:
+ description: |
+ The height of the bounding box in pixels.
+ If specified, the shape may be scaled to fit within this height.
+ If omitted, the shape uses its natural dimensions.
+
+ **Note:** Only allowed when using `shape`, not with `src`.
+ type: integer
+ minimum: 1
+ maximum: 4096
+ example: 300
+ required:
+ - type
+ example:
+ type: svg
+ shape:
+ type: star
+ points: 5
+ outerRadius: 100
+ innerRadius: 50
+ fill:
+ type: linear
+ angle: 45
+ stops:
+ - offset: 0
+ color: "#FFD700"
+ - offset: 1
+ color: "#FF6B6B"
+ opacity: 1
+ stroke:
+ color: "#2C3E50"
+ width: 3
+ opacity: 1
+ lineCap: round
+ lineJoin: round
+ transform:
+ x: 200
+ y: 150
+ rotation: 0
+ scale: 1
+ opacity: 1
diff --git a/schemas/svgproperties.yaml b/schemas/svgproperties.yaml
new file mode 100644
index 0000000..1c70760
--- /dev/null
+++ b/schemas/svgproperties.yaml
@@ -0,0 +1,311 @@
+SvgFill:
+ description: |
+ Fill properties for SVG shapes. Supports solid colors and gradients.
+ The fill defines how the interior of a shape is painted.
+ oneOf:
+ - $ref: "#/SvgSolidFill"
+ - $ref: "#/SvgLinearGradientFill"
+ - $ref: "#/SvgRadialGradientFill"
+ discriminator:
+ propertyName: type
+ mapping:
+ solid: "#/components/schemas/SvgSolidFill"
+ linear: "#/components/schemas/SvgLinearGradientFill"
+ radial: "#/components/schemas/SvgRadialGradientFill"
+
+SvgSolidFill:
+ description: A solid color fill for SVG shapes.
+ type: object
+ properties:
+ type:
+ description: The fill type - set to `solid` for a single color fill.
+ type: string
+ enum:
+ - solid
+ default: solid
+ color:
+ description: |
+ The fill color using hexadecimal color notation (e.g., `#FF0000` for red).
+ Must be a 6-digit hex color code prefixed with `#`.
+ type: string
+ pattern: "^#[A-Fa-f0-9]{6}$"
+ default: "#000000"
+ example: "#3498db"
+ opacity:
+ description: |
+ The opacity of the fill where `1` is fully opaque and `0` is fully transparent.
+ Values between 0 and 1 create semi-transparent fills.
+ type: number
+ minimum: 0
+ maximum: 1
+ default: 1
+ example: 0.8
+ required:
+ - type
+ - color
+
+SvgGradientStop:
+ description: |
+ A color stop in a gradient. Each stop defines a color at a specific position
+ along the gradient vector. Gradients require at least 2 stops.
+ type: object
+ properties:
+ offset:
+ description: |
+ Position of the color stop along the gradient vector.
+ `0` represents the start and `1` represents the end of the gradient.
+ type: number
+ minimum: 0
+ maximum: 1
+ example: 0.5
+ color:
+ description: The color at this stop using hexadecimal color notation.
+ type: string
+ pattern: "^#[A-Fa-f0-9]{6}$"
+ example: "#e74c3c"
+ required:
+ - offset
+ - color
+
+SvgLinearGradientFill:
+ description: |
+ A linear gradient fill that transitions colors along a straight line.
+ The gradient direction is controlled by the `angle` property.
+ type: object
+ properties:
+ type:
+ description: The fill type - set to `linear` for a linear gradient fill.
+ type: string
+ enum:
+ - linear
+ angle:
+ description: |
+ The angle of the gradient in degrees. `0` is horizontal (left to right),
+ `90` is vertical (bottom to top), `180` is right to left, etc.
+ type: number
+ minimum: 0
+ maximum: 360
+ default: 0
+ example: 45
+ stops:
+ description: |
+ Array of color stops that define the gradient colors and their positions.
+ Must have at least 2 stops. Offsets should increase from 0 to 1.
+ type: array
+ minItems: 2
+ items:
+ $ref: "#/SvgGradientStop"
+ opacity:
+ description: |
+ The overall opacity of the gradient where `1` is fully opaque and `0` is fully transparent.
+ type: number
+ minimum: 0
+ maximum: 1
+ default: 1
+ example: 1
+ required:
+ - type
+ - stops
+
+SvgRadialGradientFill:
+ description: |
+ A radial gradient fill that transitions colors radiating outward from a center point.
+ The gradient creates a circular or elliptical color transition.
+ type: object
+ properties:
+ type:
+ description: The fill type - set to `radial` for a radial gradient fill.
+ type: string
+ enum:
+ - radial
+ stops:
+ description: |
+ Array of color stops that define the gradient colors and their positions.
+ Must have at least 2 stops. Offset `0` is the center, `1` is the outer edge.
+ type: array
+ minItems: 2
+ items:
+ $ref: "#/SvgGradientStop"
+ opacity:
+ description: |
+ The overall opacity of the gradient where `1` is fully opaque and `0` is fully transparent.
+ type: number
+ minimum: 0
+ maximum: 1
+ default: 1
+ example: 1
+ required:
+ - type
+ - stops
+
+SvgStroke:
+ description: |
+ Stroke (outline) properties for SVG shapes. The stroke defines how the outline
+ of a shape is painted, including its color, width, and line style.
+ type: object
+ properties:
+ color:
+ description: The stroke color using hexadecimal color notation.
+ type: string
+ pattern: "^#[A-Fa-f0-9]{6}$"
+ default: "#000000"
+ example: "#2c3e50"
+ width:
+ description: |
+ The width of the stroke in pixels. Must be greater than 0.
+ Larger values create thicker outlines.
+ type: number
+ minimum: 0
+ maximum: 100
+ default: 1
+ example: 2
+ opacity:
+ description: The opacity of the stroke where `1` is opaque and `0` is transparent.
+ type: number
+ minimum: 0
+ maximum: 1
+ default: 1
+ example: 1
+ lineCap:
+ description: |
+ The shape at the end of open paths (lines, polylines, unclosed paths).
+
+
`butt` - flat edge perpendicular to the line (default)
+
`round` - semicircular cap extending beyond the endpoint
+
`square` - rectangular cap extending beyond the endpoint
+
+ type: string
+ enum:
+ - butt
+ - round
+ - square
+ default: butt
+ example: round
+ lineJoin:
+ description: |
+ The shape at the corners where two lines meet.
+
+
`miter` - sharp corner (default)
+
`round` - rounded corner
+
`bevel` - flattened corner
+
+ type: string
+ enum:
+ - miter
+ - round
+ - bevel
+ default: miter
+ example: round
+ dashArray:
+ description: |
+ Pattern of dashes and gaps for the stroke. An array of numbers where
+ odd indices are dash lengths and even indices are gap lengths.
+ For example, `[10, 5]` creates 10px dashes with 5px gaps.
+ `[10, 5, 2, 5]` creates alternating 10px and 2px dashes with 5px gaps.
+ type: array
+ items:
+ type: number
+ minimum: 0
+ example: [10, 5]
+ dashOffset:
+ description: |
+ Offset for the dash pattern. Positive values shift the pattern
+ forward along the path, negative values shift it backward.
+ type: number
+ default: 0
+ example: 5
+
+SvgShadow:
+ description: |
+ Drop shadow properties for SVG shapes. Creates a shadow effect behind the shape.
+ type: object
+ properties:
+ offsetX:
+ description: |
+ Horizontal offset of the shadow in pixels.
+ Positive values move the shadow to the right, negative to the left.
+ type: number
+ default: 0
+ example: 4
+ offsetY:
+ description: |
+ Vertical offset of the shadow in pixels.
+ Positive values move the shadow down, negative values move it up.
+ type: number
+ default: 0
+ example: 4
+ blur:
+ description: The blur radius of the shadow in pixels. Must be 0 or greater.
+ type: number
+ minimum: 0
+ default: 0
+ example: 8
+ color:
+ description: The shadow color using hexadecimal color notation.
+ type: string
+ pattern: "^#[A-Fa-f0-9]{6}$"
+ default: "#000000"
+ example: "#000000"
+ opacity:
+ description: The opacity of the shadow where `1` is opaque and `0` is transparent.
+ type: number
+ minimum: 0
+ maximum: 1
+ default: 0.5
+ example: 0.3
+
+SvgTransform:
+ description: |
+ Transformation properties for positioning, rotating, and scaling SVG shapes.
+ type: object
+ properties:
+ x:
+ description: |
+ The x-coordinate position of the shape in pixels.
+ Relative to the top-left corner of the viewport.
+ type: number
+ default: 0
+ example: 100
+ y:
+ description: |
+ The y-coordinate position of the shape in pixels.
+ Relative to the top-left corner of the viewport.
+ type: number
+ default: 0
+ example: 100
+ rotation:
+ description: |
+ Rotation angle in degrees. Positive values rotate clockwise,
+ negative values rotate counter-clockwise. Range: -360 to 360.
+ type: number
+ minimum: -360
+ maximum: 360
+ default: 0
+ example: 45
+ scale:
+ description: |
+ Scale factor for the shape. `1` is original size, `2` is double size,
+ `0.5` is half size. Must be greater than 0.
+ type: number
+ minimum: 0.01
+ maximum: 100
+ default: 1
+ example: 1.5
+ originX:
+ description: |
+ The x-coordinate of the transformation origin as a value from 0 to 1.
+ `0` is the left edge, `0.5` is the center, `1` is the right edge.
+ type: number
+ minimum: 0
+ maximum: 1
+ default: 0.5
+ example: 0.5
+ originY:
+ description: |
+ The y-coordinate of the transformation origin as a value from 0 to 1.
+ `0` is the top edge, `0.5` is the center, `1` is the bottom edge.
+ type: number
+ minimum: 0
+ maximum: 1
+ default: 0.5
+ example: 0.5
diff --git a/schemas/svgshapes.yaml b/schemas/svgshapes.yaml
new file mode 100644
index 0000000..2e2978e
--- /dev/null
+++ b/schemas/svgshapes.yaml
@@ -0,0 +1,385 @@
+SvgShape:
+ description: |
+ The shape definition for an SVG asset. Each shape type has its own specific
+ properties. The `type` field determines which shape is rendered.
+ oneOf:
+ - $ref: "#/SvgRectangleShape"
+ - $ref: "#/SvgCircleShape"
+ - $ref: "#/SvgEllipseShape"
+ - $ref: "#/SvgLineShape"
+ - $ref: "#/SvgPolygonShape"
+ - $ref: "#/SvgStarShape"
+ - $ref: "#/SvgArrowShape"
+ - $ref: "#/SvgHeartShape"
+ - $ref: "#/SvgCrossShape"
+ - $ref: "#/SvgRingShape"
+ - $ref: "#/SvgPathShape"
+ discriminator:
+ propertyName: type
+ mapping:
+ rectangle: "#/components/schemas/SvgRectangleShape"
+ circle: "#/components/schemas/SvgCircleShape"
+ ellipse: "#/components/schemas/SvgEllipseShape"
+ line: "#/components/schemas/SvgLineShape"
+ polygon: "#/components/schemas/SvgPolygonShape"
+ star: "#/components/schemas/SvgStarShape"
+ arrow: "#/components/schemas/SvgArrowShape"
+ heart: "#/components/schemas/SvgHeartShape"
+ cross: "#/components/schemas/SvgCrossShape"
+ ring: "#/components/schemas/SvgRingShape"
+ path: "#/components/schemas/SvgPathShape"
+
+SvgRectangleShape:
+ description: |
+ A rectangle shape with optional rounded corners.
+ The rectangle is defined by its width and height dimensions.
+ type: object
+ properties:
+ type:
+ description: The shape type - set to `rectangle`.
+ type: string
+ enum:
+ - rectangle
+ width:
+ description: The width of the rectangle in pixels.
+ type: number
+ minimum: 1
+ maximum: 4096
+ example: 200
+ height:
+ description: The height of the rectangle in pixels.
+ type: number
+ minimum: 1
+ maximum: 4096
+ example: 100
+ cornerRadius:
+ description: |
+ The corner radius for rounded corners in pixels.
+ Set to `0` for sharp corners. The radius is automatically clamped
+ to half of the smallest dimension.
+ type: number
+ minimum: 0
+ maximum: 2048
+ default: 0
+ example: 10
+ required:
+ - type
+ - width
+ - height
+
+SvgCircleShape:
+ description: |
+ A perfect circle shape defined by its radius.
+ The circle is centered at the shape's position.
+ type: object
+ properties:
+ type:
+ description: The shape type - set to `circle`.
+ type: string
+ enum:
+ - circle
+ radius:
+ description: The radius of the circle in pixels.
+ type: number
+ minimum: 1
+ maximum: 2048
+ example: 50
+ required:
+ - type
+ - radius
+
+SvgEllipseShape:
+ description: |
+ An ellipse (oval) shape with separate horizontal and vertical radii.
+ The ellipse is centered at the shape's position.
+ type: object
+ properties:
+ type:
+ description: The shape type - set to `ellipse`.
+ type: string
+ enum:
+ - ellipse
+ radiusX:
+ description: The horizontal radius (semi-major axis) in pixels.
+ type: number
+ minimum: 1
+ maximum: 2048
+ example: 80
+ radiusY:
+ description: The vertical radius (semi-minor axis) in pixels.
+ type: number
+ minimum: 1
+ maximum: 2048
+ example: 50
+ required:
+ - type
+ - radiusX
+ - radiusY
+
+SvgLineShape:
+ description: |
+ A straight line shape with a specified length and thickness.
+ The line is drawn horizontally by default and can be rotated using transform.
+ type: object
+ properties:
+ type:
+ description: The shape type - set to `line`.
+ type: string
+ enum:
+ - line
+ length:
+ description: The length of the line in pixels.
+ type: number
+ minimum: 1
+ maximum: 4096
+ example: 100
+ thickness:
+ description: The thickness of the line in pixels.
+ type: number
+ minimum: 1
+ maximum: 500
+ example: 4
+ required:
+ - type
+ - length
+ - thickness
+
+SvgPolygonShape:
+ description: |
+ A regular polygon shape with a specified number of sides.
+ Examples: triangle (3), square (4), pentagon (5), hexagon (6), etc.
+ The polygon is inscribed in a circle of the given radius.
+ type: object
+ properties:
+ type:
+ description: The shape type - set to `polygon`.
+ type: string
+ enum:
+ - polygon
+ sides:
+ description: |
+ The number of sides of the polygon.
+ Minimum 3 (triangle), maximum 100 for practical use.
+ type: integer
+ minimum: 3
+ maximum: 100
+ example: 6
+ radius:
+ description: |
+ The radius of the circumscribed circle in pixels.
+ This determines the size of the polygon.
+ type: number
+ minimum: 1
+ maximum: 2048
+ example: 50
+ required:
+ - type
+ - sides
+ - radius
+
+SvgStarShape:
+ description: |
+ A star shape with a specified number of points.
+ The star is defined by outer and inner radii, creating the characteristic
+ pointed appearance.
+ type: object
+ properties:
+ type:
+ description: The shape type - set to `star`.
+ type: string
+ enum:
+ - star
+ points:
+ description: |
+ The number of points on the star.
+ Minimum 3 for a triangle-like star, typically 5 for a classic star.
+ type: integer
+ minimum: 3
+ maximum: 100
+ example: 5
+ outerRadius:
+ description: |
+ The outer radius in pixels - the distance from center to the tips of the points.
+ type: number
+ minimum: 1
+ maximum: 2048
+ example: 50
+ innerRadius:
+ description: |
+ The inner radius in pixels - the distance from center to the inner vertices.
+ Should be smaller than outerRadius for a star effect.
+ type: number
+ minimum: 1
+ maximum: 2048
+ example: 25
+ required:
+ - type
+ - points
+ - outerRadius
+ - innerRadius
+
+SvgArrowShape:
+ description: |
+ An arrow shape pointing to the right by default.
+ Use transform rotation to change direction.
+ type: object
+ properties:
+ type:
+ description: The shape type - set to `arrow`.
+ type: string
+ enum:
+ - arrow
+ length:
+ description: The total length of the arrow from tail to tip in pixels.
+ type: number
+ minimum: 1
+ maximum: 4096
+ example: 100
+ headWidth:
+ description: The width of the arrow head (the widest part) in pixels.
+ type: number
+ minimum: 1
+ maximum: 1000
+ example: 40
+ headLength:
+ description: The length of the arrow head portion in pixels.
+ type: number
+ minimum: 1
+ maximum: 1000
+ example: 30
+ shaftWidth:
+ description: The width of the arrow shaft (body) in pixels.
+ type: number
+ minimum: 1
+ maximum: 1000
+ example: 20
+ required:
+ - type
+ - length
+ - headWidth
+ - headLength
+ - shaftWidth
+
+SvgHeartShape:
+ description: |
+ A heart shape commonly used for love/like icons.
+ The heart is defined by a single size parameter.
+ type: object
+ properties:
+ type:
+ description: The shape type - set to `heart`.
+ type: string
+ enum:
+ - heart
+ size:
+ description: |
+ The size of the heart in pixels.
+ This determines both the width and height proportionally.
+ type: number
+ minimum: 1
+ maximum: 4096
+ example: 100
+ required:
+ - type
+ - size
+
+SvgCrossShape:
+ description: |
+ A cross or plus shape with equal or different arm lengths.
+ Can be styled as a plus sign (+) or a cross (x with rotation).
+ type: object
+ properties:
+ type:
+ description: The shape type - set to `cross`.
+ type: string
+ enum:
+ - cross
+ width:
+ description: The total width of the cross in pixels.
+ type: number
+ minimum: 1
+ maximum: 4096
+ example: 100
+ height:
+ description: The total height of the cross in pixels.
+ type: number
+ minimum: 1
+ maximum: 4096
+ example: 100
+ thickness:
+ description: The thickness of the cross arms in pixels.
+ type: number
+ minimum: 1
+ maximum: 500
+ example: 20
+ required:
+ - type
+ - width
+ - height
+ - thickness
+
+SvgRingShape:
+ description: |
+ A ring (donut/annulus) shape - a circle with a circular hole in the center.
+ The ring is defined by outer and inner radii.
+ type: object
+ properties:
+ type:
+ description: The shape type - set to `ring`.
+ type: string
+ enum:
+ - ring
+ outerRadius:
+ description: The outer radius of the ring in pixels.
+ type: number
+ minimum: 1
+ maximum: 2048
+ example: 50
+ innerRadius:
+ description: |
+ The inner radius (hole) of the ring in pixels.
+ Must be smaller than outerRadius.
+ type: number
+ minimum: 0
+ maximum: 2048
+ example: 30
+ required:
+ - type
+ - outerRadius
+ - innerRadius
+
+SvgPathShape:
+ description: |
+ A custom shape defined by SVG path data.
+ Supports all standard SVG path commands for creating complex shapes.
+
+ **Path Commands:**
+ - `M x y` / `m dx dy` - Move to (absolute/relative)
+ - `L x y` / `l dx dy` - Line to
+ - `H x` / `h dx` - Horizontal line to
+ - `V y` / `v dy` - Vertical line to
+ - `C x1 y1 x2 y2 x y` / `c` - Cubic Bezier curve
+ - `S x2 y2 x y` / `s` - Smooth cubic Bezier
+ - `Q x1 y1 x y` / `q` - Quadratic Bezier curve
+ - `T x y` / `t` - Smooth quadratic Bezier
+ - `A rx ry angle large-arc sweep x y` / `a` - Elliptical arc
+ - `Z` / `z` - Close path
+ type: object
+ properties:
+ type:
+ description: The shape type - set to `path`.
+ type: string
+ enum:
+ - path
+ d:
+ description: |
+ The SVG path data string defining the shape.
+ Uses standard SVG path commands (M, L, C, Q, A, Z, etc.).
+ Example: `M 0 0 L 100 0 L 100 100 L 0 100 Z` draws a square.
+ type: string
+ minLength: 1
+ maxLength: 100000
+ example: "M 0 0 L 100 0 L 50 86.6 Z"
+ required:
+ - type
+ - d
diff --git a/scripts/fix-discriminator.cjs b/scripts/fix-discriminator.cjs
index 0bc421c..1d4b225 100644
--- a/scripts/fix-discriminator.cjs
+++ b/scripts/fix-discriminator.cjs
@@ -3,9 +3,7 @@ const path = require("path");
const zodGenPath = path.join(__dirname, "..", "dist", "zod", "zod.gen.ts");
-console.log(
- "Fixing discriminator and adding coercion in generated Zod schemas..."
-);
+console.log("Fixing discriminator and adding z.coerce for number fields...");
let content = fs.readFileSync(zodGenPath, "utf8");
@@ -23,6 +21,7 @@ const newAssetSchema = `export const assetAssetSchema = z.discriminatedUnion("ty
htmlassetHtmlAssetSchema,
titleassetTitleAssetSchema,
shapeassetShapeAssetSchema,
+ svgassetSvgAssetSchema,
texttoimageassetTextToImageAssetSchema,
imagetovideoassetImageToVideoAssetSchema,
]);`;
@@ -34,64 +33,150 @@ if (assetUnionPattern.test(content)) {
console.log("⚠ Could not find assetAssetSchema to replace");
}
-const clipStartPattern =
- /start: z\.union\(\[z\.number\(\), z\.enum\(\["auto"\]\)\]\)/g;
-const clipStartReplacement = `start: z.union([z.preprocess((val) => typeof val === 'string' && val !== '' && !isNaN(Number(val)) ? Number(val) : val, z.number()), z.enum(["auto"])])`;
+const svgShapeUnionPattern =
+ /export const svgshapesSvgShapeSchema = z\.union\(\[[\s\S]*?\]\);/;
+
+const newSvgShapeSchema = `export const svgshapesSvgShapeSchema = z.discriminatedUnion("type", [
+ svgshapesSvgRectangleShapeSchema,
+ svgshapesSvgCircleShapeSchema,
+ svgshapesSvgEllipseShapeSchema,
+ svgshapesSvgLineShapeSchema,
+ svgshapesSvgPolygonShapeSchema,
+ svgshapesSvgStarShapeSchema,
+ svgshapesSvgArrowShapeSchema,
+ svgshapesSvgHeartShapeSchema,
+ svgshapesSvgCrossShapeSchema,
+ svgshapesSvgRingShapeSchema,
+ svgshapesSvgPathShapeSchema,
+]);`;
-if (clipStartPattern.test(content)) {
- content = content.replace(clipStartPattern, clipStartReplacement);
- console.log("✓ Added coercion for clip start");
+if (svgShapeUnionPattern.test(content)) {
+ content = content.replace(svgShapeUnionPattern, newSvgShapeSchema);
+ console.log("✓ Fixed svgshapesSvgShapeSchema discriminator");
} else {
- console.log("⚠ Could not find clip start pattern to add coercion");
+ console.log("⚠ Could not find svgshapesSvgShapeSchema to replace");
}
-const clipLengthPattern =
- /length: z\.union\(\[z\.number\(\), z\.literal\("auto"\), z\.literal\("end"\)\]\)/g;
-const clipLengthReplacement = `length: z.union([z.preprocess((val) => typeof val === 'string' && val !== '' && !isNaN(Number(val)) ? Number(val) : val, z.number()), z.literal("auto"), z.literal("end")])`;
+const svgFillUnionPattern =
+ /export const svgpropertiesSvgFillSchema = z\.union\(\[[\s\S]*?\]\);/;
+
+const newSvgFillSchema = `export const svgpropertiesSvgFillSchema = z.discriminatedUnion("type", [
+ svgpropertiesSvgSolidFillSchema,
+ svgpropertiesSvgLinearGradientFillSchema,
+ svgpropertiesSvgRadialGradientFillSchema,
+]);`;
-if (clipLengthPattern.test(content)) {
- content = content.replace(clipLengthPattern, clipLengthReplacement);
- console.log("✓ Added coercion for clip length");
+if (svgFillUnionPattern.test(content)) {
+ content = content.replace(svgFillUnionPattern, newSvgFillSchema);
+ console.log("✓ Fixed svgpropertiesSvgFillSchema discriminator");
} else {
- console.log("⚠ Could not find clip length pattern to add coercion");
+ console.log("⚠ Could not find svgpropertiesSvgFillSchema to replace");
}
-const trimPattern = /trim: z\.optional\(z\.number\(\)\)/g;
-const trimReplacement = `trim: z.optional(z.preprocess((val) => typeof val === 'string' && val !== '' && !isNaN(Number(val)) ? Number(val) : val, z.number()))`;
+const svgAssetPattern =
+ /export const svgassetSvgAssetSchema = z\.object\(\{[\s\S]*?\}\);/;
+
+const svgAssetSuperRefine = `export const svgassetSvgAssetSchema = z.object({
+ type: z.enum(["svg"]),
+ src: z.optional(z.string().min(1).max(500000)),
+ shape: z.optional(svgshapesSvgShapeSchema),
+ fill: z.optional(svgpropertiesSvgFillSchema),
+ stroke: z.optional(svgpropertiesSvgStrokeSchema),
+ shadow: z.optional(svgpropertiesSvgShadowSchema),
+ transform: z.optional(svgpropertiesSvgTransformSchema),
+ opacity: z.optional(z.preprocess(((v: unknown) => { if (v === '' || v === null || v === undefined || Array.isArray(v)) return undefined; if (typeof v === 'string') return Number(v); return v; }), z.number().gte(0).lte(1))).default(1),
+ width: z.optional(z.preprocess(((v: unknown) => { if (v === '' || v === null || v === undefined || Array.isArray(v)) return undefined; if (typeof v === 'string') return Number(v); return v; }), z.number().int().gte(1).lte(4096))),
+ height: z.optional(z.preprocess(((v: unknown) => { if (v === '' || v === null || v === undefined || Array.isArray(v)) return undefined; if (typeof v === 'string') return Number(v); return v; }), z.number().int().gte(1).lte(4096))),
+}).superRefine((data, ctx) => {
+ const hasShape = data.shape !== undefined;
+ const hasSrc = data.src !== undefined && data.src.trim() !== "";
+
+ if (!hasShape && !hasSrc) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Either 'src' or 'shape' must be provided",
+ path: [],
+ });
+ }
-const trimCount = (content.match(trimPattern) || []).length;
-if (trimCount > 0) {
- content = content.replace(trimPattern, trimReplacement);
- console.log(`✓ Added coercion for trim (${trimCount} occurrences)`);
-}
+ if (hasShape && hasSrc) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Provide either 'src' or 'shape', not both",
+ path: ["src"],
+ });
+ }
-const volumePattern =
- /volume: z\.optional\(z\.number\(\)\.gte\(0\)\.lte\(1\)\)/g;
-const volumeReplacement = `volume: z.optional(z.preprocess((val) => typeof val === 'string' && val !== '' && !isNaN(Number(val)) ? Number(val) : val, z.number().gte(0).lte(1)))`;
+ if (hasSrc) {
+ const disallowedProps = ["shape", "fill", "stroke", "shadow", "transform", "width", "height"];
+ for (const prop of disallowedProps) {
+ if (data[prop] !== undefined) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: \`'\${prop}' is not allowed when using 'src'. Only 'type' and 'src' are allowed in import mode\`,
+ path: [prop],
+ });
+ }
+ }
+ }
+});`;
-const volumeCount = (content.match(volumePattern) || []).length;
-if (volumeCount > 0) {
- content = content.replace(volumePattern, volumeReplacement);
- console.log(`✓ Added coercion for volume (${volumeCount} occurrences)`);
+if (svgAssetPattern.test(content)) {
+ content = content.replace(svgAssetPattern, svgAssetSuperRefine);
+ console.log("✓ Added superRefine validation to svgassetSvgAssetSchema for mutual exclusivity");
+} else {
+ console.log("⚠ Could not find svgassetSvgAssetSchema to add superRefine validation");
}
-const speedPattern =
- /speed: z\.optional\(z\.number\(\)\.gte\(0\)\.lte\(10\)\)/g;
-const speedReplacement = `speed: z.optional(z.preprocess((val) => typeof val === 'string' && val !== '' && !isNaN(Number(val)) ? Number(val) : val, z.number().gte(0).lte(10)))`;
+// Coercion function that converts strings to numbers inside preprocess (doesn't rely on z.coerce)
+const coerceNumber = `((v: unknown) => { if (v === '' || v === null || v === undefined || Array.isArray(v)) return undefined; if (typeof v === 'string') return Number(v); return v; })`;
+
+const plainNumberPattern = /z\.number\(\)(?!\.)/g;
+const plainNumberCount = (content.match(plainNumberPattern) || []).length;
+if (plainNumberCount > 0) {
+ content = content.replace(
+ plainNumberPattern,
+ `z.preprocess(${coerceNumber}, z.number())`
+ );
+ console.log(
+ `✓ Added number coercion for plain z.number() (${plainNumberCount} occurrences)`
+ );
+}
-const speedCount = (content.match(speedPattern) || []).length;
-if (speedCount > 0) {
- content = content.replace(speedPattern, speedReplacement);
- console.log(`✓ Added coercion for speed (${speedCount} occurrences)`);
+const chainedNumberPattern = /z\.number\(\)((?:\.[a-zA-Z]+\([^)]*\))+)/g;
+let chainedCount = 0;
+content = content.replace(chainedNumberPattern, (match, chain) => {
+ chainedCount++;
+ return `z.preprocess(${coerceNumber}, z.number()${chain})`;
+});
+if (chainedCount > 0) {
+ console.log(
+ `✓ Added number coercion for chained z.number() (${chainedCount} occurrences)`
+ );
}
-const scalePattern = /scale: z\.optional\(z\.number\(\)\)/g;
-const scaleReplacement = `scale: z.optional(z.preprocess((val) => typeof val === 'string' && val !== '' && !isNaN(Number(val)) ? Number(val) : val, z.number()))`;
+const plainIntPattern = /z\.int\(\)(?!\.)/g;
+const plainIntCount = (content.match(plainIntPattern) || []).length;
+if (plainIntCount > 0) {
+ content = content.replace(
+ plainIntPattern,
+ `z.preprocess(${coerceNumber}, z.number().int())`
+ );
+ console.log(
+ `✓ Added number coercion for plain z.int() (${plainIntCount} occurrences)`
+ );
+}
-const scaleCount = (content.match(scalePattern) || []).length;
-if (scaleCount > 0) {
- content = content.replace(scalePattern, scaleReplacement);
- console.log(`✓ Added coercion for scale (${scaleCount} occurrences)`);
+const chainedIntPattern = /z\.int\(\)((?:\.[a-zA-Z]+\([^)]*\))+)/g;
+let chainedIntCount = 0;
+content = content.replace(chainedIntPattern, (match, chain) => {
+ chainedIntCount++;
+ return `z.preprocess(${coerceNumber}, z.number().int()${chain})`;
+});
+if (chainedIntCount > 0) {
+ console.log(
+ `✓ Added number coercion for chained z.int() (${chainedIntCount} occurrences)`
+ );
}
fs.writeFileSync(zodGenPath, content);
@@ -114,6 +199,7 @@ if (fs.existsSync(zodGenCjsPath)) {
exports.htmlassetHtmlAssetSchema,
exports.titleassetTitleAssetSchema,
exports.shapeassetShapeAssetSchema,
+ exports.svgassetSvgAssetSchema,
exports.texttoimageassetTextToImageAssetSchema,
exports.imagetovideoassetImageToVideoAssetSchema,
]);`;
@@ -123,50 +209,129 @@ if (fs.existsSync(zodGenCjsPath)) {
console.log("✓ Fixed assetAssetSchema discriminator in CJS");
}
- const cjsCoercionPatterns = [
- {
- pattern:
- /start: zod_1\.z\.union\(\[zod_1\.z\.number\(\), zod_1\.z\.enum\(\["auto"\]\)\]\)/g,
- replacement: `start: zod_1.z.union([zod_1.z.preprocess((val) => typeof val === 'string' && val !== '' && !isNaN(Number(val)) ? Number(val) : val, zod_1.z.number()), zod_1.z.enum(["auto"])])`,
- name: "clip start",
- },
- {
- pattern:
- /length: zod_1\.z\.union\(\[zod_1\.z\.number\(\), zod_1\.z\.literal\("auto"\), zod_1\.z\.literal\("end"\)\]\)/g,
- replacement: `length: zod_1.z.union([zod_1.z.preprocess((val) => typeof val === 'string' && val !== '' && !isNaN(Number(val)) ? Number(val) : val, zod_1.z.number()), zod_1.z.literal("auto"), zod_1.z.literal("end")])`,
- name: "clip length",
- },
- {
- pattern: /trim: zod_1\.z\.optional\(zod_1\.z\.number\(\)\)/g,
- replacement: `trim: zod_1.z.optional(zod_1.z.preprocess((val) => typeof val === 'string' && val !== '' && !isNaN(Number(val)) ? Number(val) : val, zod_1.z.number()))`,
- name: "trim",
- },
- {
- pattern:
- /volume: zod_1\.z\.optional\(zod_1\.z\.number\(\)\.gte\(0\)\.lte\(1\)\)/g,
- replacement: `volume: zod_1.z.optional(zod_1.z.preprocess((val) => typeof val === 'string' && val !== '' && !isNaN(Number(val)) ? Number(val) : val, zod_1.z.number().gte(0).lte(1)))`,
- name: "volume",
- },
- {
- pattern:
- /speed: zod_1\.z\.optional\(zod_1\.z\.number\(\)\.gte\(0\)\.lte\(10\)\)/g,
- replacement: `speed: zod_1.z.optional(zod_1.z.preprocess((val) => typeof val === 'string' && val !== '' && !isNaN(Number(val)) ? Number(val) : val, zod_1.z.number().gte(0).lte(10)))`,
- name: "speed",
- },
- {
- pattern: /scale: zod_1\.z\.optional\(zod_1\.z\.number\(\)\)/g,
- replacement: `scale: zod_1.z.optional(zod_1.z.preprocess((val) => typeof val === 'string' && val !== '' && !isNaN(Number(val)) ? Number(val) : val, zod_1.z.number()))`,
- name: "scale",
- },
- ];
-
- for (const { pattern, replacement, name } of cjsCoercionPatterns) {
- const count = (cjsContent.match(pattern) || []).length;
- if (count > 0) {
- cjsContent = cjsContent.replace(pattern, replacement);
- console.log(`✓ Added coercion for ${name} in CJS (${count} occurrences)`);
+ const cjsSvgShapeUnionPattern =
+ /exports\.svgshapesSvgShapeSchema = zod_1\.z\.union\(\[[\s\S]*?\]\);/;
+
+ const newCjsSvgShapeSchema = `exports.svgshapesSvgShapeSchema = zod_1.z.discriminatedUnion("type", [
+ exports.svgshapesSvgRectangleShapeSchema,
+ exports.svgshapesSvgCircleShapeSchema,
+ exports.svgshapesSvgEllipseShapeSchema,
+ exports.svgshapesSvgLineShapeSchema,
+ exports.svgshapesSvgPolygonShapeSchema,
+ exports.svgshapesSvgStarShapeSchema,
+ exports.svgshapesSvgArrowShapeSchema,
+ exports.svgshapesSvgHeartShapeSchema,
+ exports.svgshapesSvgCrossShapeSchema,
+ exports.svgshapesSvgRingShapeSchema,
+ exports.svgshapesSvgPathShapeSchema,
+]);`;
+
+ if (cjsSvgShapeUnionPattern.test(cjsContent)) {
+ cjsContent = cjsContent.replace(
+ cjsSvgShapeUnionPattern,
+ newCjsSvgShapeSchema
+ );
+ console.log("✓ Fixed svgshapesSvgShapeSchema discriminator in CJS");
+ }
+
+ const cjsSvgFillUnionPattern =
+ /exports\.svgpropertiesSvgFillSchema = zod_1\.z\.union\(\[[\s\S]*?\]\);/;
+
+ const newCjsSvgFillSchema = `exports.svgpropertiesSvgFillSchema = zod_1.z.discriminatedUnion("type", [
+ exports.svgpropertiesSvgSolidFillSchema,
+ exports.svgpropertiesSvgLinearGradientFillSchema,
+ exports.svgpropertiesSvgRadialGradientFillSchema,
+]);`;
+
+ if (cjsSvgFillUnionPattern.test(cjsContent)) {
+ cjsContent = cjsContent.replace(
+ cjsSvgFillUnionPattern,
+ newCjsSvgFillSchema
+ );
+ console.log("✓ Fixed svgpropertiesSvgFillSchema discriminator in CJS");
+ }
+
+ const cjsSvgAssetPattern =
+ /exports\.svgassetSvgAssetSchema = zod_1\.z\.object\(\{[\s\S]*?\}\);/;
+
+ const cjsSvgAssetSuperRefine = `exports.svgassetSvgAssetSchema = zod_1.z.object({
+ type: zod_1.z.enum(["svg"]),
+ src: zod_1.z.optional(zod_1.z.string().min(1).max(500000)),
+ shape: zod_1.z.optional(exports.svgshapesSvgShapeSchema),
+ fill: zod_1.z.optional(exports.svgpropertiesSvgFillSchema),
+ stroke: zod_1.z.optional(exports.svgpropertiesSvgStrokeSchema),
+ shadow: zod_1.z.optional(exports.svgpropertiesSvgShadowSchema),
+ transform: zod_1.z.optional(exports.svgpropertiesSvgTransformSchema),
+ opacity: zod_1.z.optional(zod_1.z.preprocess(((v) => { if (v === '' || v === null || v === undefined || Array.isArray(v)) return undefined; if (typeof v === 'string') return Number(v); return v; }), zod_1.z.number().gte(0).lte(1))).default(1),
+ width: zod_1.z.optional(zod_1.z.preprocess(((v) => { if (v === '' || v === null || v === undefined || Array.isArray(v)) return undefined; if (typeof v === 'string') return Number(v); return v; }), zod_1.z.number().int().gte(1).lte(4096))),
+ height: zod_1.z.optional(zod_1.z.preprocess(((v) => { if (v === '' || v === null || v === undefined || Array.isArray(v)) return undefined; if (typeof v === 'string') return Number(v); return v; }), zod_1.z.number().int().gte(1).lte(4096))),
+}).superRefine((data, ctx) => {
+ const hasShape = data.shape !== undefined;
+ const hasSrc = data.src !== undefined && data.src.trim() !== "";
+
+ if (!hasShape && !hasSrc) {
+ ctx.addIssue({
+ code: zod_1.z.ZodIssueCode.custom,
+ message: "Either 'src' or 'shape' must be provided",
+ path: [],
+ });
+ }
+
+ if (hasShape && hasSrc) {
+ ctx.addIssue({
+ code: zod_1.z.ZodIssueCode.custom,
+ message: "Provide either 'src' or 'shape', not both",
+ path: ["src"],
+ });
+ }
+
+ if (hasSrc) {
+ const disallowedProps = ["shape", "fill", "stroke", "shadow", "transform", "width", "height"];
+ for (const prop of disallowedProps) {
+ if (data[prop] !== undefined) {
+ ctx.addIssue({
+ code: zod_1.z.ZodIssueCode.custom,
+ message: "'" + prop + "' is not allowed when using 'src'. Only 'type' and 'src' are allowed in import mode",
+ path: [prop],
+ });
+ }
}
}
+});`;
+
+ if (cjsSvgAssetPattern.test(cjsContent)) {
+ cjsContent = cjsContent.replace(cjsSvgAssetPattern, cjsSvgAssetSuperRefine);
+ console.log("✓ Added superRefine validation to svgassetSvgAssetSchema in CJS");
+ }
+
+ // Coercion function for CJS (without TypeScript type annotation)
+ const cjsCoerceNumber = `((v) => { if (v === '' || v === null || v === undefined || Array.isArray(v)) return undefined; if (typeof v === 'string') return Number(v); return v; })`;
+
+ const cjsPlainNumberPattern = /zod_1\.z\.number\(\)(?!\.)/g;
+ const cjsPlainNumberCount = (cjsContent.match(cjsPlainNumberPattern) || [])
+ .length;
+ if (cjsPlainNumberCount > 0) {
+ cjsContent = cjsContent.replace(
+ cjsPlainNumberPattern,
+ `zod_1.z.preprocess(${cjsCoerceNumber}, zod_1.z.number())`
+ );
+ console.log(
+ `✓ Added number coercion in CJS (${cjsPlainNumberCount} occurrences)`
+ );
+ }
+
+ const cjsChainedNumberPattern =
+ /zod_1\.z\.number\(\)((?:\.[a-zA-Z]+\([^)]*\))+)/g;
+ let cjsChainedCount = 0;
+ cjsContent = cjsContent.replace(cjsChainedNumberPattern, (match, chain) => {
+ cjsChainedCount++;
+ return `zod_1.z.preprocess(${cjsCoerceNumber}, zod_1.z.number()${chain})`;
+ });
+ if (cjsChainedCount > 0) {
+ console.log(
+ `✓ Added number coercion chains in CJS (${cjsChainedCount} occurrences)`
+ );
+ }
fs.writeFileSync(zodGenCjsPath, cjsContent);
}
@@ -183,31 +348,47 @@ if (fs.existsSync(zodGenJsPath)) {
console.log("✓ Fixed assetAssetSchema discriminator in ESM JS");
}
- const esmCoercionPatterns = [
- {
- pattern: clipStartPattern,
- replacement: clipStartReplacement,
- name: "clip start",
- },
- {
- pattern: clipLengthPattern,
- replacement: clipLengthReplacement,
- name: "clip length",
- },
- { pattern: trimPattern, replacement: trimReplacement, name: "trim" },
- { pattern: volumePattern, replacement: volumeReplacement, name: "volume" },
- { pattern: speedPattern, replacement: speedReplacement, name: "speed" },
- { pattern: scalePattern, replacement: scaleReplacement, name: "scale" },
- ];
-
- for (const { pattern, replacement, name } of esmCoercionPatterns) {
- const count = (jsContent.match(pattern) || []).length;
- if (count > 0) {
- jsContent = jsContent.replace(pattern, replacement);
- console.log(
- `✓ Added coercion for ${name} in ESM JS (${count} occurrences)`
- );
- }
+ if (svgShapeUnionPattern.test(jsContent)) {
+ jsContent = jsContent.replace(svgShapeUnionPattern, newSvgShapeSchema);
+ console.log("✓ Fixed svgshapesSvgShapeSchema discriminator in ESM JS");
+ }
+
+ if (svgFillUnionPattern.test(jsContent)) {
+ jsContent = jsContent.replace(svgFillUnionPattern, newSvgFillSchema);
+ console.log("✓ Fixed svgpropertiesSvgFillSchema discriminator in ESM JS");
+ }
+
+ if (svgAssetPattern.test(jsContent)) {
+ jsContent = jsContent.replace(svgAssetPattern, svgAssetSuperRefine);
+ console.log("✓ Added superRefine validation to svgassetSvgAssetSchema in ESM JS");
+ }
+
+ // Coercion function for ESM JS (without TypeScript type annotation)
+ const esmCoerceNumber = `((v) => { if (v === '' || v === null || v === undefined || Array.isArray(v)) return undefined; if (typeof v === 'string') return Number(v); return v; })`;
+
+ const esmPlainNumberPattern = /z\.number\(\)(?!\.)/g;
+ const esmPlainNumberCount = (jsContent.match(esmPlainNumberPattern) || [])
+ .length;
+ if (esmPlainNumberCount > 0) {
+ jsContent = jsContent.replace(
+ esmPlainNumberPattern,
+ `z.preprocess(${esmCoerceNumber}, z.number())`
+ );
+ console.log(
+ `✓ Added number coercion in ESM JS (${esmPlainNumberCount} occurrences)`
+ );
+ }
+
+ const esmChainedNumberPattern = /z\.number\(\)((?:\.[a-zA-Z]+\([^)]*\))+)/g;
+ let esmChainedCount = 0;
+ jsContent = jsContent.replace(esmChainedNumberPattern, (match, chain) => {
+ esmChainedCount++;
+ return `z.preprocess(${esmCoerceNumber}, z.number()${chain})`;
+ });
+ if (esmChainedCount > 0) {
+ console.log(
+ `✓ Added number coercion chains in ESM JS (${esmChainedCount} occurrences)`
+ );
}
fs.writeFileSync(zodGenJsPath, jsContent);