swaggie 2.2.1 → 2.3.0

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
@@ -396,6 +396,21 @@ Sometimes an API spec marks a parameter as required, but your client handles it
396
396
  - `"optional"` — the parameter becomes optional regardless of what the spec says
397
397
  - `"required"` — the parameter is always required (generally better to just fix the spec)
398
398
 
399
+ ### Excluding Operations
400
+
401
+ Use `exclude` to drop entire operations from the generated output by tag or `operationId` — without modifying the spec. Types used exclusively by excluded operations are pruned automatically. Both fields support `*` and `?` wildcards.
402
+
403
+ ```json
404
+ {
405
+ "exclude": {
406
+ "tags": ["admin", "internal"],
407
+ "operationIds": ["deleteAccount", "admin*"]
408
+ }
409
+ }
410
+ ```
411
+
412
+ See the [full documentation](https://yhnavein.github.io/swaggie/guide/advanced#excluding-operations) for details and examples.
413
+
399
414
  ### Code Quality
400
415
 
401
416
  Swaggie's output is functional but not always perfectly formatted, since it uses a templating engine internally. It is strongly recommended to run the output through a formatter to ensure consistent style across regenerations.
@@ -48,10 +48,7 @@ const MOCK_FILE_HEADER = `\
48
48
  const template = options.template;
49
49
 
50
50
  if (template === 'ng1') {
51
- throw new Error(
52
- 'Mock generation is not supported for the "ng1" template. ' +
53
- 'Use "ng2", "axios", "fetch", "xior", "swr", or "tsq" instead.'
54
- );
51
+ throw new Error('Mock generation is not supported for the "ng1" template');
55
52
  }
56
53
 
57
54
  const isNg2 = template === 'ng2';
@@ -254,28 +251,34 @@ interface MockSWRReturn {
254
251
  data: unknown;
255
252
  /** Whether to return a loading state (default: false) */
256
253
  isLoading?: boolean;
254
+ /** Whether to return a validating state (default: false) */
255
+ isValidating?: boolean;
257
256
  /** Whether to return an error (default: undefined) */
258
257
  error?: Error | undefined | null;
258
+ /** The mutate function to return (default: empty mock) */
259
+ mutate?: KeyedMutator<any>;
259
260
  }
260
261
 
261
262
  interface MockSWRMutationReturn {
262
263
  /** The data to return from the mock */
263
- data: unknown;
264
+ data?: unknown;
264
265
  /** Whether to return a mutating state (default: false) */
265
266
  isMutating?: boolean;
266
267
  /** Whether to return an error (default: undefined) */
267
268
  error?: Error | undefined | null;
269
+ /** The trigger function to return (default: empty mock) */
270
+ trigger?: (...args: unknown[]) => Promise<unknown>;
271
+ /** The reset function to return (default: empty mock) */
272
+ reset?: () => void;
268
273
  }
269
274
 
270
275
  /** Augments a spy with a \`mockSWR\` shorthand for useSWR query hooks. */
271
276
  function withMockSWR<Fn extends (...args: never[]) => unknown>(spy: ${ref}.SpiedFunction<Fn>) {
272
277
  return Object.assign(spy, {
273
- mockSWR({ data, isLoading, error }: MockSWRReturn) {
278
+ mockSWR({ ...rest }: MockSWRReturn) {
274
279
  spy.mockReturnValue({
275
280
  ...defaultSWRReturn,
276
- data,
277
- isLoading: isLoading ?? false,
278
- error: error ?? undefined,
281
+ ...rest,
279
282
  } as ReturnType<Fn>);
280
283
  },
281
284
  });
@@ -284,17 +287,10 @@ function withMockSWR<Fn extends (...args: never[]) => unknown>(spy: ${ref}.Spied
284
287
  /** Augments a spy with a \`mockSWRMutation\` shorthand for useSWRMutation hooks. */
285
288
  function withMockSWRMutation<Fn extends (...args: never[]) => unknown>(spy: ${ref}.SpiedFunction<Fn>) {
286
289
  return Object.assign(spy, {
287
- mockSWRMutation({
288
- data,
289
- isMutating,
290
- error,
291
- }: MockSWRMutationReturn) {
290
+ mockSWRMutation({ ...rest }: MockSWRMutationReturn) {
292
291
  spy.mockReturnValue({
293
292
  ...defaultSWRMutationReturn,
294
- trigger: ${ref}.fn(() => Promise.resolve(undefined)),
295
- isMutating: isMutating ?? false,
296
- error: error ?? undefined,
297
- data,
293
+ ...rest,
298
294
  } as ReturnType<Fn>);
299
295
  },
300
296
  });
@@ -255,6 +255,18 @@ function prepareClient(
255
255
  ops = ops.filter((op) => !op.deprecated);
256
256
  }
257
257
 
258
+ if (_optionalChain([options, 'access', _2 => _2.exclude, 'optionalAccess', _3 => _3.tags, 'optionalAccess', _4 => _4.length])) {
259
+ ops = ops.filter(
260
+ (op) => !_optionalChain([op, 'access', _5 => _5.tags, 'optionalAccess', _6 => _6.some, 'call', _7 => _7((t) => options.exclude.tags.some((p) => matchesPattern(t, p)))])
261
+ );
262
+ }
263
+
264
+ if (_optionalChain([options, 'access', _8 => _8.exclude, 'optionalAccess', _9 => _9.operationIds, 'optionalAccess', _10 => _10.length])) {
265
+ ops = ops.filter(
266
+ (op) => !options.exclude.operationIds.some((p) => matchesPattern(_nullishCoalesce(op.operationId, () => ( '')), p))
267
+ );
268
+ }
269
+
258
270
  return ops.map((op) => {
259
271
  const operationContext = `${op.method.toUpperCase()} ${op.path} (${op.operationId || 'unknown operationId'})`;
260
272
 
@@ -295,9 +307,9 @@ function prepareClient(
295
307
 
296
308
  const headers = getParams(op.parameters , options, ['header']);
297
309
  // Some libraries need explicit Content-Type for request bodies.
298
- if (_optionalChain([body, 'optionalAccess', _2 => _2.contentType]) === 'urlencoded') {
310
+ if (_optionalChain([body, 'optionalAccess', _11 => _11.contentType]) === 'urlencoded') {
299
311
  upsertFixedHeader(headers, 'Content-Type', 'application/x-www-form-urlencoded');
300
- } else if (_optionalChain([body, 'optionalAccess', _3 => _3.contentType]) === 'json' && _templateValidator.getL1Template.call(void 0, options.template) === 'fetch') {
312
+ } else if (_optionalChain([body, 'optionalAccess', _12 => _12.contentType]) === 'json' && _templateValidator.getL1Template.call(void 0, options.template) === 'fetch') {
301
313
  upsertFixedHeader(headers, 'Content-Type', 'application/json');
302
314
  }
303
315
 
@@ -485,10 +497,10 @@ function prepareUrl(path) {
485
497
  type: _swagger.getParameterType.call(void 0, p, options),
486
498
  optional: p.required === undefined || p.required === null ? true : !p.required,
487
499
  original: p,
488
- jsDoc: _optionalChain([p, 'access', _4 => _4.description, 'optionalAccess', _5 => _5.trim, 'call', _6 => _6()]),
500
+ jsDoc: _optionalChain([p, 'access', _13 => _13.description, 'optionalAccess', _14 => _14.trim, 'call', _15 => _15()]),
489
501
  }));
490
502
 
491
- if (_optionalChain([options, 'access', _7 => _7.modifiers, 'optionalAccess', _8 => _8.parameters])) {
503
+ if (_optionalChain([options, 'access', _16 => _16.modifiers, 'optionalAccess', _17 => _17.parameters])) {
492
504
  for (const [name, modifier] of Object.entries(options.modifiers.parameters)) {
493
505
  const paramIndex = result.findIndex(
494
506
  (p) => p.original.in !== 'path' && (p.originalName === name || p.name === name)
@@ -539,7 +551,7 @@ function getRequestBody(
539
551
  let reqBody;
540
552
  if ('$ref' in rawReqBody) {
541
553
  const refName = rawReqBody.$ref.replace('#/components/requestBodies/', '');
542
- const resolved = _optionalChain([components, 'optionalAccess', _9 => _9.requestBodies, 'optionalAccess', _10 => _10[refName]]);
554
+ const resolved = _optionalChain([components, 'optionalAccess', _18 => _18.requestBodies, 'optionalAccess', _19 => _19[refName]]);
543
555
  if (!resolved || '$ref' in resolved) {
544
556
  console.error(`RequestBody $ref '${rawReqBody.$ref}' not found in components/requestBodies`);
545
557
  return null;
@@ -673,6 +685,23 @@ function getL1HttpTypeImport(template) {
673
685
  }
674
686
  }
675
687
 
688
+ /**
689
+ * Matches a value against a pattern that may contain `*` (any sequence of
690
+ * characters) or `?` (any single character). Falls back to exact equality for
691
+ * patterns that contain neither wildcard (fast path).
692
+ */
693
+ function matchesPattern(value, pattern) {
694
+ if (!pattern.includes('*') && !pattern.includes('?')) {
695
+ return value === pattern;
696
+ }
697
+ const regex = new RegExp(
698
+ '^' +
699
+ pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.') +
700
+ '$'
701
+ );
702
+ return regex.test(value);
703
+ } exports.matchesPattern = matchesPattern;
704
+
676
705
  function upsertFixedHeader(headers, headerName, value) {
677
706
  const headerIndex = headers.findIndex(
678
707
  (header) => header.originalName.toLowerCase() === headerName.toLowerCase()
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ var _gen = require('./gen'); var _gen2 = _interopRequireDefault(_gen);
10
10
 
11
11
 
12
12
 
13
+
13
14
  var _utils = require('./utils');
14
15
  var _templateValidator = require('./utils/templateValidator');
15
16
  var _swagger = require('./swagger');
@@ -86,6 +87,7 @@ function verifyOptions(options) {
86
87
  );
87
88
  }
88
89
  }
90
+ verifyExcludeOptions(options.exclude);
89
91
  }
90
92
 
91
93
  /**
@@ -126,6 +128,43 @@ function verifyEntryOptions(opts) {
126
128
  );
127
129
  }
128
130
  }
131
+ verifyExcludeOptions(opts.exclude);
132
+ }
133
+
134
+ /**
135
+ * Returns true when a pattern looks like a regex (starts with `/` or contains
136
+ * regex-specific metacharacters that are not our supported wildcards).
137
+ * Our supported wildcards are `*` and `?` only.
138
+ */
139
+ function isRegexPattern(pattern) {
140
+ if (pattern.startsWith('/')) {
141
+ return true;
142
+ }
143
+ // Characters that are regex metacharacters but are NOT our supported wildcards
144
+ const regexOnlyChars = /[\\^$.|+()[\]{}]/;
145
+ return regexOnlyChars.test(pattern);
146
+ }
147
+
148
+ /**
149
+ * Throws if any pattern in `exclude.tags` or `exclude.operationIds` looks like
150
+ * a regex. Only plain strings and * / ? wildcards are supported.
151
+ */
152
+ function verifyExcludeOptions(exclude) {
153
+ if (!exclude) {
154
+ return;
155
+ }
156
+ const allPatterns = [
157
+ ...(_nullishCoalesce(exclude.tags, () => ( []))).map((p) => ['exclude.tags', p]),
158
+ ...(_nullishCoalesce(exclude.operationIds, () => ( []))).map((p) => ['exclude.operationIds', p]),
159
+ ];
160
+ for (const [field, pattern] of allPatterns) {
161
+ if (isRegexPattern(pattern)) {
162
+ throw new Error(
163
+ `Invalid pattern "${pattern}" in ${field}: regex patterns are not supported. ` +
164
+ 'Use plain strings or wildcard patterns with * and ? only.'
165
+ );
166
+ }
167
+ }
129
168
  }
130
169
 
131
170
  /**
package/dist/types.d.ts CHANGED
@@ -4,6 +4,18 @@ interface QueryParamsSerializationOptions {
4
4
  arrayFormat?: ArrayFormat;
5
5
  queryParamsAsObject?: boolean | number;
6
6
  }
7
+ export interface ExcludeOptions {
8
+ /**
9
+ * Exclude operations whose first tag matches any of these values.
10
+ * Supports * (any sequence of characters) and ? (any single character) wildcards.
11
+ */
12
+ tags?: string[];
13
+ /**
14
+ * Exclude operations whose operationId matches any of these values.
15
+ * Supports * (any sequence of characters) and ? (any single character) wildcards.
16
+ */
17
+ operationIds?: string[];
18
+ }
7
19
  export interface ClientOptions {
8
20
  /**
9
21
  * Path or URL to the Swagger specification file (JSON or YAML).
@@ -106,6 +118,8 @@ export interface ClientOptions {
106
118
  [key: string]: 'optional' | 'required' | 'ignore';
107
119
  };
108
120
  };
121
+ /** Excludes specific operations from code generation by tag or operationId */
122
+ exclude?: ExcludeOptions;
109
123
  }
110
124
  export interface CliOptions extends Omit<FullAppOptions, 'enumNamesStyle'> {
111
125
  allowDots?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swaggie",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "Generate a fully typed TypeScript API client from your OpenAPI 3 spec",
5
5
  "author": {
6
6
  "name": "Piotr Dabrowski",