Repository: fabien0102/ts-to-zod
Pull request #159 (2023-09-20 07:59:09)

fix: Various type extractions
# Why

Various expressions where not taken into account when extracting types, thus leading to missing schema in output generation.

The two latter ones were implemented in https://github.com/fabien0102/ts-to-zod/pull/152 and https://github.com/fabien0102/ts-to-zod/pull/155 which I close to merge all fixes in this PR. 

## Support of TypeScript Helpers

The following helpers were not handled well: `["Array", "Promise",  "Omit", "Pick", "Record", "Partial", "Required"]`
when used in expressions such as 
```ts
export type Person = Partial<Villain>
```

## Add support for ParenthesisTypeNodes
This was added in https://github.com/fabien0102/ts-to-zod/pull/152 (closed) following this comment: https://github.com/fabien0102/ts-to-zod/pull/148#issuecomment-1664231249

## Type identifiers not extracted from type alias declaration
This was added in https://github.com/fabien0102/ts-to-zod/pull/155 (closed)

Extracting types from expression such as 
```ts
export type Person = SuperHero
```
was not working


codecov-commenter: ## [Codecov](https://app.codecov.io/gh/fabien0102/ts-to-zod/pull/159?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fabien+BERNARD) Report
> Merging [#159](https://app.codecov.io/gh/fabien0102/ts-to-zod/pull/159?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fabien+BERNARD) (9ce1c42) into [main](https://app.codecov.io/gh/fabien0102/ts-to-zod/commit/8d3b297e4131ef105bfe155b038e4b4f76a7d5fc?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fabien+BERNARD) (8d3b297) will **increase** coverage by `0.04%`.
> The diff coverage is `100.00%`.

:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

```diff
@@            Coverage Diff             @@
##             main     #159      +/-   ##
==========================================
+ Coverage   97.33%   97.38%   +0.04%     
==========================================
  Files          14       14              
  Lines         676      688      +12     
  Branches      275      278       +3     
==========================================
+ Hits          658      670      +12     
  Misses         18       18              
```


| [Files Changed](https://app.codecov.io/gh/fabien0102/ts-to-zod/pull/159?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fabien+BERNARD) | Coverage Δ | |
|---|---|---|
| [src/utils/traverseTypes.ts](https://app.codecov.io/gh/fabien0102/ts-to-zod/pull/159?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Fabien+BERNARD#diff-c3JjL3V0aWxzL3RyYXZlcnNlVHlwZXMudHM=) | `100.00% <100.00%> (ø)` | |



Patch:
diff --git a/README.md b/README.md
index c70b2bf..56de124 100644
--- a/README.md
+++ b/README.md
@@ -29,20 +29,35 @@ Notes:
 - Only exported types/interface are tested (so you can have some private types/interface and just exports the composed type)
 - Even if this is not recommended, you can skip this validation step with `--skipValidation`. (At your own risk!)
 
-## JSDoc tags Validators
+## JSDoc Tag Validators
 
-This tool supports some JSDoc tags inspired from openapi to generate zod validator.
+This tool supports some JSDoc tags (inspired by OpenAPI) to generate additional Zod schema validators.
 
 List of supported keywords:
 
-| JSDoc keyword                      | JSDoc Example     | Generated Zod validator      |
-| ---------------------------------- | ----------------- | ---------------------------- |
-| `@minimum {number}`                | `@minimum 42`     | `z.number().min(42)`         |
-| `@maximum {number}`                | `@maximum 42`     | `z.number().max(42)`         |
-| `@minLength {number}`              | `@minLength 42`   | `z.string().min(42)`         |
-| `@maxLength {number}`              | `@maxLength 42`   | `z.string().max(42)`         |
-| `@format {"email"\|"uuid"\|"url"}` | `@format email`   | `z.string().email()`         |
-| `@pattern {regex}`                 | `@pattern ^hello` | `z.string().regex(/^hello/)` |
+| JSDoc keyword                                                                                                              | JSDoc Example              | Generated Zod validator              |
+| -------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------ |
+| `@minimum {number} [err_msg]`                                                                                              | `@minimum 42`              | `z.number().min(42)`                 |
+| `@maximum {number} [err_msg]`                                                                                              | `@maximum 42 Must be < 42` | `z.number().max(42, "Must be < 42")` |
+| `@minLength {number} [err_msg]`                                                                                            | `@minLength 42`            | `z.string().min(42)`                 |
+| `@maxLength {number} [err_msg]`                                                                                            | `@maxLength 42`            | `z.string().max(42)`                 |
+| `@format {FormatType} [err_msg]`                                                                                           | `@format email`            | `z.string().email()`                 |
+| `@pattern {regex}` <br><br> **Note**: Due to parsing ambiguities, `@pattern` does _not_ support generating error messages. | `@pattern ^hello`          | `z.string().regex(/^hello/)`         |
+
+By default, `FormatType` is defined as:
+
+```ts
+type FormatType =
+  | "date-time"
+  | "email"
+  | "ip"
+  | "ipv4"
+  | "ipv6"
+  | "url"
+  | "uuid";
+```
+
+However, see the section on [Custom JSDoc Format Types](#custom-jsdoc-format-types) to learn more about defining other types of formats for string validation.
 
 Those validators can be combined:
 
@@ -139,7 +154,7 @@ Other JSDoc tags are available:
 
 ## Advanced configuration
 
-If you want to customized the schema name or restrict the exported schemas, you can do this by adding a `ts-to-zod.config.js` at the root of your project.
+If you want to customize the schema name or restrict the exported schemas, you can do this by adding a `ts-to-zod.config.js` at the root of your project.
 
 Just run `yarn ts-to-zod --init` and you will have a ready to use configuration file (with a bit of typesafety).
 
@@ -188,7 +203,92 @@ export interface Superman {
 }
 ```
 
-/!\ Please note: if your exported interface/type have a reference to a non-exported interface/type, ts-to-zod will not be able to generate anything (missing dependencies will be reported).
+**Please note**: if your exported interface/type have a reference to a non-exported interface/type, ts-to-zod will not be able to generate anything (missing dependencies will be reported).
+
+### Custom JSDoc Format Types
+
+`ts-to-zod` already supports converting several `@format` types such as `email` and `ip` to built-in Zod string validation functions. However, the types supported out of the box are only a subset of those recognized by the OpenAPI specification, which doesn't fit every use case. Thus, you can use the config file to define additional format types using the `customJSDocFormats` property like so:
+
+```ts
+{
+  "customJSDocFormats": {
+    [formatTypeNoSpaces]:
+      | string
+      | {regex: string, errorMessage: string}
+  }
+}
+```
+
+Here is an example configuration:
+
+```json
+{
+  "customJSDocFormats": {
+    "phone-number": "^\\d{3}-\\d{3}-\\d{4}$",
+    "date": {
+      "regex": "^\\d{4}-\\d{2}-\\d{2}$",
+      "errorMessage": "Must be in YYYY-MM-DD format."
+    }
+  }
+}
+```
+
+As a result, `ts-to-zod` will perform the following transformation:
+
+<table>
+<thead>
+<tr>
+
+<th>TypeScript</th>
+<th>Zod</th>
+
+</tr>
+</thead>
+
+<tbody>
+<tr>
+
+<td>
+
+```ts
+interface Info {
+  /**
+   * @format date
+   */
+  birthdate: string;
+  /**
+   * @format phone-number Must be a valid phone number.
+   */
+  phoneNumber: string;
+}
+```
+
+</td>
+
+<td>
+
+```ts
+const infoSchema = z.object({
+  /**
+   * @format date
+   */
+  birthdate: z
+    .string()
+    .regex(/^\d{4}-\d{2}-\d{2}$/, "Must be in YYYY-MM-DD format."),
+  /**
+   * @format phone-number
+   */
+  phoneNumber: z
+    .string()
+    .regex(/^\d{3}-\d{3}-\d{4}$/, "Must be a valid phone number."),
+});
+```
+
+</td>
+
+</tr>
+</tbody>
+</table>
 
 ## Limitation
 
diff --git a/example/heros.ts b/example/heros.ts
index 5cfb90f..585a56a 100644
--- a/example/heros.ts
+++ b/example/heros.ts
@@ -114,7 +114,7 @@ export interface HeroContact {
   /**
    * The phone number of the hero.
    *
-   * @pattern ^([+]?d{1,2}[-s]?|)d{3}[-s]?d{3}[-s]?d{4}$
+   * @pattern ^\d{3}-\d{3}-\d{4}$
    */
   phoneNumber: string;
 
@@ -132,4 +132,11 @@ export interface HeroContact {
    * @maximum 500
    */
   age: number;
+
+  /**
+   * The hero's birthday.
+   *
+   * @format date
+   */
+  birthday: string;
 }
diff --git a/example/heros.types.ts b/example/heros.types.ts
index 0b39fb5..44c8489 100644
--- a/example/heros.types.ts
+++ b/example/heros.types.ts
@@ -34,3 +34,7 @@ export type SupermanName = z.infer<typeof generated.supermanNameSchema>;
 export type SupermanInvinciblePower = z.infer<
   typeof generated.supermanInvinciblePowerSchema
 >;
+
+export type EvilPlan = z.infer<typeof generated.evilPlanSchema>;
+
+export type EvilPlanDetails = z.infer<typeof generated.evilPlanDetailsSchema>;
diff --git a/example/heros.zod.ts b/example/heros.zod.ts
index a5ac122..762ad6b 100644
--- a/example/heros.zod.ts
+++ b/example/heros.zod.ts
@@ -78,9 +78,12 @@ export const getSupermanSkillSchema = z
 export const heroContactSchema = z.object({
   email: z.string().email(),
   name: z.string().min(2).max(50),
-  phoneNumber: z.string().regex(/^([+]?d{1,2}[-s]?|)d{3}[-s]?d{3}[-s]?d{4}$/),
+  phoneNumber: z.string().regex(/^\d{3}-\d{3}-\d{4}$/),
   hasSuperPower: z.boolean().optional().default(true),
   age: z.number().min(0).max(500),
+  birthday: z
+    .string()
+    .regex(/^\d{4}-\d{2}-\d{2}$/, "Must be in YYYY-MM-DD format."),
 });
 
 export const supermanEnemySchema = supermanSchema.shape.enemies.valueSchema;
diff --git a/package.json b/package.json
index 2a9b547..3358ced 100644
--- a/package.json
+++ b/package.json
@@ -57,7 +57,7 @@
     "tslib": "^2.3.1",
     "tsutils": "^3.21.0",
     "typescript": "^4.5.2",
-    "zod": "^3.11.6"
+    "zod": "^3.21.0"
   },
   "devDependencies": {
     "@babel/core": "^7.17.5",
@@ -75,7 +75,7 @@
     "auto-changelog": "^2.2.1",
     "babel-jest": "^27.5.1",
     "codecov": "^3.8.3",
-    "eslint": "^7.22.0",
+    "eslint": "^8.51.0",
     "eslint-config-prettier": "^8.1.0",
     "eslint-plugin-prettier": "^3.3.1",
     "husky": "^5.2.0",
diff --git a/src/cli.ts b/src/cli.ts
index a915462..5658d05 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -1,25 +1,25 @@
 import { Command, flags } from "@oclif/command";
-import { OutputFlags } from "@oclif/parser";
 import { error as oclifError } from "@oclif/errors";
-import { readFile, outputFile, existsSync } from "fs-extra";
-import { join, relative, parse } from "path";
+import { OutputFlags } from "@oclif/parser";
+import { eachSeries } from "async";
+import chokidar from "chokidar";
+import { existsSync, outputFile, readFile } from "fs-extra";
+import inquirer from "inquirer";
+import ora from "ora";
+import { join, parse, relative } from "path";
+import prettier from "prettier";
 import slash from "slash";
 import ts from "typescript";
-import { generate, GenerateProps } from "./core/generate";
-import { TsToZodConfig, Config } from "./config";
+import { Config, TsToZodConfig } from "./config";
 import {
-  tsToZodConfigSchema,
   getSchemaNameSchema,
   nameFilterSchema,
+  tsToZodConfigSchema,
 } from "./config.zod";
+import { GenerateProps, generate } from "./core/generate";
+import { createConfig } from "./createConfig";
 import { getImportPath } from "./utils/getImportPath";
-import ora from "ora";
-import prettier from "prettier";
 import * as worker from "./worker";
-import inquirer from "inquirer";
-import { eachSeries } from "async";
-import { createConfig } from "./createConfig";
-import chokidar from "chokidar";
 
 // Try to load `ts-to-zod.config.js`
 // We are doing this here to be able to infer the `flags` & `usage` in the cli help
diff --git a/src/config.ts b/src/config.ts
index 6c78770..38c47d4 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -18,6 +18,35 @@ export type GetSchemaName = (identifier: string) => string;
 export type NameFilter = (name: string) => boolean;
 export type JSDocTagFilter = (tags: SimplifiedJSDocTag[]) => boolean;
 
+/**
+ * @example
+ *  {
+ *    regex: "^\\d{4}-\\d{2}-\\d{2}$",
+ *    errorMessage: "Must be in YYYY-MM-DD format."
+ *  }
+ */
+export type CustomJSDocFormatTypeAttributes = {
+  regex: string;
+  errorMessage?: string;
+};
+
+export type CustomJSDocFormatType = string;
+
+/**
+ * @example
+ *  {
+ *    "phone-number": "^\\d{3}-\\d{3}-\\d{4}$",
+ *    date: {
+ *      regex: "^\\d{4}-\\d{2}-\\d{2}$",
+ *      errorMessage: "Must be in YYYY-MM-DD format."
+ *    }
+ * }
+ */
+export type CustomJSDocFormatTypes = Record<
+  CustomJSDocFormatType,
+  string | CustomJSDocFormatTypeAttributes
+>;
+
 export type Config = {
   /**
    * Path of the input file (types source)
@@ -66,6 +95,11 @@ export type Config = {
    * Path of z.infer<> types file.
    */
   inferredTypes?: string;
+
+  /**
+   * A record of custom `@format` types with their corresponding regex patterns.
+   */
+  customJSDocFormats?: CustomJSDocFormatTypes;
 };
 
 export type Configs = Array<
diff --git a/src/config.zod.ts b/src/config.zod.ts
index 6910164..54875a4 100644
--- a/src/config.zod.ts
+++ b/src/config.zod.ts
@@ -21,6 +21,15 @@ export const jSDocTagFilterSchema = z
   .args(z.array(simplifiedJSDocTagSchema))
   .returns(z.boolean());
 
+export const customJSDocFormatTypeAttributesSchema = z.object({
+  regex: z.string(),
+  errorMessage: z.string().optional(),
+});
+
+export const customJSDocFormatTypesSchema = z.record(
+  z.union([z.string(), customJSDocFormatTypeAttributesSchema])
+);
+
 export const configSchema = z.object({
   input: z.string(),
   output: z.string(),
@@ -31,6 +40,7 @@ export const configSchema = z.object({
   keepComments: z.boolean().optional().default(false),
   skipParseJSDoc: z.boolean().optional().default(false),
   inferredTypes: z.string().optional(),
+  customJSDocFormats: customJSDocFormatTypesSchema.optional(),
 });
 
 export const configsSchema = z.array(
diff --git a/src/core/generate.ts b/src/core/generate.ts
index 660ae45..99f2655 100644
--- a/src/core/generate.ts
+++ b/src/core/generate.ts
@@ -1,7 +1,7 @@
 import { camel } from "case";
 import { getJsDoc } from "tsutils";
 import ts from "typescript";
-import { JSDocTagFilter, NameFilter } from "../config";
+import { JSDocTagFilter, NameFilter, CustomJSDocFormatTypes } from "../config";
 import { getSimplifiedJsDocTags } from "../utils/getSimplifiedJsDocTags";
 import { resolveModules } from "../utils/resolveModules";
 import {
@@ -52,6 +52,11 @@ export interface GenerateProps {
    * Path of z.infer<> types file.
    */
   inferredTypes?: string;
+
+  /**
+   * Custom JSDoc format types.
+   */
+  customJSDocFormatTypes?: CustomJSDocFormatTypes;
 }
 
 /**
@@ -66,6 +71,7 @@ export function generate({
   getSchemaName = (id) => camel(id) + "Schema",
   keepComments = false,
   skipParseJSDoc = false,
+  customJSDocFormatTypes = {},
 }: GenerateProps) {
   // Create a source file and deal with modules
   const sourceFile = resolveModules(sourceText);
@@ -121,6 +127,7 @@ export function generate({
       varName,
       getDependencyName: getSchemaName,
       skipParseJSDoc,
+      customJSDocFormatTypes,
     });
 
     return { typeName, varName, ...zodSchema };
diff --git a/src/core/generateZodSchema.ts b/src/core/generateZodSchema.ts
index 6c780f8..6eddb0d 100644
--- a/src/core/generateZodSchema.ts
+++ b/src/core/generateZodSchema.ts
@@ -1,13 +1,14 @@
 import { camel, lower } from "case";
 import uniq from "lodash/uniq";
 import * as ts from "typescript";
+import { CustomJSDocFormatTypes } from "../config";
 import { findNode } from "../utils/findNode";
 import { isNotNull } from "../utils/isNotNull";
 import {
-  getJSDocTags,
   JSDocTags,
-  jsDocTagToZodProperties,
   ZodProperty,
+  getJSDocTags,
+  jsDocTagToZodProperties,
 } from "./jsDocTags";
 
 const { factory: f } = ts;
@@ -48,6 +49,11 @@ export interface GenerateZodSchemaProps {
    * @default false
    */
   skipParseJSDoc?: boolean;
+
+  /**
+   * Custom JSDoc format types.
+   */
+  customJSDocFormatTypes: CustomJSDocFormatTypes;
 }
 
 /**
@@ -64,6 +70,7 @@ export function generateZodSchemaVariableStatement({
   zodImportValue = "z",
   getDependencyName = (identifierName) => camel(`${identifierName}Schema`),
   skipParseJSDoc = false,
+  customJSDocFormatTypes,
 }: GenerateZodSchemaProps) {
   let schema:
     | ts.CallExpression
@@ -107,6 +114,7 @@ export function generateZodSchemaVariableStatement({
       getDependencyName,
       schemaExtensionClauses,
       skipParseJSDoc,
+      customJSDocFormatTypes,
     });
 
     if (!skipParseJSDoc) {
@@ -135,6 +143,7 @@ export function generateZodSchemaVariableStatement({
       typeNode: node.type,
       isOptional: false,
       jsDocTags,
+      customJSDocFormatTypes,
       sourceFile,
       dependencies,
       getDependencyName,
@@ -174,6 +183,7 @@ function buildZodProperties({
   dependencies,
   getDependencyName,
   skipParseJSDoc,
+  customJSDocFormatTypes,
 }: {
   members: ts.NodeArray<ts.TypeElement> | ts.PropertySignature[];
   zodImportValue: string;
@@ -181,6 +191,7 @@ function buildZodProperties({
   dependencies: string[];
   getDependencyName: (identifierName: string) => string;
   skipParseJSDoc: boolean;
+  customJSDocFormatTypes: CustomJSDocFormatTypes;
 }) {
   const properties = new Map<
     ts.Identifier | ts.StringLiteral | ts.NumericLiteral,
@@ -209,6 +220,7 @@ function buildZodProperties({
         typeNode: member.type,
         isOptional,
         jsDocTags,
+        customJSDocFormatTypes,
         sourceFile,
         dependencies,
         getDependencyName,
@@ -227,6 +239,7 @@ function buildZodPrimitive({
   isPartial,
   isRequired,
   jsDocTags,
+  customJSDocFormatTypes,
   sourceFile,
   dependencies,
   getDependencyName,
@@ -239,6 +252,7 @@ function buildZodPrimitive({
   isPartial?: boolean;
   isRequired?: boolean;
   jsDocTags: JSDocTags;
+  customJSDocFormatTypes: CustomJSDocFormatTypes;
   sourceFile: ts.SourceFile;
   dependencies: string[];
   getDependencyName: (identifierName: string) => string;
@@ -246,6 +260,7 @@ function buildZodPrimitive({
 }): ts.CallExpression | ts.Identifier | ts.PropertyAccessExpression {
   const zodProperties = jsDocTagToZodProperties(
     jsDocTags,
+    customJSDocFormatTypes,
     isOptional,
     Boolean(isPartial),
     Boolean(isRequired),
@@ -258,6 +273,7 @@ function buildZodPrimitive({
       typeNode: typeNode.type,
       isOptional,
       jsDocTags,
+      customJSDocFormatTypes,
       sourceFile,
       dependencies,
       getDependencyName,
@@ -280,6 +296,7 @@ function buildZodPrimitive({
         dependencies,
         getDependencyName,
         skipParseJSDoc,
+        customJSDocFormatTypes,
       });
     }
 
@@ -291,6 +308,7 @@ function buildZodPrimitive({
         isOptional,
         isNullable,
         jsDocTags,
+        customJSDocFormatTypes,
         sourceFile,
         isPartial: true,
         dependencies,
@@ -307,6 +325,7 @@ function buildZodPrimitive({
         isOptional,
         isNullable,
         jsDocTags,
+        customJSDocFormatTypes,
         sourceFile,
         isRequired: true,
         dependencies,
@@ -323,6 +342,7 @@ function buildZodPrimitive({
         isOptional,
         isNullable,
         jsDocTags,
+        customJSDocFormatTypes,
         sourceFile,
         dependencies,
         getDependencyName,
@@ -345,6 +365,7 @@ function buildZodPrimitive({
             dependencies,
             getDependencyName,
             skipParseJSDoc,
+            customJSDocFormatTypes,
           }),
         ],
         zodProperties
@@ -353,25 +374,52 @@ function buildZodPrimitive({
 
     // Deal with `Record<>` syntax
     if (identifierName === "Record" && typeNode.typeArguments) {
-      if (
-        typeNode.typeArguments.length !== 2 ||
-        typeNode.typeArguments[0].kind !== ts.SyntaxKind.StringKeyword
-      ) {
-        throw new Error(
-          `Record<${typeNode.typeArguments[0].getText(
-            sourceFile
-          )}, …> are not supported (https://github.com/colinhacks/zod/tree/v3#records)`
+      if (typeNode.typeArguments[0].kind === ts.SyntaxKind.StringKeyword) {
+        // Short version (`z.record(zodType)`)
+        return buildZodSchema(
+          z,
+          "record",
+          [
+            buildZodPrimitive({
+              z,
+              typeNode: typeNode.typeArguments[1],
+              isOptional: false,
+              jsDocTags,
+              customJSDocFormatTypes,
+              sourceFile,
+              isPartial: false,
+              dependencies,
+              getDependencyName,
+              skipParseJSDoc,
+            }),
+          ],
+          zodProperties
         );
       }
+
+      // Expanded version (`z.record(zodType, zodType)`)
       return buildZodSchema(
         z,
         "record",
         [
+          buildZodPrimitive({
+            z,
+            typeNode: typeNode.typeArguments[0],
+            isOptional: false,
+            jsDocTags,
+            customJSDocFormatTypes,
+            sourceFile,
+            isPartial: false,
+            dependencies,
+            getDependencyName,
+            skipParseJSDoc,
+          }),
           buildZodPrimitive({
             z,
             typeNode: typeNode.typeArguments[1],
             isOptional: false,
             jsDocTags,
+            customJSDocFormatTypes,
             sourceFile,
             isPartial: false,
             dependencies,
@@ -399,6 +447,7 @@ function buildZodPrimitive({
             typeNode: i,
             isOptional: false,
             jsDocTags,
+            customJSDocFormatTypes,
             sourceFile,
             dependencies,
             getDependencyName,
@@ -420,6 +469,7 @@ function buildZodPrimitive({
             typeNode: i,
             isOptional: false,
             jsDocTags,
+            customJSDocFormatTypes,
             sourceFile,
             dependencies,
             getDependencyName,
@@ -480,6 +530,7 @@ function buildZodPrimitive({
             dependencies,
             getDependencyName,
             skipParseJSDoc,
+            customJSDocFormatTypes,
           }),
           f.createIdentifier(lower(identifierName))
         ),
@@ -516,6 +567,7 @@ function buildZodPrimitive({
         isOptional,
         isNullable: hasNull,
         jsDocTags,
+        customJSDocFormatTypes,
         sourceFile,
         dependencies,
         getDependencyName,
@@ -534,6 +586,7 @@ function buildZodPrimitive({
         dependencies,
         getDependencyName,
         skipParseJSDoc,
+        customJSDocFormatTypes,
       })
     );
 
@@ -563,6 +616,7 @@ function buildZodPrimitive({
         dependencies,
         getDependencyName,
         skipParseJSDoc,
+        customJSDocFormatTypes,
       })
     );
     return buildZodSchema(
@@ -650,6 +704,7 @@ function buildZodPrimitive({
           dependencies,
           getDependencyName,
           skipParseJSDoc,
+          customJSDocFormatTypes,
         }),
       ],
       zodProperties
@@ -665,6 +720,7 @@ function buildZodPrimitive({
         dependencies,
         getDependencyName,
         skipParseJSDoc,
+        customJSDocFormatTypes,
       }),
       zodProperties
     );
@@ -681,6 +737,7 @@ function buildZodPrimitive({
       dependencies,
       getDependencyName,
       skipParseJSDoc,
+      customJSDocFormatTypes,
     });
 
     return rest.reduce(
@@ -701,6 +758,7 @@ function buildZodPrimitive({
               dependencies,
               getDependencyName,
               skipParseJSDoc,
+              customJSDocFormatTypes,
             }),
           ]
         ),
@@ -731,6 +789,7 @@ function buildZodPrimitive({
               typeNode:
                 p.type || f.createKeywordTypeNode(ts.SyntaxKind.AnyKeyword),
               jsDocTags,
+              customJSDocFormatTypes,
               sourceFile,
               dependencies,
               getDependencyName,
@@ -746,6 +805,7 @@ function buildZodPrimitive({
               z,
               typeNode: typeNode.type,
               jsDocTags,
+              customJSDocFormatTypes,
               sourceFile,
               dependencies,
               getDependencyName,
@@ -888,6 +948,7 @@ function buildZodObject({
   getDependencyName,
   schemaExtensionClauses,
   skipParseJSDoc,
+  customJSDocFormatTypes,
 }: {
   typeNode: ts.TypeLiteralNode | ts.InterfaceDeclaration;
   z: string;
@@ -896,6 +957,7 @@ function buildZodObject({
   getDependencyName: Required<GenerateZodSchemaProps>["getDependencyName"];
   schemaExtensionClauses?: string[];
   skipParseJSDoc: boolean;
+  customJSDocFormatTypes: CustomJSDocFormatTypes;
 }) {
   const { properties, indexSignature } = typeNode.members.reduce<{
     properties: ts.PropertySignature[];
@@ -930,6 +992,7 @@ function buildZodObject({
           dependencies,
           getDependencyName,
           skipParseJSDoc,
+          customJSDocFormatTypes,
         })
       : new Map();
 
@@ -975,6 +1038,7 @@ function buildZodObject({
         dependencies,
         getDependencyName,
         skipParseJSDoc,
+        customJSDocFormatTypes,
       }),
     ]);
 
diff --git a/src/core/jsDocTags.ts b/src/core/jsDocTags.ts
index 1ab3117..a0d9c71 100644
--- a/src/core/jsDocTags.ts
+++ b/src/core/jsDocTags.ts
@@ -1,18 +1,49 @@
-import ts from "typescript";
 import { getJsDoc } from "tsutils";
+import ts from "typescript";
+import type { ZodString } from "zod";
+import { CustomJSDocFormatType, CustomJSDocFormatTypes } from "../config";
 const { factory: f } = ts;
 
 /**
  * List of formats that can be translated in zod functions.
  */
-const formats = [
-  "email" as const,
-  "uuid" as const,
-  // "uri" as const,
-  "url" as const,
-  // "date" as const,
-  // "date-time" as const,
-];
+const builtInJSDocFormatsTypes = [
+  "date-time",
+  "email",
+  "ip",
+  "ipv4",
+  "ipv6",
+  "url",
+  "uuid",
+  // "uri",
+  // "date",
+] as const;
+
+type BuiltInJSDocFormatsType = typeof builtInJSDocFormatsTypes[number];
+
+/**
+ * Type guard to filter supported JSDoc format tag values (built-in).
+ *
+ * @param formatType
+ */
+function isBuiltInFormatType(
+  formatType = ""
+): formatType is BuiltInJSDocFormatsType {
+  return builtInJSDocFormatsTypes.map(String).includes(formatType);
+}
+
+/**
+ * Type guard to filter supported JSDoc format tag values (custom).
+ *
+ * @param formatType
+ * @param customFormatTypes
+ */
+function isCustomFormatType(
+  formatType = "",
+  customFormatTypes: Array<keyof CustomJSDocFormatTypes>
+): formatType is CustomJSDocFormatType {
+  return customFormatTypes.includes(formatType);
+}
 
 type TagWithError<T> = {
   value: T;
@@ -28,38 +59,32 @@ export interface JSDocTags {
   default?: number | string | boolean;
   minLength?: TagWithError<number>;
   maxLength?: TagWithError<number>;
-  format?: TagWithError<typeof formats[-1]>;
+  format?: TagWithError<BuiltInJSDocFormatsType | CustomJSDocFormatType>;
+  /**
+   * Due to parsing ambiguities, `@pattern`
+   * does not support custom error messages.
+   */
   pattern?: string;
   strict?: boolean;
 }
 
+const jsDocTagKeys: Array<keyof JSDocTags> = [
+  "minimum",
+  "maximum",
+  "default",
+  "minLength",
+  "maxLength",
+  "format",
+  "pattern",
+];
+
 /**
- * Typeguard to filter supported JSDoc tag key.
+ * Type guard to filter supported JSDoc tag key.
  *
  * @param tagName
  */
 function isJSDocTagKey(tagName: string): tagName is keyof JSDocTags {
-  const keys: Array<keyof JSDocTags> = [
-    "minimum",
-    "maximum",
-    "default",
-    "minLength",
-    "maxLength",
-    "format",
-    "pattern",
-  ];
-  return (keys as string[]).includes(tagName);
-}
-
-/**
- * Typeguard to filter supported JSDoc format tag values.
- *
- * @param format
- */
-function isSupportedFormat(
-  format = ""
-): format is Required<JSDocTags>["format"]["value"] {
-  return (formats as string[]).includes(format);
+  return jsDocTagKeys.map(String).includes(tagName);
 }
 
 /**
@@ -87,6 +112,9 @@ function parseJsDocComment(
 /**
  * Return parsed JSTags.
  *
+ * This function depends on `customJSDocFormatTypeContext`. Before
+ * calling it, make sure the context has been supplied the expected value.
+ *
  * @param nodeType
  * @param sourceFile
  * @returns Tags list
@@ -123,9 +151,7 @@ export function getJSDocTags(nodeType: ts.Node, sourceFile: ts.SourceFile) {
             }
             break;
           case "format":
-            if (isSupportedFormat(value)) {
-              jsDocTags[tagName] = { value, errorMessage };
-            }
+            jsDocTags[tagName] = { value, errorMessage };
             break;
           case "default":
             if (
@@ -164,9 +190,10 @@ export type ZodProperty = {
 };
 
 /**
- * Convert a set of jsDocTags to zod properties
+ * Convert a set of JSDoc tags to zod properties.
  *
  * @param jsDocTags
+ * @param customJSDocFormats
  * @param isOptional
  * @param isPartial
  * @param isRequired
@@ -174,6 +201,7 @@ export type ZodProperty = {
  */
 export function jsDocTagToZodProperties(
   jsDocTags: JSDocTags,
+  customJSDocFormats: CustomJSDocFormatTypes,
   isOptional: boolean,
   isPartial: boolean,
   isRequired: boolean,
@@ -216,19 +244,20 @@ export function jsDocTagToZodProperties(
       ),
     });
   }
-  if (jsDocTags.format) {
-    zodProperties.push({
-      identifier: jsDocTags.format.value,
-      expressions: jsDocTags.format.errorMessage
-        ? [f.createStringLiteral(jsDocTags.format.errorMessage)]
-        : undefined,
-    });
+  if (
+    jsDocTags.format &&
+    (isBuiltInFormatType(jsDocTags.format.value) ||
+      isCustomFormatType(
+        jsDocTags.format.value,
+        Object.keys(customJSDocFormats)
+      ))
+  ) {
+    zodProperties.push(
+      formatToZodProperty(jsDocTags.format, customJSDocFormats)
+    );
   }
   if (jsDocTags.pattern) {
-    zodProperties.push({
-      identifier: "regex",
-      expressions: [f.createRegularExpressionLiteral(`/${jsDocTags.pattern}/`)],
-    });
+    zodProperties.push(createZodRegexProperty(jsDocTags.pattern));
   }
   // strict() must be before optional() and nullable()
   if (jsDocTags.strict) {
@@ -271,6 +300,124 @@ export function jsDocTagToZodProperties(
   return zodProperties;
 }
 
+/**
+ * Converts the given JSDoc format to the corresponding Zod
+ * string validation function call represented by a {@link ZodProperty}.
+ *
+ * @param format The format to be converted.
+ * @returns A ZodProperty representing a Zod string validation function call.
+ */
+function formatToZodProperty(
+  format: Required<JSDocTags>["format"],
+  customFormatTypes: CustomJSDocFormatTypes
+): ZodProperty {
+  if (isCustomFormatType(format.value, Object.keys(customFormatTypes))) {
+    const rule = customFormatTypes[format.value];
+    const regex = typeof rule === "string" ? rule : rule.regex;
+    const errorMessage =
+      typeof rule === "string" ? undefined : rule.errorMessage;
+
+    return createZodRegexProperty(regex, format.errorMessage ?? errorMessage);
+  }
+
+  const identifier = builtInFormatTypeToZodPropertyIdentifier(format.value);
+  const expressions = builtInFormatTypeToZodPropertyArguments(
+    format.value,
+    format.errorMessage
+  );
+  return { identifier, expressions };
+}
+
+/**
+ * Maps the given JSDoc format type to its corresponding
+ * Zod string validation function name.
+ *
+ * @param formatType The format type to be converted.
+ * @returns The name of a Zod string validation function.
+ */
+function builtInFormatTypeToZodPropertyIdentifier(
+  formatType: BuiltInJSDocFormatsType
+): keyof ZodString {
+  switch (formatType) {
+    case "date-time":
+      return "datetime";
+    case "ipv4":
+    case "ipv6":
+    case "ip":
+      return "ip";
+    default:
+      return formatType as keyof ZodString;
+  }
+}
+
+/**
+ * Maps the given JSDoc format type and error message to the
+ * expected Zod string validation function arguments.
+ *
+ * @param formatType The format type to be converted.
+ * @param errorMessage The error message to display if validation fails.
+ * @returns A list of expressions which represent function arguments.
+ */
+function builtInFormatTypeToZodPropertyArguments(
+  formatType: BuiltInJSDocFormatsType,
+  errorMessage?: string
+): ts.Expression[] | undefined {
+  switch (formatType) {
+    case "ipv4":
+      return createZodStringIpArgs("v4", errorMessage);
+    case "ipv6":
+      return createZodStringIpArgs("v6", errorMessage);
+    default:
+      return errorMessage ? [f.createStringLiteral(errorMessage)] : undefined;
+  }
+}
+
+/**
+ * Constructs a list of expressions which represent the arguments
+ * for `ip()` string validation function.
+ *
+ * @param version The IP version to use.
+ * @param errorMessage The error message to display if validation fails.
+ * @returns A list of expressions which represent the function arguments.
+ */
+function createZodStringIpArgs(
+  version: "v4" | "v6",
+  errorMessage?: string
+): ts.Expression[] {
+  const propertyAssignments: ts.ObjectLiteralElementLike[] = [
+    f.createPropertyAssignment("version", f.createStringLiteral(version)),
+  ];
+
+  if (errorMessage) {
+    propertyAssignments.push(
+      f.createPropertyAssignment("message", f.createStringLiteral(errorMessage))
+    );
+  }
+
+  return [f.createObjectLiteralExpression(propertyAssignments)];
+}
+
+/**
+ * Constructs a ZodProperty that represents a call to
+ * `.regex()` with the given regular expression.
+ *
+ * @param regex The regular expression to match.
+ * @param errorMessage The error message to display if validation fails.
+ * @returns A ZodProperty representing a `.regex()` call.
+ */
+function createZodRegexProperty(
+  regex: string,
+  errorMessage?: string
+): ZodProperty {
+  return {
+    identifier: "regex",
+    expressions: withErrorMessage(
+      f.createRegularExpressionLiteral(`/${regex}/`),
+      errorMessage
+    ),
+  };
+}
+
 function withErrorMessage(expression: ts.Expression, errorMessage?: string) {
   if (errorMessage) {
     return [expression, f.createStringLiteral(errorMessage)];
diff --git a/src/utils/traverseTypes.ts b/src/utils/traverseTypes.ts
index edff2f5..3afb4ae 100644
--- a/src/utils/traverseTypes.ts
+++ b/src/utils/traverseTypes.ts
@@ -1,5 +1,15 @@
 import ts from "typescript";
 
+const typeScriptHelper = [
+  "Array",
+  "Promise",
+  "Omit",
+  "Pick",
+  "Record",
+  "Partial",
+  "Required",
+];
+
 export type TypeNode =
   | ts.InterfaceDeclaration
   | ts.TypeAliasDeclaration
@@ -14,55 +24,75 @@ export function isTypeNode(node: ts.Node): node is TypeNode {
 }
 
 export function getExtractedTypeNames(
-  node: TypeNode,
+  node: ts.InterfaceDeclaration | ts.TypeAliasDeclaration | ts.EnumDeclaration,
   sourceFile: ts.SourceFile
 ): string[] {
   const referenceTypeNames = new Set<string>();
+
+  // Adding the node name
   referenceTypeNames.add(node.name.text);
 
-  const heritageClauses = (node as ts.InterfaceDeclaration).heritageClauses;
+  const visitorExtract = (child: ts.Node) => {
+    if (!ts.isPropertySignature(child)) {
+      return;
+    }
+
+    const childNode = child as ts.PropertySignature;
+    if (childNode.type) {
+      handleTypeNode(childNode.type);
+    }
+  };
 
-  if (heritageClauses) {
-    heritageClauses.forEach((clause) => {
-      const extensionTypes = clause.types;
-      extensionTypes.forEach((extensionTypeNode) => {
-        const typeName = extensionTypeNode.expression.getText(sourceFile);
+  const handleTypeNode = (typeNode: ts.Node) => {
+    if (ts.isParenthesizedTypeNode(typeNode)) {
+      typeNode = typeNode.type;
+    }
 
-        referenceTypeNames.add(typeName);
+    if (ts.isTypeReferenceNode(typeNode)) {
+      handleTypeReferenceNode(typeNode);
+    } else if (ts.isArrayTypeNode(typeNode)) {
+      handleTypeNode(typeNode.elementType);
+    } else if (ts.isTypeLiteralNode(typeNode)) {
+      typeNode.forEachChild(visitorExtract);
+    } else if (
+      ts.isIntersectionTypeNode(typeNode) ||
+      ts.isUnionTypeNode(typeNode)
+    ) {
+      typeNode.types.forEach((childNode: ts.TypeNode) => {
+        if (ts.isTypeReferenceNode(childNode)) {
+          handleTypeReferenceNode(childNode);
+        } else childNode.forEachChild(visitorExtract);
       });
-    });
-  }
+    }
+  };
 
-  const visitorExtract = (child: ts.Node) => {
-    const childNode = child as ts.PropertySignature;
-    if (!ts.isPropertySignature(childNode)) {
-      return;
+  const handleTypeReferenceNode = (typeRefNode: ts.TypeReferenceNode) => {
+    const typeName = typeRefNode.typeName.getText(sourceFile);
+    if (typeScriptHelper.indexOf(typeName) > -1 && typeRefNode.typeArguments) {
+      typeRefNode.typeArguments.forEach((t) => handleTypeNode(t));
+    } else {
+      referenceTypeNames.add(typeName);
     }
+  };
 
-    if (childNode.type) {
-      if (ts.isTypeReferenceNode(childNode.type)) {
-        referenceTypeNames.add(childNode.type.getText(sourceFile));
-      } else if (
-        ts.isArrayTypeNode(childNode.type) &&
-        ts.isTypeNode(childNode.type.elementType)
-      ) {
-        referenceTypeNames.add(childNode.type.elementType.getText(sourceFile));
-      } else if (ts.isTypeLiteralNode(childNode.type)) {
-        childNode.type.forEachChild(visitorExtract);
-      } else if (
-        ts.isIntersectionTypeNode(childNode.type) ||
-        ts.isUnionTypeNode(childNode.type)
-      ) {
-        childNode.type.types.forEach((typeNode: ts.TypeNode) => {
-          if (ts.isTypeReferenceNode(typeNode)) {
-            referenceTypeNames.add(typeNode.getText(sourceFile));
-          } else typeNode.forEachChild(visitorExtract);
+  if (ts.isInterfaceDeclaration(node)) {
+    const heritageClauses = (node as ts.InterfaceDeclaration).heritageClauses;
+
+    if (heritageClauses) {
+      heritageClauses.forEach((clause) => {
+        const extensionTypes = clause.types;
+        extensionTypes.forEach((extensionTypeNode) => {
+          const typeName = extensionTypeNode.expression.getText(sourceFile);
+
+          referenceTypeNames.add(typeName);
         });
-      }
+      });
     }
-  };
 
-  node.forEachChild(visitorExtract);
+    node.forEachChild(visitorExtract);
+  } else if (ts.isTypeAliasDeclaration(node)) {
+    handleTypeNode(node.type);
+  }
 
   return Array.from(referenceTypeNames);
 }
diff --git a/ts-to-zod.config.js b/ts-to-zod.config.js
index d9b9759..a7a9c58 100644
--- a/ts-to-zod.config.js
+++ b/ts-to-zod.config.js
@@ -9,6 +9,12 @@ module.exports = [
     input: "example/heros.ts",
     output: "example/heros.zod.ts",
     inferredTypes: "example/heros.types.ts",
+    customJSDocFormats: {
+      date: {
+        regex: "^\\d{4}-\\d{2}-\\d{2}$",
+        errorMessage: "Must be in YYYY-MM-DD format.",
+      },
+    },
   },
   { name: "config", input: "src/config.ts", output: "src/config.zod.ts" },
 ];
diff --git a/yarn.lock b/yarn.lock
index c37f764..0ecbfb9 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,6 +2,11 @@
 # yarn lockfile v1
 
 
+"@aashutoshrathi/word-wrap@^1.2.3":
+  version "1.2.6"
+  resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf"
+  integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==
+
 "@ampproject/remapping@^2.1.0":
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.1.2.tgz#4edca94973ded9630d20101cd8559cedb8d8bd34"
@@ -9,13 +14,6 @@
   dependencies:
     "@jridgewell/trace-mapping" "^0.3.0"
 
-"@babel/code-frame@7.12.11":
-  version "7.12.11"
-  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
-  integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==
-  dependencies:
-    "@babel/highlight" "^7.10.4"
-
 "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.16.7":
   version "7.16.7"
   resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.16.7.tgz#44416b6bd7624b998f5b1af5d470856c40138789"
@@ -575,7 +573,7 @@
     "@babel/traverse" "^7.17.0"
     "@babel/types" "^7.17.0"
 
-"@babel/highlight@^7.10.4", "@babel/highlight@^7.12.13":
+"@babel/highlight@^7.12.13":
   version "7.13.10"
   resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.10.tgz#a8b2a66148f5b27d666b15d81774347a731d52d1"
   integrity sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==
@@ -1370,21 +1368,57 @@
   dependencies:
     "@jridgewell/trace-mapping" "0.3.9"
 
-"@eslint/eslintrc@^0.4.0":
-  version "0.4.0"
-  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.0.tgz#99cc0a0584d72f1df38b900fb062ba995f395547"
-  integrity sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==
+"@eslint-community/eslint-utils@^4.2.0":
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
+  integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
+  dependencies:
+    eslint-visitor-keys "^3.3.0"
+
+"@eslint-community/regexpp@^4.6.1":
+  version "4.9.1"
+  resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.9.1.tgz#449dfa81a57a1d755b09aa58d826c1262e4283b4"
+  integrity sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==
+
+"@eslint/eslintrc@^2.1.2":
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.2.tgz#c6936b4b328c64496692f76944e755738be62396"
+  integrity sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==
   dependencies:
     ajv "^6.12.4"
-    debug "^4.1.1"
-    espree "^7.3.0"
-    globals "^12.1.0"
-    ignore "^4.0.6"
+    debug "^4.3.2"
+    espree "^9.6.0"
+    globals "^13.19.0"
+    ignore "^5.2.0"
     import-fresh "^3.2.1"
-    js-yaml "^3.13.1"
-    minimatch "^3.0.4"
+    js-yaml "^4.1.0"
+    minimatch "^3.1.2"
     strip-json-comments "^3.1.1"
 
+"@eslint/js@8.51.0":
+  version "8.51.0"
+  resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.51.0.tgz#6d419c240cfb2b66da37df230f7e7eef801c32fa"
+  integrity sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==
+
+"@humanwhocodes/config-array@^0.11.11":
+  version "0.11.11"
+  resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844"
+  integrity sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==
+  dependencies:
+    "@humanwhocodes/object-schema" "^1.2.1"
+    debug "^4.1.1"
+    minimatch "^3.0.5"
+
+"@humanwhocodes/module-importer@^1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
+  integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
+
+"@humanwhocodes/object-schema@^1.2.1":
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
+  integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
+
 "@istanbuljs/load-nyc-config@^1.0.0":
   version "1.1.0"
   resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
@@ -1615,11 +1649,24 @@
     "@nodelib/fs.stat" "2.0.4"
     run-parallel "^1.1.9"
 
+"@nodelib/fs.scandir@2.1.5":
+  version "2.1.5"
+  resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
+  integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
+  dependencies:
+    "@nodelib/fs.stat" "2.0.5"
+    run-parallel "^1.1.9"
+
 "@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2":
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655"
   integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q==
 
+"@nodelib/fs.stat@2.0.5":
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
+  integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
+
 "@nodelib/fs.walk@^1.2.3":
   version "1.2.6"
   resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063"
@@ -1628,6 +1675,14 @@
     "@nodelib/fs.scandir" "2.1.4"
     fastq "^1.6.0"
 
+"@nodelib/fs.walk@^1.2.8":
+  version "1.2.8"
+  resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
+  integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
+  dependencies:
+    "@nodelib/fs.scandir" "2.1.5"
+    fastq "^1.6.0"
+
 "@oclif/command@^1.8.4":
   version "1.8.4"
   resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.8.4.tgz#176a4c2e9db5eac99dddb5f480ffd577e20e91ed"
@@ -2037,10 +2092,10 @@ acorn-globals@^6.0.0:
     acorn "^7.1.1"
     acorn-walk "^7.1.1"
 
-acorn-jsx@^5.3.1:
-  version "5.3.1"
-  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.1.tgz#fc8661e11b7ac1539c47dbfea2e72b3af34d267b"
-  integrity sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==
+acorn-jsx@^5.3.2:
+  version "5.3.2"
+  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
+  integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
 
 acorn-walk@^7.1.1:
   version "7.2.0"
@@ -2052,7 +2107,7 @@ acorn-walk@^8.1.1:
   resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
   integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
 
-acorn@^7.1.1, acorn@^7.4.0:
+acorn@^7.1.1:
   version "7.4.1"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
   integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
@@ -2067,6 +2122,11 @@ acorn@^8.4.1:
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
   integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
 
+acorn@^8.9.0:
+  version "8.10.0"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
+  integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
+
 agent-base@6:
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
@@ -2074,7 +2134,7 @@ agent-base@6:
   dependencies:
     debug "4"
 
-ajv@^6.10.0, ajv@^6.12.4:
+ajv@^6.12.4:
   version "6.12.6"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
   integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -2084,21 +2144,6 @@ ajv@^6.10.0, ajv@^6.12.4:
     json-schema-traverse "^0.4.1"
     uri-js "^4.2.2"
 
-ajv@^7.0.2:
-  version "7.2.3"
-  resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.2.3.tgz#ca78d1cf458d7d36d1c3fa0794dd143406db5772"
-  integrity sha512-idv5WZvKVXDqKralOImQgPM9v6WOdLNa0IY3B3doOjw/YxRGT8I+allIJ6kd7Uaj+SF1xZUSU+nPM5aDNBVtnw==
-  dependencies:
-    fast-deep-equal "^3.1.1"
-    json-schema-traverse "^1.0.0"
-    require-from-string "^2.0.2"
-    uri-js "^4.2.2"
-
-ansi-colors@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
-  integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==
-
 ansi-escapes@^4.2.1:
   version "4.3.2"
   resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
@@ -2168,6 +2213,11 @@ argparse@^1.0.7:
   dependencies:
     sprintf-js "~1.0.2"
 
+argparse@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
+  integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
 argv@0.0.2:
   version "0.0.2"
   resolved "https://registry.yarnpkg.com/argv/-/argv-0.0.2.tgz#ecbd16f8949b157183711b1bda334f37840185ab"
@@ -2188,11 +2238,6 @@ arrify@^2.0.1:
   resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa"
   integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==
 
-astral-regex@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
-  integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==
-
 async@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
@@ -2680,7 +2725,7 @@ data-urls@^2.0.0:
     whatwg-mimetype "^2.3.0"
     whatwg-url "^8.0.0"
 
-debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
+debug@4, debug@^4.1.0, debug@^4.1.1:
   version "4.3.1"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
   integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
@@ -2694,6 +2739,13 @@ debug@^4.2.0:
   dependencies:
     ms "2.1.2"
 
+debug@^4.3.2:
+  version "4.3.4"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+  integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+  dependencies:
+    ms "2.1.2"
+
 decimal.js@^10.2.1:
   version "10.2.1"
   resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3"
@@ -2806,13 +2858,6 @@ end-of-stream@^1.1.0:
   dependencies:
     once "^1.4.0"
 
-enquirer@^2.3.5:
-  version "2.3.6"
-  resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d"
-  integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==
-  dependencies:
-    ansi-colors "^4.1.1"
-
 error-ex@^1.3.1:
   version "1.3.2"
   resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
@@ -2825,7 +2870,7 @@ escalade@^3.1.1:
   resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
   integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
 
-escape-string-regexp@4.0.0:
+escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
   integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
@@ -2864,7 +2909,7 @@ eslint-plugin-prettier@^3.3.1:
   dependencies:
     prettier-linter-helpers "^1.0.0"
 
-eslint-scope@^5.0.0, eslint-scope@^5.1.1:
+eslint-scope@^5.0.0:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
   integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
@@ -2872,14 +2917,22 @@ eslint-scope@^5.0.0, eslint-scope@^5.1.1:
     esrecurse "^4.3.0"
     estraverse "^4.1.1"
 
-eslint-utils@^2.0.0, eslint-utils@^2.1.0:
+eslint-scope@^7.2.2:
+  version "7.2.2"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f"
+  integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
+  dependencies:
+    esrecurse "^4.3.0"
+    estraverse "^5.2.0"
+
+eslint-utils@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
   integrity sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==
   dependencies:
     eslint-visitor-keys "^1.1.0"
 
-eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
+eslint-visitor-keys@^1.1.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
   integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
@@ -2889,72 +2942,77 @@ eslint-visitor-keys@^2.0.0:
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz#21fdc8fbcd9c795cc0321f0563702095751511a8"
   integrity sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==
 
-eslint@^7.22.0:
-  version "7.22.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.22.0.tgz#07ecc61052fec63661a2cab6bd507127c07adc6f"
-  integrity sha512-3VawOtjSJUQiiqac8MQc+w457iGLfuNGLFn8JmF051tTKbh5/x/0vlcEj8OgDCaw7Ysa2Jn8paGshV7x2abKXg==
-  dependencies:
-    "@babel/code-frame" "7.12.11"
-    "@eslint/eslintrc" "^0.4.0"
-    ajv "^6.10.0"
+eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3:
+  version "3.4.3"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
+  integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
+
+eslint@^8.51.0:
+  version "8.51.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.51.0.tgz#4a82dae60d209ac89a5cff1604fea978ba4950f3"
+  integrity sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==
+  dependencies:
+    "@eslint-community/eslint-utils" "^4.2.0"
+    "@eslint-community/regexpp" "^4.6.1"
+    "@eslint/eslintrc" "^2.1.2"
+    "@eslint/js" "8.51.0"
+    "@humanwhocodes/config-array" "^0.11.11"
+    "@humanwhocodes/module-importer" "^1.0.1"
+    "@nodelib/fs.walk" "^1.2.8"
+    ajv "^6.12.4"
     chalk "^4.0.0"
     cross-spawn "^7.0.2"
-    debug "^4.0.1"
+    debug "^4.3.2"
     doctrine "^3.0.0"
-    enquirer "^2.3.5"
-    eslint-scope "^5.1.1"
-    eslint-utils "^2.1.0"
-    eslint-visitor-keys "^2.0.0"
-    espree "^7.3.1"
-    esquery "^1.4.0"
+    escape-string-regexp "^4.0.0"
+    eslint-scope "^7.2.2"
+    eslint-visitor-keys "^3.4.3"
+    espree "^9.6.1"
+    esquery "^1.4.2"
     esutils "^2.0.2"
+    fast-deep-equal "^3.1.3"
     file-entry-cache "^6.0.1"
-    functional-red-black-tree "^1.0.1"
-    glob-parent "^5.0.0"
-    globals "^13.6.0"
-    ignore "^4.0.6"
-    import-fresh "^3.0.0"
+    find-up "^5.0.0"
+    glob-parent "^6.0.2"
+    globals "^13.19.0"
+    graphemer "^1.4.0"
+    ignore "^5.2.0"
     imurmurhash "^0.1.4"
     is-glob "^4.0.0"
-    js-yaml "^3.13.1"
+    is-path-inside "^3.0.3"
+    js-yaml "^4.1.0"
     json-stable-stringify-without-jsonify "^1.0.1"
     levn "^0.4.1"
-    lodash "^4.17.21"
-    minimatch "^3.0.4"
+    lodash.merge "^4.6.2"
+    minimatch "^3.1.2"
     natural-compare "^1.4.0"
-    optionator "^0.9.1"
-    progress "^2.0.0"
-    regexpp "^3.1.0"
-    semver "^7.2.1"
-    strip-ansi "^6.0.0"
-    strip-json-comments "^3.1.0"
-    table "^6.0.4"
+    optionator "^0.9.3"
+    strip-ansi "^6.0.1"
     text-table "^0.2.0"
-    v8-compile-cache "^2.0.3"
 
 esm@^3.2.25:
   version "3.2.25"
   resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"
   integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==
 
-espree@^7.3.0, espree@^7.3.1:
-  version "7.3.1"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6"
-  integrity sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==
+espree@^9.6.0, espree@^9.6.1:
+  version "9.6.1"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
+  integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
   dependencies:
-    acorn "^7.4.0"
-    acorn-jsx "^5.3.1"
-    eslint-visitor-keys "^1.3.0"
+    acorn "^8.9.0"
+    acorn-jsx "^5.3.2"
+    eslint-visitor-keys "^3.4.1"
 
 esprima@^4.0.0, esprima@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
   integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
 
-esquery@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5"
-  integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==
+esquery@^1.4.2:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b"
+  integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==
   dependencies:
     estraverse "^5.1.0"
 
@@ -3034,7 +3092,7 @@ external-editor@^3.0.3:
     iconv-lite "^0.4.24"
     tmp "^0.0.33"
 
-fast-deep-equal@^3.1.1:
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
   version "3.1.3"
   resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
   integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
@@ -3116,6 +3174,14 @@ find-up@^4.0.0, find-up@^4.1.0:
     locate-path "^5.0.0"
     path-exists "^4.0.0"
 
+find-up@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+  integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+  dependencies:
+    locate-path "^6.0.0"
+    path-exists "^4.0.0"
+
 flat-cache@^3.0.4:
   version "3.0.4"
   resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
@@ -3213,13 +3279,20 @@ get-stream@^6.0.0:
   resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
   integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
 
-glob-parent@^5.0.0, glob-parent@^5.1.0, glob-parent@~5.1.0:
+glob-parent@^5.1.0, glob-parent@~5.1.0:
   version "5.1.2"
   resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
   integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
   dependencies:
     is-glob "^4.0.1"
 
+glob-parent@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
+  integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
+  dependencies:
+    is-glob "^4.0.3"
+
 glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
   version "7.1.6"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
@@ -3237,17 +3310,10 @@ globals@^11.1.0:
   resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
   integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
 
-globals@^12.1.0:
-  version "12.4.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-12.4.0.tgz#a18813576a41b00a24a97e7f815918c2e19925f8"
-  integrity sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==
-  dependencies:
-    type-fest "^0.8.1"
-
-globals@^13.6.0:
-  version "13.7.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-13.7.0.tgz#aed3bcefd80ad3ec0f0be2cf0c895110c0591795"
-  integrity sha512-Aipsz6ZKRxa/xQkZhNg0qIWXT6x6rD46f6x/PCnBomlttdIyAPak4YD9jTmKpZ72uROSMU87qJtcgpgHaVchiA==
+globals@^13.19.0:
+  version "13.23.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-13.23.0.tgz#ef31673c926a0976e1f61dab4dca57e0c0a8af02"
+  integrity sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==
   dependencies:
     type-fest "^0.20.2"
 
@@ -3273,6 +3339,11 @@ graceful-fs@^4.2.9:
   resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96"
   integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==
 
+graphemer@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
+  integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
+
 handlebars@^4.7.3:
   version "4.7.7"
   resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1"
@@ -3370,17 +3441,17 @@ ignore-walk@3.0.4:
   dependencies:
     minimatch "^3.0.4"
 
-ignore@^4.0.6:
-  version "4.0.6"
-  resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
-  integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
-
 ignore@^5.1.4:
   version "5.1.8"
   resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57"
   integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==
 
-import-fresh@^3.0.0, import-fresh@^3.2.1:
+ignore@^5.2.0:
+  version "5.2.4"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
+  integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
+
+import-fresh@^3.2.1:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
   integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
@@ -3497,6 +3568,13 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
   dependencies:
     is-extglob "^2.1.1"
 
+is-glob@^4.0.3:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+  integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+  dependencies:
+    is-extglob "^2.1.1"
+
 is-interactive@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e"
@@ -3512,6 +3590,11 @@ is-observable@^2.1.0:
   resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-2.1.0.tgz#5c8d733a0b201c80dff7bb7c0df58c6a255c7c69"
   integrity sha512-DailKdLb0WU+xX8K5w7VsJhapwHLZ9jjmazqCJq4X12CTgqq73TKnbRcnSLuXYPOoLQgV5IrD7ePiX/h1vnkBw==
 
+is-path-inside@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
+  integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+
 is-potential-custom-element-name@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
@@ -4024,6 +4107,13 @@ js-yaml@3.14.1, js-yaml@^3.13.1:
     argparse "^1.0.7"
     esprima "^4.0.0"
 
+js-yaml@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
+  integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+  dependencies:
+    argparse "^2.0.1"
+
 jsdom@^16.6.0:
   version "16.7.0"
   resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710"
@@ -4077,11 +4167,6 @@ json-schema-traverse@^0.4.1:
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
   integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
 
-json-schema-traverse@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
-  integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
-
 json-stable-stringify-without-jsonify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
@@ -4148,17 +4233,29 @@ locate-path@^5.0.0:
   dependencies:
     p-locate "^4.1.0"
 
+locate-path@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+  integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+  dependencies:
+    p-locate "^5.0.0"
+
 lodash.debounce@^4.0.8:
   version "4.0.8"
   resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
   integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
 
+lodash.merge@^4.6.2:
+  version "4.6.2"
+  resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
+  integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+
 lodash.uniqby@^4.7.0:
   version "4.7.0"
   resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302"
   integrity sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI=
 
-lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
+lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.7.0:
   version "4.17.21"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -4252,6 +4349,13 @@ minimatch@^3.0.4:
   dependencies:
     brace-expansion "^1.1.7"
 
+minimatch@^3.0.5, minimatch@^3.1.2:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+  integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+  dependencies:
+    brace-expansion "^1.1.7"
+
 minimist@^1.2.5:
   version "1.2.5"
   resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
@@ -4388,17 +4492,17 @@ optionator@^0.8.1:
     type-check "~0.3.2"
     word-wrap "~1.2.3"
 
-optionator@^0.9.1:
-  version "0.9.1"
-  resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499"
-  integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==
+optionator@^0.9.3:
+  version "0.9.3"
+  resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64"
+  integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==
   dependencies:
+    "@aashutoshrathi/word-wrap" "^1.2.3"
     deep-is "^0.1.3"
     fast-levenshtein "^2.0.6"
     levn "^0.4.1"
     prelude-ls "^1.2.1"
     type-check "^0.4.0"
-    word-wrap "^1.2.3"
 
 ora@^5.4.0:
   version "5.4.0"
@@ -4442,6 +4546,13 @@ p-limit@^2.2.0:
   dependencies:
     p-try "^2.0.0"
 
+p-limit@^3.0.2:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
+  integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+  dependencies:
+    yocto-queue "^0.1.0"
+
 p-locate@^4.1.0:
   version "4.1.0"
   resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
@@ -4449,6 +4560,13 @@ p-locate@^4.1.0:
   dependencies:
     p-limit "^2.2.0"
 
+p-locate@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+  integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+  dependencies:
+    p-limit "^3.0.2"
+
 p-try@^2.0.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
@@ -4591,11 +4709,6 @@ pretty-quick@^3.1.0:
     mri "^1.1.5"
     multimatch "^4.0.0"
 
-progress@^2.0.0:
-  version "2.0.3"
-  resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
-  integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
-
 prompts@^2.0.1:
   version "2.4.0"
   resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.0.tgz#4aa5de0723a231d1ee9121c40fdf663df73f61d7"
@@ -4677,7 +4790,7 @@ regenerator-transform@^0.14.2:
   dependencies:
     "@babel/runtime" "^7.8.4"
 
-regexpp@^3.0.0, regexpp@^3.1.0:
+regexpp@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.1.0.tgz#206d0ad0a5648cffbdb8ae46438f3dc51c9f78e2"
   integrity sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==
@@ -4711,11 +4824,6 @@ require-directory@^2.1.1:
   resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
   integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
 
-require-from-string@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
-  integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
-
 resolve-cwd@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
@@ -4826,7 +4934,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
 
-semver@^7.2.1, semver@^7.3.2:
+semver@^7.3.2:
   version "7.3.5"
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
   integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
@@ -4865,15 +4973,6 @@ slash@^3.0.0:
   resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
   integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
 
-slice-ansi@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b"
-  integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==
-  dependencies:
-    ansi-styles "^4.0.0"
-    astral-regex "^2.0.0"
-    is-fullwidth-code-point "^3.0.0"
-
 source-map-support@^0.5.6:
   version "0.5.19"
   resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
@@ -4979,7 +5078,7 @@ strip-final-newline@^2.0.0:
   resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
   integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
 
-strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
+strip-json-comments@^3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
   integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
@@ -5028,16 +5127,6 @@ symbol-tree@^3.2.4:
   resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
   integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
 
-table@^6.0.4:
-  version "6.0.7"
-  resolved "https://registry.yarnpkg.com/table/-/table-6.0.7.tgz#e45897ffbcc1bcf9e8a87bf420f2c9e5a7a52a34"
-  integrity sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==
-  dependencies:
-    ajv "^7.0.2"
-    lodash "^4.17.20"
-    slice-ansi "^4.0.0"
-    string-width "^4.2.0"
-
 teeny-request@7.1.1:
   version "7.1.1"
   resolved "https://registry.yarnpkg.com/teeny-request/-/teeny-request-7.1.1.tgz#2b0d156f4a8ad81de44303302ba8d7f1f05e20e6"
@@ -5222,11 +5311,6 @@ type-fest@^0.21.3:
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
   integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
 
-type-fest@^0.8.1:
-  version "0.8.1"
-  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
-  integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
-
 typedarray-to-buffer@^3.1.5:
   version "3.1.5"
   resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
@@ -5306,11 +5390,6 @@ v8-compile-cache-lib@^3.0.1:
   resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
   integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
 
-v8-compile-cache@^2.0.3:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"
-  integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==
-
 v8-to-istanbul@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.0.tgz#0aeb763894f1a0a1676adf8a8b7612a38902446c"
@@ -5415,7 +5494,7 @@ widest-line@^3.1.0:
   dependencies:
     string-width "^4.0.0"
 
-word-wrap@^1.2.3, word-wrap@~1.2.3:
+word-wrap@~1.2.3:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
   integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
@@ -5506,7 +5585,12 @@ yn@3.1.1:
   resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
   integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==
 
-zod@^3.11.6:
-  version "3.11.6"
-  resolved "https://registry.yarnpkg.com/zod/-/zod-3.11.6.tgz#e43a5e0c213ae2e02aefe7cb2b1a6fa3d7f1f483"
-  integrity sha512-daZ80A81I3/9lIydI44motWe6n59kRBfNzTuS2bfzVh1nAXi667TOTWWtatxyG+fwgNUiagSj/CWZwRRbevJIg==
+yocto-queue@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
+  integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
+
+zod@^3.21.0:
+  version "3.21.4"
+  resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db"
+  integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==


Test patch:
diff --git a/src/core/generateZodSchema.test.ts b/src/core/generateZodSchema.test.ts
index b9a9e51..c4965f2 100644
--- a/src/core/generateZodSchema.test.ts
+++ b/src/core/generateZodSchema.test.ts
@@ -1,5 +1,6 @@
 import { camel } from "case";
 import ts from "typescript";
+import type { CustomJSDocFormatTypes } from "../config";
 import { findNode } from "../utils/findNode";
 import { generateZodSchemaVariableStatement } from "./generateZodSchema";
 
@@ -291,13 +292,6 @@ describe("generateZodSchema", () => {
     );
   });
 
-  it("should throw on non string record", () => {
-    const source = `export type UnsupportedType = Record<number, number>;`;
-    expect(() => generate(source)).toThrowErrorMatchingInlineSnapshot(
-      `"Record<number, …> are not supported (https://github.com/colinhacks/zod/tree/v3#records)"`
-    );
-  });
-
   it("should throw on not supported key in omit", () => {
     const source = `export type UnsupportedType = Omit<Superman, Krytonite>;`;
     expect(() => generate(source)).toThrowErrorMatchingInlineSnapshot(
@@ -540,6 +534,16 @@ describe("generateZodSchema", () => {
     );
   });
 
+  it("should deal with record with a union as key", () => {
+    const source = `
+    export type AvailablePower = Record<Power, boolean>;
+    `;
+
+    expect(generate(source)).toMatchInlineSnapshot(
+      `"export const availablePowerSchema = z.record(powerSchema, z.boolean());"`
+    );
+  });
+
   it("should deal with index access type (tuple)", () => {
     const source = `export type SupermanPower = Superman["powers"][1];
 
@@ -696,6 +700,34 @@ describe("generateZodSchema", () => {
        * @maximum 500
        */
       age: number;
+
+      /**
+       * The hero's birthday.
+       *
+       * @format date-time
+       */
+      birthday: string;
+
+      /**
+       * The hero's ipv4 address.
+       *
+       * @format ipv4
+       */
+      ipv4: string;
+
+      /**
+       * The hero's ipv6 address.
+       *
+       * @format ipv6
+       */
+      ipv6: string;
+
+      /**
+       * The hero's ip address.
+       *
+       * @format ip
+       */
+      ip: string;
     }`;
     expect(generate(source)).toMatchInlineSnapshot(`
       "export const heroContactSchema = z.object({
@@ -730,7 +762,31 @@ describe("generateZodSchema", () => {
            * @minimum 0
            * @maximum 500
            */
-          age: z.number().min(0).max(500)
+          age: z.number().min(0).max(500),
+          /**
+           * The hero's birthday.
+           *
+           * @format date-time
+           */
+          birthday: z.string().datetime(),
+          /**
+           * The hero's ipv4 address.
+           *
+           * @format ipv4
+           */
+          ipv4: z.string().ip({ version: \\"v4\\" }),
+          /**
+           * The hero's ipv6 address.
+           *
+           * @format ipv6
+           */
+          ipv6: z.string().ip({ version: \\"v6\\" }),
+          /**
+           * The hero's ip address.
+           *
+           * @format ip
+           */
+          ip: z.string().ip()
       });"
     `);
   });
@@ -757,6 +813,20 @@ describe("generateZodSchema", () => {
        * @format email "Should be an email"
        */
       supermanEmail: string;
+
+      /**
+       * The hero's ipv6 address.
+       *
+       * @format ipv6 Must be an ipv6 address
+       */
+      ipv6: string;
+
+      /**
+       * The hero's ip address.
+       *
+       * @format ip "Must be a ipv4 or an ipv6 address"
+       */
+      ip: string;
     }`;
     expect(generate(source)).toMatchInlineSnapshot(`
       "export const heroContactSchema = z.object({
@@ -777,7 +847,19 @@ describe("generateZodSchema", () => {
            *
            * @format email \\"Should be an email\\"
            */
-          supermanEmail: z.string().email(\\"Should be an email\\")
+          supermanEmail: z.string().email(\\"Should be an email\\"),
+          /**
+           * The hero's ipv6 address.
+           *
+           * @format ipv6 Must be an ipv6 address
+           */
+          ipv6: z.string().ip({ version: \\"v6\\", message: \\"Must be an ipv6 address\\" }),
+          /**
+           * The hero's ip address.
+           *
+           * @format ip \\"Must be a ipv4 or an ipv6 address\\"
+           */
+          ip: z.string().ip(\\"Must be a ipv4 or an ipv6 address\\")
       });"
     `);
   });
@@ -833,6 +915,63 @@ describe("generateZodSchema", () => {
     `);
   });
 
+  it("should generate custom error messages for custom jsdoc format types", () => {
+    const source = `export interface Info {
+      /**
+       * A birthday.
+       *
+       * @format date
+       */
+      birthday: string;
+
+      /**
+       * A phone number.
+       *
+       * @format phone-number
+       */
+      phoneNumber: string;
+
+      /**
+       * A price.
+       *
+       * @format price Must start with "$" and end with cents.
+       */
+      cost: string;
+    }`;
+    expect(
+      generate(source, undefined, undefined, {
+        date: {
+          regex: "^\\d{4}-\\d{2}-\\d{2}$",
+          errorMessage: "Must be in YYYY-MM-DD format.",
+        },
+
+        "phone-number": "^\\d{3}-\\d{3}-\\d{4}$",
+        price: "^\\$\\d+.\\d{2}$",
+      })
+    ).toMatchInlineSnapshot(`
+      "export const infoSchema = z.object({
+          /**
+           * A birthday.
+           *
+           * @format date
+           */
+          birthday: z.string().regex(/^\\\\d{4}-\\\\d{2}-\\\\d{2}$/, \\"Must be in YYYY-MM-DD format.\\"),
+          /**
+           * A phone number.
+           *
+           * @format phone-number
+           */
+          phoneNumber: z.string().regex(/^\\\\d{3}-\\\\d{3}-\\\\d{4}$/),
+          /**
+           * A price.
+           *
+           * @format price Must start with \\"$\\" and end with cents.
+           */
+          cost: z.string().regex(/^\\\\$\\\\d+.\\\\d{2}$/, \\"Must start with \\\\\\"$\\\\\\" and end with cents.\\")
+      });"
+    `);
+  });
+
   it("should generate validator on top-level types", () => {
     const source = `/**
     * @minLength 1
@@ -1023,6 +1162,23 @@ describe("generateZodSchema", () => {
     `);
   });
 
+  it("should handle parenthesis type nodes", () => {
+    const source = `export interface A {
+      a: (number) | string | null;
+      b: (string)
+      c: (number | string)
+    }
+    `;
+
+    expect(generate(source)).toMatchInlineSnapshot(`
+      "export const aSchema = z.object({
+          a: z.union([z.number(), z.string()]).nullable(),
+          b: z.string(),
+          c: z.union([z.number(), z.string()])
+      });"
+    `);
+  });
+
   it("should allow nullable on optional union properties", () => {
     const source = `export interface A {
       a?: number | string | null;
@@ -1193,7 +1349,12 @@ describe("generateZodSchema", () => {
  * @param sourceText Typescript interface or type
  * @returns Generated Zod schema
  */
-function generate(sourceText: string, z?: string, skipParseJSDoc?: boolean) {
+function generate(
+  sourceText: string,
+  z?: string,
+  skipParseJSDoc?: boolean,
+  customJSDocFormatTypes: CustomJSDocFormatTypes = {}
+) {
   const sourceFile = ts.createSourceFile(
     "index.ts",
     sourceText,
@@ -1223,7 +1384,9 @@ function generate(sourceText: string, z?: string, skipParseJSDoc?: boolean) {
     sourceFile,
     varName: zodConstName,
     skipParseJSDoc,
+    customJSDocFormatTypes,
   });
+
   return ts
     .createPrinter({ newLine: ts.NewLineKind.LineFeed })
     .printNode(ts.EmitHint.Unspecified, zodSchema.statement, sourceFile);
diff --git a/src/utils/traverseTypes.test.ts b/src/utils/traverseTypes.test.ts
index 0f791af..565ea3f 100644
--- a/src/utils/traverseTypes.test.ts
+++ b/src/utils/traverseTypes.test.ts
@@ -119,6 +119,140 @@ describe("traverseTypes", () => {
       const result = extractNames(source);
       expect(result).toEqual(["Person", "SuperHero", "Villain"]);
     });
+
+    it("should extract types between parenthesis", () => {
+      const source = `
+        export interface Person {
+            id: number,
+            t: (SuperHero)
+        }`;
+
+      const result = extractNames(source);
+      expect(result).toEqual(["Person", "SuperHero"]);
+    });
+
+    it("should extract union types between parenthesis", () => {
+      const source = `
+        export interface Person {
+            id: number,
+            t: (SuperHero | Villain)
+        }`;
+
+      const result = extractNames(source);
+      expect(result).toEqual(["Person", "SuperHero", "Villain"]);
+    });
+
+    it("should extract type from type alias", () => {
+      const source = `
+        export type Person = SuperHero `;
+
+      const result = extractNames(source);
+      expect(result).toEqual(["Person", "SuperHero"]);
+    });
+
+    it("should extract type from type alias with union", () => {
+      const source = `
+        export type Person = Villain | SuperHero `;
+
+      const result = extractNames(source);
+      expect(result).toEqual(["Person", "Villain", "SuperHero"]);
+    });
+
+    it("should extract type from type alias with array", () => {
+      const source = `
+        export type Person = Villain[] `;
+
+      const result = extractNames(source);
+      expect(result).toEqual(["Person", "Villain"]);
+    });
+
+    it("should extract type from type alias with Array helper", () => {
+      const source = `
+        export type Person = Array<Villain> `;
+
+      const result = extractNames(source);
+      expect(result).toEqual(["Person", "Villain"]);
+    });
+
+    it("should extract type from type alias with Promise helper", () => {
+      const source = `
+        export type Person = Promise<Villain> `;
+
+      const result = extractNames(source);
+      expect(result).toEqual(["Person", "Villain"]);
+    });
+
+    it("should extract type from type alias with Required helper", () => {
+      const source = `
+        export type Person = Required<Villain> `;
+
+      const result = extractNames(source);
+      expect(result).toEqual(["Person", "Villain"]);
+    });
+
+    it("should extract type from type alias with Partial helper", () => {
+      const source = `
+        export type Person = Partial<Villain> `;
+
+      const result = extractNames(source);
+      expect(result).toEqual(["Person", "Villain"]);
+    });
+
+    it("should extract type from type alias with Omit helper", () => {
+      const source = `
+        export type Person = Omit<Villain> `;
+
+      const result = extractNames(source);
+      expect(result).toEqual(["Person", "Villain"]);
+    });
+
+    it("should extract type from type alias with Pick helper", () => {
+      const source = `
+        export type Person = Pick<Villain> `;
+
+      const result = extractNames(source);
+      expect(result).toEqual(["Person", "Villain"]);
+    });
+
+    it("should extract type from type alias with Record helper", () => {
+      const source = `
+        export type Person = Record<string, Villain> `;
+
+      const result = extractNames(source);
+      expect(result).toEqual(["Person", "Villain"]);
+    });
+
+    it("should extract type from type alias with parenthesis", () => {
+      const source = `
+        export type Person = (Villain) `;
+
+      const result = extractNames(source);
+      expect(result).toEqual(["Person", "Villain"]);
+    });
+
+    it("should extract type from type alias with parenthesis", () => {
+      const source = `
+        export type Person = (Villain | Hero)[]`;
+
+      const result = extractNames(source);
+      expect(result).toEqual(["Person", "Villain", "Hero"]);
+    });
+
+    it("should extract type from type alias with object literal", () => {
+      const source = `
+        export type Person = { hero: SuperHero } `;
+
+      const result = extractNames(source);
+      expect(result).toEqual(["Person", "SuperHero"]);
+    });
+
+    it("should extract type from type alias with union & object literal", () => {
+      const source = `
+        export type Person = Villain | { hero: SuperHero } `;
+
+      const result = extractNames(source);
+      expect(result).toEqual(["Person", "Villain", "SuperHero"]);
+    });
   });
 });