Repository: colinhacks/zod
Pull request #4685 (2025-06-12 09:04:08)

chore: remove deprecated @types/chalk
remove deprecated @types/chalk
- <https://www.npmjs.com/package/@types/chalk>


Patch:
diff --git a/package.json b/package.json
index 673a8979..e9068abd 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,6 @@
     "@arethetypeswrong/cli": "^0.17.4",
     "@biomejs/biome": "^1.9.4",
     "@types/benchmark": "^2.1.5",
-    "@types/chalk": "^2.2.4",
     "@types/node": "^20.17.30",
     "@web-std/file": "^3.0.3",
     "arktype": "^2.1.19",
diff --git a/packages/docs/components/ecosystem.tsx b/packages/docs/components/ecosystem.tsx
index 7a89faa2..2df03f2c 100644
--- a/packages/docs/components/ecosystem.tsx
+++ b/packages/docs/components/ecosystem.tsx
@@ -7,7 +7,7 @@ interface ZodResource {
   description: React.ReactNode;
   slug: string;
   stars?: number;
-  v4?: boolean;
+  // v4?: boolean;
   // author?: string;
 }
 
@@ -35,14 +35,12 @@ const apiLibraries: ZodResource[] = [
     url: "https://github.com/RobinTail/express-zod-api",
     description: "Build Express-based API with I/O validation and middlewares, OpenAPI docs and type-safe client.",
     slug: "RobinTail/express-zod-api",
-    v4: true,
   },
   {
     name: "Zod Sockets",
     url: "https://github.com/RobinTail/zod-sockets",
     description: "Socket.IO solution with I/O validation, an AsyncAPI generator, and a type-safe events map.",
     slug: "RobinTail/zod-sockets",
-    v4: true,
   },
 
   // https://github.com/honojs/middleware/tree/main/packages/zod-validator
@@ -105,7 +103,6 @@ const mockingLibraries: ZodResource[] = [
     url: "https://zocker.sigrist.dev",
     description: "Generates valid, semantically meaningful data for your Zod schemas.",
     slug: "LorisSigrist/zocker",
-    v4: true,
   },
 ];
 
@@ -143,7 +140,7 @@ export function Table(props: { resources: ZodResource[] }) {
         <tr>
           <th>Name</th>
           <th>Stars</th>
-          <th>Zod 4 support</th>
+          {/* <th>Zod 4 support</th> */}
           <th>Description</th>
         </tr>
       </thead>
@@ -156,7 +153,7 @@ export function Table(props: { resources: ZodResource[] }) {
               </a>
             </td>
             <td className="whitespace-nowrap">{`⭐️ ${resource.stars ?? "ERR"}`}</td>
-            <td className="whitespace-nowrap">{`⭐️ ${resource.v4 ? "✅" : ""}`}</td>
+            {/* <td className="whitespace-nowrap">{`⭐️ ${resource.v4 ? "✅" : ""}`}</td> */}
             <td>{resource.description}</td>
           </tr>
         ))}
diff --git a/packages/docs/content/api.mdx b/packages/docs/content/api.mdx
index 2b39f76a..13168ad3 100644
--- a/packages/docs/content/api.mdx
+++ b/packages/docs/content/api.mdx
@@ -480,7 +480,7 @@ cidrv6.parse("2001:db8::/32"); // ✅
 To define a template literal schema:
 
 ```ts
-const schema = z.templateLiteral("hello, ", z.string(), "!");
+const schema = z.templateLiteral([ "hello, ", z.string(), "!" ]);
 // `hello, ${string}!`
 ```
 
