@cerios/openapi-to-zod 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -187,7 +187,7 @@ Examples:
187
187
  | `schemaType` | `"all"` \| `"request"` \| `"response"` | Schema filtering |
188
188
  | `prefix` | `string` | Prefix for schema names |
189
189
  | `suffix` | `string` | Suffix for schema names |
190
- | `stripSchemaPrefix` | `string \| RegExp` | Strip prefix from schema names before generating (e.g., `"Company.Models."` or `/^[A-Z]+\./`) |
190
+ | `stripSchemaPrefix` | `string` | Strip prefix from schema names before generating using glob patterns (e.g., `"Company.Models."` or `"*.Models."`) |
191
191
  | `showStats` | `boolean` | Include generation statistics |
192
192
  | `request` | `object` | Request-specific options (mode, includeDescriptions, useDescribe) |
193
193
  | `response` | `object` | Response-specific options (mode, includeDescriptions, useDescribe) |
@@ -571,10 +571,46 @@ OpenAPI's `nullable: true` is converted to `.nullable()`
571
571
 
572
572
  ### Enums
573
573
 
574
- Enums are generated as Zod enums with:
575
- - Proper string value handling
576
- - Zod schema using `z.enum()`
577
- - TypeScript type inference from the Zod schema
574
+ Enums are generated based on their value types:
575
+
576
+ - **String enums**: `z.enum()` for type-safe string unions
577
+ - **Numeric enums**: `z.union([z.literal(n), ...])` for proper number types
578
+ - **Boolean enums**: `z.boolean()` for true/false values
579
+ - **Mixed enums**: `z.union([z.literal(...), ...])` for heterogeneous values
580
+
581
+ **Examples:**
582
+
583
+ ```yaml
584
+ # String enum
585
+ Status:
586
+ type: string
587
+ enum: [active, inactive, pending]
588
+
589
+ # Integer enum
590
+ Priority:
591
+ type: integer
592
+ enum: [0, 1, 2, 3]
593
+
594
+ # Mixed enum
595
+ Value:
596
+ enum: [0, "none", 1, "some"]
597
+ ```
598
+
599
+ **Generated schemas:**
600
+
601
+ ```typescript
602
+ // String enum → z.enum()
603
+ export const statusSchema = z.enum(["active", "inactive", "pending"]);
604
+ export type Status = z.infer<typeof statusSchema>; // "active" | "inactive" | "pending"
605
+
606
+ // Integer enum → z.union with z.literal
607
+ export const prioritySchema = z.union([z.literal(0), z.literal(1), z.literal(2), z.literal(3)]);
608
+ export type Priority = z.infer<typeof prioritySchema>; // 0 | 1 | 2 | 3
609
+
610
+ // Mixed enum → z.union with z.literal
611
+ export const valueSchema = z.union([z.literal(0), z.literal("none"), z.literal(1), z.literal("some")]);
612
+ export type Value = z.infer<typeof valueSchema>; // 0 | "none" | 1 | "some"
613
+ ```
578
614
 
579
615
  ## Schema Naming
580
616
 
@@ -684,39 +720,39 @@ export default defineConfig({
684
720
  });
685
721
  ```
686
722
 
687
- #### Regex Patterns
723
+ #### Glob Patterns
688
724
 
689
- Use regex patterns to strip dynamic prefixes:
725
+ Use glob patterns to strip dynamic prefixes:
690
726
 
691
727
  ```typescript
692
728
  export default defineConfig({
693
729
  specs: [{
694
730
  input: 'openapi.yaml',
695
731
  output: 'schemas.ts',
696
- // Strip any namespace prefix ending with a dot
697
- stripSchemaPrefix: '^[A-Z][a-z]+\\.'
732
+ // Strip any namespace prefix with wildcard
733
+ stripSchemaPrefix: '*.Models.'
698
734
  }]
699
735
  });