diff --git a/packages/zod/package.json b/packages/zod/package.json
index 1de8e3bb..a34e0c09 100644
--- a/packages/zod/package.json
+++ b/packages/zod/package.json
@@ -1,6 +1,6 @@
 {
   "name": "zod",
-  "version": "3.25.63",
+  "version": "3.25.64",
   "type": "module",
   "author": "Colin McDonnell <zod@colinhacks.com>",
   "description": "TypeScript-first schema declaration and validation library with static type inference",
diff --git a/packages/zod/src/v4/classic/schemas.ts b/packages/zod/src/v4/classic/schemas.ts
index f97fbc39..ddbb2e4a 100644
--- a/packages/zod/src/v4/classic/schemas.ts
+++ b/packages/zod/src/v4/classic/schemas.ts
@@ -1913,6 +1913,7 @@ export const ZodCustom: core.$constructor<ZodCustom> = /*@__PURE__*/ core.$const
 export function check<O = unknown>(fn: core.CheckFn<O>, params?: string | core.$ZodCustomParams): core.$ZodCheck<O> {
   const ch = new core.$ZodCheck({
     check: "custom",
+
     ...util.normalizeParams(params),
   });
 
diff --git a/packages/zod/src/v4/core/checks.ts b/packages/zod/src/v4/core/checks.ts
index 70556524..bfe179d7 100644
--- a/packages/zod/src/v4/core/checks.ts
+++ b/packages/zod/src/v4/core/checks.ts
@@ -1148,7 +1148,6 @@ export const $ZodCheckMimeType: core.$constructor<$ZodCheckMimeType> = /*@__PURE
         code: "invalid_value",
         values: def.mime,
         input: payload.value.type,
-        path: ["type"],
         inst,
       });
     };
diff --git a/packages/zod/src/v4/core/parse.ts b/packages/zod/src/v4/core/parse.ts
index 1a482452..ec046dfe 100644
--- a/packages/zod/src/v4/core/parse.ts
+++ b/packages/zod/src/v4/core/parse.ts
@@ -21,7 +21,7 @@ export const _parse: (_Err: $ZodErrorClass) => $Parse = (_Err) => (schema, value
   }
   if (result.issues.length) {
     const e = new (_params?.Err ?? _Err)(result.issues.map((iss) => util.finalizeIssue(iss, ctx, core.config())));
-    Error.captureStackTrace(e, _params?.callee);
+    util.captureStackTrace(e, _params?.callee);
     throw e;
   }
   return result.value as core.output<typeof schema>;
@@ -42,7 +42,7 @@ export const _parseAsync: (_Err: $ZodErrorClass) => $ParseAsync = (_Err) => asyn
   if (result instanceof Promise) result = await result;
   if (result.issues.length) {
     const e = new (params?.Err ?? _Err)(result.issues.map((iss) => util.finalizeIssue(iss, ctx, core.config())));
-    Error.captureStackTrace(e, params?.callee);
+    util.captureStackTrace(e, params?.callee);
     throw e;
   }
   return result.value as core.output<typeof schema>;
diff --git a/packages/zod/src/v4/core/schemas.ts b/packages/zod/src/v4/core/schemas.ts
index e3ef9ef3..59379dbe 100644
--- a/packages/zod/src/v4/core/schemas.ts
+++ b/packages/zod/src/v4/core/schemas.ts
@@ -1597,6 +1597,7 @@ export type $strip = {
   out: {};
   in: {};
 };
+
 export type $catchall<T extends SomeType> = {
   out: { [k: string]: core.output<T> };
   in: { [k: string]: core.input<T> };
@@ -3629,7 +3630,7 @@ export interface $ZodCustomDef<O = unknown> extends $ZodTypeDef, checks.$ZodChec
   path?: PropertyKey[] | undefined;
   error?: errors.$ZodErrorMap | undefined;
   params?: Record<string, any> | undefined;
-  fn: (arg: O) => unknown; // checks.$ZodCheck<O>["_zod"]["check"];
+  fn: (arg: O) => unknown;
 }
 
 export interface $ZodCustomInternals<O = unknown, I = unknown>
diff --git a/packages/zod/src/v4/core/util.ts b/packages/zod/src/v4/core/util.ts
index 340268c1..0c6d80d9 100644
--- a/packages/zod/src/v4/core/util.ts
+++ b/packages/zod/src/v4/core/util.ts
@@ -314,12 +314,16 @@ export function esc(str: string): string {
   return JSON.stringify(str);
 }
 
+export const captureStackTrace: typeof Error.captureStackTrace = Error.captureStackTrace
+  ? Error.captureStackTrace
+  : (..._args) => {};
+
 export function isObject(data: any): data is Record<PropertyKey, unknown> {
   return typeof data === "object" && data !== null && !Array.isArray(data);
 }
 
 export const allowsEval: { value: boolean } = cached(() => {
-  if (typeof navigator !== "undefined" && navigator?.userAgent.includes("Cloudflare")) {
+  if (typeof navigator !== "undefined" && navigator?.userAgent?.includes("Cloudflare")) {
     return false;
   }
 
diff --git a/play.ts b/play.ts
index 7cf893bd..fee6c349 100644
--- a/play.ts
+++ b/play.ts
@@ -2,4 +2,42 @@ import { z } from "zod/v4";
 
 z;
 
-console.dir(z.iso.datetime({ local: true }).parse("2025-05-21T12:00"), { depth: null });
+const file = z
+  .file()
+  .max(5_000_000, { message: "File too large (max 5MB)" })
+  .mime(["image/png", "image/jpeg"], { error: "Only PNG and JPEG allowed" })
+  .optional();
+
+// test parsing with invalid mime
+const f = new File([""], "test.txt", {
+  type: "text/plain",
+});
+file.parse(f);
+
+const Story = z.object({
+  type: z.literal("story"),
+  story: z.looseObject({}),
+});
+
+const Collection = z.object({
+  type: z.literal("collection"),
+  get items(): z.ZodArray<z.ZodDiscriminatedUnion<[typeof Story, typeof Collection]>> {
+    return z.array(z.discriminatedUnion("type", [Story, Collection]));
+  },
+});
+
+type Collection = z.infer<typeof Collection>;
+
+const ActivityUnion = z.union([
+  z.object({
+    name: z.string(),
+
+    // now it's `any` though the whole object is exactly the same
+    get subactivities(): z.ZodNullable<z.ZodArray<typeof ActivityUnion>> {
+      return z.nullable(z.array(ActivityUnion));
+    },
+  }),
+  z.string(),
+]);
+
+type A = z.infer<typeof ActivityUnion>;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 757611d4..98543a37 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,9 +17,6 @@ importers:
       '@types/benchmark':
         specifier: ^2.1.5
         version: 2.1.5
-      '@types/chalk':
-        specifier: ^2.2.4
-        version: 2.2.4
       '@types/node':
         specifier: ^20.17.30
         version: 20.17.30
@@ -2609,10 +2606,6 @@ packages:
   '@types/benchmark@2.1.5':
     resolution: {integrity: sha512-cKio2eFB3v7qmKcvIHLUMw/dIx/8bhWPuzpzRT4unCPRTD8VdA9Zb0afxpcxOqR4PixRS7yT42FqGS8BYL8g1w==}
 
-  '@types/chalk@2.2.4':
-    resolution: {integrity: sha512-pb/QoGqtCpH2famSp72qEsXkNzcErlVmiXlQ/ww+5AddD8TmmYS7EWg5T20YiNCAiTgs8pMf2G8SJG5h/ER1ZQ==}
-    deprecated: This is a stub types definition. chalk provides its own type definitions, so you do not need this installed.
-
   '@types/debug@4.1.12':
     resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
 
@@ -7896,10 +7889,6 @@ snapshots:
 
   '@types/benchmark@2.1.5': {}
 
-  '@types/chalk@2.2.4':
-    dependencies:
-      chalk: 5.4.1
-
   '@types/debug@4.1.12':
     dependencies:
       '@types/ms': 2.1.0


Test patch:
diff --git a/packages/zod/src/v4/classic/tests/file.test.ts b/packages/zod/src/v4/classic/tests/file.test.ts
index 10424b2d..1388c7ef 100644
--- a/packages/zod/src/v4/classic/tests/file.test.ts
+++ b/packages/zod/src/v4/classic/tests/file.test.ts
@@ -20,33 +20,72 @@ afterEach(() => {
 });
 
 test("passing validations", () => {
-  minCheck.parse(new File(["12345"], "test.txt"));
-  maxCheck.parse(new File(["12345678"], "test.txt"));
-  mimeCheck.parse(new File([""], "test.csv", { type: "text/plain" }));
+  minCheck.safeParse(new File(["12345"], "test.txt"));
+  maxCheck.safeParse(new File(["12345678"], "test.txt"));
+  mimeCheck.safeParse(new File([""], "test.csv", { type: "text/plain" }));
   expect(() => mimeCheck.parse(new File([""], "test.txt"))).toThrow();
   expect(() => mimeCheck.parse(new File([""], "test.txt", { type: "text/csv" }))).toThrow();
 });
 
 test("failing validations", () => {
-  expect(() => minCheck.parse(new File(["1234"], "test.txt"))).toThrow();
-  expect(() => maxCheck.parse(new File(["123456789"], "test.txt"))).toThrow();
-  expect(() => mimeCheck.parse(new File([""], "test.csv"))).toThrow();
-  expect(() => mimeCheck.parse(new File([""], "test.csv", { type: "text/csv" }))).toThrow();
+  expect(minCheck.safeParse(new File(["1234"], "test.txt"))).toMatchInlineSnapshot(`
+    {
+      "error": [ZodError: [
+      {
+        "origin": "file",
+        "code": "too_small",
+        "minimum": 5,
+        "path": [],
+        "message": "Too small: expected file to have >5 bytes"
+      }
+    ]],
+      "success": false,
+    }
+  `);
+  expect(maxCheck.safeParse(new File(["123456789"], "test.txt"))).toMatchInlineSnapshot(`
+    {
+      "error": [ZodError: [
+      {
+        "origin": "file",
+        "code": "too_big",
+        "maximum": 8,
+        "path": [],
+        "message": "Too big: expected file to have <8 bytes"
+      }
+    ]],
+      "success": false,
+    }
+  `);
+  expect(mimeCheck.safeParse(new File([""], "test.csv"))).toMatchInlineSnapshot(`
+    {
+      "error": [ZodError: [
+      {
+        "code": "invalid_value",
+        "values": [
+          "text/plain",
+          "application/json"
+        ],
+        "path": [],
+        "message": "Invalid option: expected one of \\"text/plain\\"|\\"application/json\\""
+      }
+    ]],
+      "success": false,
+    }
+  `);
+  expect(mimeCheck.safeParse(new File([""], "test.csv", { type: "text/csv" }))).toMatchInlineSnapshot(`
+    {
+      "error": [ZodError: [
+      {
+        "code": "invalid_value",
+        "values": [
+          "text/plain",
+          "application/json"
+        ],
+        "path": [],
+        "message": "Invalid option: expected one of \\"text/plain\\"|\\"application/json\\""
+      }
+    ]],
+      "success": false,
+    }
+  `);
 });
-
-// test("min max getters", () => {
-//   expect(minCheck.minSize).toEqual(5);
-//   expect(minCheck.min(10).minSize).toEqual(10);
-
-//   expect(maxCheck.maxSize).toEqual(8);
-//   expect(maxCheck.max(6).maxSize).toEqual(6);
-// });
-
-// test("accept getter", () => {
-//   expect(mimeCheck.acceptedTypes).toEqual(["text/plain", "application/json"]);
-//   expect(mimeCheck.type(["text/plain", "application/xml"]).acceptedTypes).toEqual(["text/plain"]);
-// });
-
-// test("invalid mime types", () => {
-//   expect(() => z.file().type([".txt"])).toThrow();
-// });