700
736
  ```
701
737
 
702
- **Regex Auto-Detection:**
703
-
704
- Regex patterns are auto-detected if they contain: `^`, `$`, `\\d`, `\\w`, `\\s`, `.*`, `.+`, `[]`, `()`
738
+ **Glob Pattern Syntax:**
705
739
 
706
- ```typescript
707
- // These are all treated as regex patterns:
708
- stripSchemaPrefix: '^Company\\.' // Starts with ^
709
- stripSchemaPrefix: '[A-Z]+\\.' // Contains []
710
- stripSchemaPrefix: '.*\\.Models\\.' // Contains .*
711
-
712
- // This is a literal string:
713
- stripSchemaPrefix: 'Company.Models.' // No regex markers
714
- ```
715
-
716
- For TypeScript configs, you can also use `RegExp` objects:
740
+ Glob patterns support powerful matching using [minimatch](https://github.com/isaacs/minimatch):
741
+ - `*` matches any characters within a single segment (stops at `.`)
742
+ - `**` matches any characters across multiple segments (crosses `.` boundaries)
743
+ - `?` matches a single character
744
+ - `[abc]` matches any character in the set
745
+ - `{a,b}` matches any of the alternatives
746
+ - `!(pattern)` matches anything except the pattern
717
747
 
718
748
  ```typescript
719
- stripSchemaPrefix: /^[A-Z][a-z]+\./
749
+ // Examples of glob patterns:
750
+ stripSchemaPrefix: '*.Models.' // Matches Company.Models., App.Models.
751
+ stripSchemaPrefix: '**.Models.' // Matches any depth: Company.Api.Models., App.V2.Models.
752
+ stripSchemaPrefix: 'Company.{Models,Services}.' // Matches Company.Models. or Company.Services.
753
+ stripSchemaPrefix: 'api_v[0-9]_' // Matches api_v1_, api_v2_, etc.
754
+ stripSchemaPrefix: 'v*.*.' // Matches v1.0., v2.1., etc.
755
+ stripSchemaPrefix: '!(Internal)*.' // Matches any prefix except those starting with Internal
720
756
  ```
721
757
 
722
758
  #### Common Patterns
@@ -730,24 +766,44 @@ stripSchemaPrefix: /^[A-Z][a-z]+\./
730
766
  // Company.Models.Post → Post
731
767
  ```
732
768
 
733
- **Pattern 2: Multiple Namespaces**
769
+ **Pattern 2: Multiple Namespaces with Wildcard**
734
770
  ```typescript
735
771
  {
736
- stripSchemaPrefix: '^[A-Za-z]+\\.Models\\.'
772
+ stripSchemaPrefix: '*.Models.'
737
773
  }
738
774
  // MyApp.Models.User → User
739
775
  // OtherApp.Models.User → User
776
+ // Company.Models.Post → Post
740
777
  ```
741
778
 
742
- **Pattern 3: Version Prefixes**
779
+ **Pattern 3: Multiple Namespace Types**
743
780
  ```typescript
744
781
  {
745
- stripSchemaPrefix: '^v\\d+\\.'
782
+ stripSchemaPrefix: '*.{Models,Services}.'
783
+ }
784
+ // App.Models.User → User
785
+ // App.Services.UserService → UserService
786
+ ```
787
+
788
+ **Pattern 4: Version Prefixes with Character Class**
789
+ ```typescript
790
+ {
791
+ stripSchemaPrefix: 'v[0-9].'
746
792
  }
747
793
  // v1.User → User
748
794
  // v2.Product → Product
749
795
  ```
750
796
 
797
+ **Pattern 5: Versioned Prefixes with Wildcards**
798
+ ```typescript
799
+ {
800
+ stripSchemaPrefix: 'api_v*_'
801
+ }
802
+ // api_v1_User → User
803
+ // api_v2_Product → Product
804
+ // api_v10_Comment → Comment
805
+ ```
806
+
751
807
  #### Interaction with prefix/suffix Options
752
808
 
753
809
  `stripSchemaPrefix` is applied **before** `prefix` and `suffix` options:
package/dist/cli.js CHANGED
@@ -5129,7 +5129,7 @@ function getBatchExitCode(summary) {
5129
5129
  init_cjs_shims();
5130
5130
  var import_node_fs = require("fs");
5131
5131
  var import_node_path = require("path");
5132
- var import_minimatch2 = require("minimatch");
5132
+ var import_minimatch3 = require("minimatch");
5133
5133
  var import_yaml = require("yaml");
5134
5134
 
5135
5135
  // src/generators/enum-generator.ts
@@ -5192,8 +5192,26 @@ function resolveRef(ref) {
5192
5192
  function generateEnum(name, values, options) {
5193
5193
  const schemaName = `${toCamelCase(name, options)}Schema`;
5194
5194
  const typeName = toPascalCase(name);
5195
- const enumValues = values.map((v) => `"${v}"`).join(", ");
5196
- const schemaCode = `export const ${schemaName} = z.enum([${enumValues}]);`;
5195
+ const allBooleans = values.every((v) => typeof v === "boolean");
5196
+ if (allBooleans) {
5197
+ const schemaCode2 = `export const ${schemaName} = z.boolean();`;
5198
+ const typeCode2 = `export type ${typeName} = z.infer<typeof ${schemaName}>;`;
5199
+ return { schemaCode: schemaCode2, typeCode: typeCode2 };
5200
+ }
5201
+ const allStrings = values.every((v) => typeof v === "string");
5202
+ if (allStrings) {
5203
+ const enumValues = values.map((v) => `"${v}"`).join(", ");
5204
+ const schemaCode2 = `export const ${schemaName} = z.enum([${enumValues}]);`;
5205
+ const typeCode2 = `export type ${typeName} = z.infer<typeof ${schemaName}>;`;
5206
+ return { schemaCode: schemaCode2, typeCode: typeCode2 };
5207
+ }
5208
+ const literalValues = values.map((v) => {
5209
+ if (typeof v === "string") {
5210
+ return `z.literal("${v}")`;
5211
+ }
5212
+ return `z.literal(${v})`;
5213
+ }).join(", ");
5214
+ const schemaCode = `export const ${schemaName} = z.union([${literalValues}]);`;
5197
5215
  const typeCode = `export type ${typeName} = z.infer<typeof ${schemaName}>;`;
5198
5216
  return { schemaCode, typeCode };
5199
5217
  }
@@ -5207,7 +5225,7 @@ function escapeDescription(str) {
5207
5225
  return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n");
5208
5226
  }
5209
5227
  function escapePattern(str) {
5210
- return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
5228
+ return str.replace(/\//g, "\\/");
5211
5229
  }
5212
5230
  function escapeJSDoc(str) {
5213
5231
  return str.replace(/\*\//g, "*\\/");
@@ -5337,61 +5355,36 @@ var LRUCache = class {
5337
5355
 
5338
5356
  // src/utils/pattern-utils.ts
5339
5357
  init_cjs_shims();
5340
- function isRegexPattern(pattern) {
5341
- if (pattern.startsWith("^") || pattern.endsWith("$")) {
5342
- return true;
5343
- }
5344
- if (/\\[dDwWsS]/.test(pattern)) {
5345
- return true;
5346
- }
5347
- if (/\.\*|\.\+/.test(pattern)) {
5348
- return true;
5349
- }
5350
- if (/[[\]()]/.test(pattern)) {
5351
- return true;
5352
- }
5353
- if (/[^/][+?*]\{/.test(pattern)) {
5358
+ var import_minimatch = require("minimatch");
5359
+ function isValidGlobPattern(pattern) {
5360
+ try {
5361
+ new import_minimatch.minimatch.Minimatch(pattern);
5354
5362
  return true;
5363
+ } catch {
5364
+ return false;
5355
5365
  }
5356
- return false;
5357
5366
  }
5358
- function patternToRegex(pattern) {
5359
- if (pattern instanceof RegExp) {
5360
- return pattern;
5361
- }
5362
- if (isRegexPattern(pattern)) {
5363
- try {
5364
- return new RegExp(pattern);
5365
- } catch (error) {
5366
- console.warn(`\u26A0\uFE0F Invalid regex pattern "${pattern}": ${error instanceof Error ? error.message : String(error)}`);
5367
- return null;
5368
- }
5369
- }
5370
- return null;
5367
+ function isGlobPattern(pattern) {
5368
+ return /[*?[\]{}!]/.test(pattern);
5371
5369
  }
5372
5370
  function stripPrefix(input, pattern, ensureLeadingChar) {
5373
5371
  if (!pattern) {
5374
5372
  return input;
5375
5373
  }
5376
- const regex = patternToRegex(pattern);
5377
- if (regex) {
5378
- const match = input.match(regex);
5379
- if (match && match.index === 0) {
5380
- const stripped = input.substring(match[0].length);
5381
- if (ensureLeadingChar) {
5382
- if (stripped === "") {
5383
- return ensureLeadingChar;
5384
- }
5385
- if (!stripped.startsWith(ensureLeadingChar)) {
5386
- return `${ensureLeadingChar}${stripped}`;
5387
- }
5374
+ if (isGlobPattern(pattern) && !isValidGlobPattern(pattern)) {
5375
+ console.warn(`\u26A0\uFE0F Invalid glob pattern "${pattern}": Pattern is malformed`);
5376
+ return input;
5377
+ }
5378
+ if (isGlobPattern(pattern)) {
5379
+ let longestMatch = -1;
5380
+ for (let i = 1; i <= input.length; i++) {
5381
+ const testPrefix = input.substring(0, i);
5382
+ if ((0, import_minimatch.minimatch)(testPrefix, pattern)) {
5383
+ longestMatch = i;
5388
5384
  }
5389
- return stripped;
5390
5385
  }
5391
- } else {
5392
- const stringPattern = pattern;
5393
- if (input.startsWith(stringPattern)) {
5394
- const stripped = input.substring(stringPattern.length);
5386
+ if (longestMatch > 0) {
5387
+ const stripped = input.substring(longestMatch);
5395
5388
  if (ensureLeadingChar) {
5396
5389
  if (stripped === "") {
5397
5390
  return ensureLeadingChar;
@@ -5400,8 +5393,21 @@ function stripPrefix(input, pattern, ensureLeadingChar) {
5400
5393
  return `${ensureLeadingChar}${stripped}`;
5401
5394
  }
5402
5395
  }
5403
- return stripped;
5396
+ return stripped === "" && !ensureLeadingChar ? input : stripped;
5404
5397
  }
5398
+ return input;
5399
+ }
5400
+ if (input.startsWith(pattern)) {
5401
+ const stripped = input.substring(pattern.length);
5402
+ if (ensureLeadingChar) {
5403
+ if (stripped === "") {
5404
+ return ensureLeadingChar;
5405
+ }
5406
+ if (!stripped.startsWith(ensureLeadingChar)) {
5407
+ return `${ensureLeadingChar}${stripped}`;
5408
+ }
5409
+ }
5410
+ return stripped;
5405
5411
  }
5406
5412
  return input;
5407
5413
  }
@@ -6284,9 +6290,25 @@ var _PropertyGenerator = class _PropertyGenerator {
6284
6290
  return wrapNullable(zodLiteral, nullable);
6285
6291
  }
6286
6292
  if (schema.enum) {
6287
- const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
6288
- const zodEnum = `z.enum([${enumValues}])`;
6289
- return wrapNullable(zodEnum, nullable);
6293
+ const allBooleans = schema.enum.every((v) => typeof v === "boolean");
6294
+ if (allBooleans) {
6295
+ const zodBoolean = "z.boolean()";
6296
+ return wrapNullable(zodBoolean, nullable);
6297
+ }
6298
+ const allStrings = schema.enum.every((v) => typeof v === "string");
6299
+ if (allStrings) {
6300
+ const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
6301
+ const zodEnum = `z.enum([${enumValues}])`;
6302
+ return wrapNullable(zodEnum, nullable);
6303
+ }
6304
+ const literalValues = schema.enum.map((v) => {
6305
+ if (typeof v === "string") {
6306
+ return `z.literal("${v}")`;
6307
+ }
6308
+ return `z.literal(${v})`;
6309
+ }).join(", ");
6310
+ const zodUnion = `z.union([${literalValues}])`;
6311
+ return wrapNullable(zodUnion, nullable);
6290
6312
  }
6291
6313
  if (schema.allOf) {
6292
6314
  let composition = generateAllOf(
@@ -6418,7 +6440,7 @@ var PropertyGenerator = _PropertyGenerator;
6418
6440
 
6419
6441
  // src/utils/operation-filters.ts
6420
6442
  init_cjs_shims();
6421
- var import_minimatch = require("minimatch");
6443
+ var import_minimatch2 = require("minimatch");
6422
6444
  function createFilterStatistics() {
6423
6445
  return {
6424
6446
  totalOperations: 0,
@@ -6437,7 +6459,7 @@ function matchesAnyPattern(value, patterns) {
6437
6459
  if (!value) {
6438
6460
  return false;
6439
6461
  }
6440
- return patterns.some((pattern) => (0, import_minimatch.minimatch)(value, pattern));
6462
+ return patterns.some((pattern) => (0, import_minimatch2.minimatch)(value, pattern));
6441
6463
  }
6442
6464
  function containsAny(arr, values) {
6443
6465
  if (!values || values.length === 0) {
@@ -6664,6 +6686,9 @@ var OpenApiGenerator = class {
6664
6686
  throw new SpecValidationError("No schemas found in OpenAPI spec", { filePath: this.options.input });
6665
6687
  }
6666
6688
  for (const [name, schema] of Object.entries(this.spec.components.schemas)) {
6689
+ if (this.options.operationFilters && this.schemaUsageMap.size > 0 && !this.schemaUsageMap.has(name)) {
6690
+ continue;
6691
+ }
6667
6692
  this.generateComponentSchema(name, schema);
6668
6693
  }
6669
6694
  this.generateQueryParameterSchemas();
@@ -7152,7 +7177,7 @@ ${propsCode}
7152
7177
  const headerLower = headerName.toLowerCase();
7153
7178
  return ignorePatterns.some((pattern) => {
7154
7179
  const patternLower = pattern.toLowerCase();
7155
- return (0, import_minimatch2.minimatch)(headerLower, patternLower);
7180
+ return (0, import_minimatch3.minimatch)(headerLower, patternLower);
7156
7181
  });
7157
7182
  }
7158
7183
  /**
@@ -7240,8 +7265,22 @@ ${propsCode}
7240
7265
  return `${schemaName}Schema`;
7241
7266
  }
7242
7267
  if (schema.enum) {
7243
- const enumValues = schema.enum.map((v) => typeof v === "string" ? `"${v}"` : v).join(", ");
7244
- return `z.enum([${enumValues}])`;
7268
+ const allBooleans = schema.enum.every((v) => typeof v === "boolean");
7269
+ if (allBooleans) {
7270
+ return "z.boolean()";
7271
+ }
7272
+ const allStrings = schema.enum.every((v) => typeof v === "string");
7273
+ if (allStrings) {
7274
+ const enumValues = schema.enum.map((v) => `"${v}"`).join(", ");
7275
+ return `z.enum([${enumValues}])`;
7276
+ }
7277
+ const literalValues = schema.enum.map((v) => {
7278
+ if (typeof v === "string") {
7279
+ return `z.literal("${v}")`;
7280
+ }
7281
+ return `z.literal(${v})`;
7282
+ }).join(", ");
7283
+ return `z.union([${literalValues}])`;
7245
7284
  }
7246
7285
  const type = schema.type;
7247
7286
  if (type === "string") {