swaggie 1.8.8 → 1.9.0-alpha.10
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 +116 -31
- package/dist/browser.js +24 -5
- package/dist/cli.js +53 -3
- package/dist/gen/genMocks.js +316 -0
- package/dist/gen/genOperations.js +79 -5
- package/dist/gen/genTypes.js +17 -7
- package/dist/gen/header.js +1 -0
- package/dist/gen/index.js +9 -0
- package/dist/gen/jsDocs.js +35 -1
- package/dist/generated/bundledTemplates.js +21 -23
- package/dist/index.js +17 -1
- package/dist/swagger/typesExtractor.js +10 -3
- package/dist/types.d.ts +54 -4
- package/dist/utils/fileUtils.js +22 -0
- package/dist/utils/templateEngine.js +20 -0
- package/dist/utils/templateManager.js +123 -14
- package/dist/utils/templateValidator.js +127 -0
- package/package.json +1 -1
- package/templates/axios/baseClient.ejs +0 -11
- package/templates/axios/operation.ejs +4 -0
- package/templates/fetch/baseClient.ejs +0 -11
- package/templates/fetch/operation.ejs +4 -0
- package/templates/ng1/baseClient.ejs +0 -11
- package/templates/ng1/operation.ejs +3 -3
- package/templates/ng2/baseClient.ejs +1 -61
- package/templates/ng2/client.ejs +3 -7
- package/templates/ng2/operation.ejs +20 -13
- package/templates/swr/baseClient.ejs +7 -0
- package/templates/swr/client.ejs +63 -0
- package/templates/swr/swrMutationOperation.ejs +32 -0
- package/templates/swr/swrOperation.ejs +18 -0
- package/templates/tsq/baseClient.ejs +1 -0
- package/templates/tsq/client.ejs +67 -0
- package/templates/tsq/mutationOperation.ejs +31 -0
- package/templates/tsq/queryOperation.ejs +19 -0
- package/templates/xior/baseClient.ejs +1 -12
- package/templates/xior/operation.ejs +4 -0
- package/templates/swr-axios/barrel.ejs +0 -58
- package/templates/swr-axios/baseClient.ejs +0 -30
- package/templates/swr-axios/client.ejs +0 -21
- package/templates/swr-axios/operation.ejs +0 -40
- package/templates/swr-axios/swrOperation.ejs +0 -50
- package/templates/tsq-xior/barrel.ejs +0 -0
- package/templates/tsq-xior/baseClient.ejs +0 -25
- package/templates/tsq-xior/client.ejs +0 -22
- package/templates/tsq-xior/operation.ejs +0 -40
- package/templates/tsq-xior/queryOperation.ejs +0 -30
package/README.md
CHANGED
|
@@ -21,7 +21,8 @@ See the [Example section](#example) for a quick demo, or visit the full document
|
|
|
21
21
|
## Features
|
|
22
22
|
|
|
23
23
|
- Generates TypeScript code from OpenAPI 3.0, 3.1, and 3.2 specs
|
|
24
|
-
- Supports multiple HTTP client libraries out of the box: `fetch`, `axios`, `xior`, `
|
|
24
|
+
- Supports multiple HTTP client libraries out of the box: `fetch`, `axios`, `xior`, `Angular 1`, `Angular 2+`; with optional reactive layers (`swr`, `tsq`) that compose with any compatible HTTP client
|
|
25
|
+
- **Auto-generated mock/stub files** for Vitest and Jest — typed spies for every client method and hook, with ergonomic helpers like `mockSWR()` and `mockQuery()`
|
|
25
26
|
- Custom templates — bring your own to fit your existing codebase
|
|
26
27
|
- Supports `allOf`, `oneOf`, `anyOf`, `$ref`, nullable types, and various enum definitions
|
|
27
28
|
- Handles multiple content types: JSON, plain text, multipart/form-data, URL-encoded, and binary
|
|
@@ -72,24 +73,27 @@ swaggie -s https://petstore3.swagger.io/api/v3/openapi.json -o ./client/petstore
|
|
|
72
73
|
**Available options:**
|
|
73
74
|
|
|
74
75
|
```
|
|
75
|
-
-V, --version
|
|
76
|
-
-c, --config <path>
|
|
77
|
-
-s, --src <url|path>
|
|
78
|
-
-o, --out <filePath>
|
|
79
|
-
-b, --baseUrl <string>
|
|
80
|
-
-t, --template <string>
|
|
81
|
-
-m, --mode <mode>
|
|
82
|
-
-d, --schemaStyle <style>
|
|
83
|
-
--enumStyle <style>
|
|
84
|
-
--enumNamesStyle <s>
|
|
85
|
-
--dateFormat <format>
|
|
86
|
-
--nullables <strategy>
|
|
87
|
-
--preferAny
|
|
88
|
-
--skipDeprecated
|
|
89
|
-
--servicePrefix
|
|
90
|
-
--allowDots
|
|
91
|
-
--arrayFormat
|
|
92
|
-
-
|
|
76
|
+
-V, --version Output the version number
|
|
77
|
+
-c, --config <path> Path to a JSON configuration file
|
|
78
|
+
-s, --src <url|path> URL or file path to the OpenAPI spec
|
|
79
|
+
-o, --out <filePath> Output file path (omit to print to stdout)
|
|
80
|
+
-b, --baseUrl <string> Base URL that will be used as a default value in the clients
|
|
81
|
+
-t, --template <string> Template to use. Single name: "axios", "fetch", "xior", "ng1", "ng2", "swr", "tsq". Reactive pair: "swr,axios" / "tsq,xior" / etc. (default: "axios")
|
|
82
|
+
-m, --mode <mode> Generation mode: "full" or "schemas" (default: "full")
|
|
83
|
+
-d, --schemaStyle <style> Schema object style: "interface" or "type" (default: "interface")
|
|
84
|
+
--enumStyle <style> Enum style for plain string enums: "union" or "enum" (default: "union")
|
|
85
|
+
--enumNamesStyle <s> Enum member name casing: "original" or "PascalCase" (default: "original")
|
|
86
|
+
--dateFormat <format> How date fields are emitted in generated types
|
|
87
|
+
--nullables <strategy> Nullable handling: "include", "nullableAsOptional", or "ignore"
|
|
88
|
+
--preferAny Use "any" instead of "unknown" for untyped values (default: false)
|
|
89
|
+
--skipDeprecated Exclude deprecated operations from the output (default: false)
|
|
90
|
+
--servicePrefix Prefix for service names — useful when generating multiple APIs
|
|
91
|
+
--allowDots Use dot notation to serialize nested object query params
|
|
92
|
+
--arrayFormat How arrays are serialized: "indices", "repeat", or "brackets"
|
|
93
|
+
-C, --useClient Prepend 'use client'; directive (Next.js App Router + SWR/TSQ)
|
|
94
|
+
--mocks <path> Output path for a generated mock/stub file (requires --testingFramework and --out)
|
|
95
|
+
-T, --testingFramework <name> Framework for generated mocks: "vitest" or "jest" (requires --mocks and --out)
|
|
96
|
+
-h, --help Show help
|
|
93
97
|
```
|
|
94
98
|
|
|
95
99
|
### Formatting the Output
|
|
@@ -142,24 +146,72 @@ The `$schema` field enables autocompletion and inline documentation in most edit
|
|
|
142
146
|
|
|
143
147
|
## Templates
|
|
144
148
|
|
|
145
|
-
Swaggie
|
|
149
|
+
Swaggie's templates are split into two independent layers that you can combine freely.
|
|
146
150
|
|
|
147
|
-
|
|
148
|
-
| ----------- | ----------------------------------------------------------------------------------------- |
|
|
149
|
-
| `axios` | Default. Recommended for React, Vue, and similar frameworks |
|
|
150
|
-
| `xior` | Lightweight modern alternative to axios ([xior](https://github.com/suhaotian/xior#intro)) |
|
|
151
|
-
| `swr-axios` | SWR hooks for GET requests, backed by axios |
|
|
152
|
-
| `tsq-xior` | TanStack Query hooks for GET requests, backed by xior |
|
|
153
|
-
| `fetch` | Uses the native browser Fetch API |
|
|
154
|
-
| `ng1` | Angular 1 client |
|
|
155
|
-
| `ng2` | Angular 2+ client (uses HttpClient and InjectionTokens) |
|
|
151
|
+
### HTTP client templates (L1)
|
|
156
152
|
|
|
157
|
-
|
|
153
|
+
These are standalone and cover the most common client libraries:
|
|
154
|
+
|
|
155
|
+
| Template | Description |
|
|
156
|
+
| -------- | ----------- |
|
|
157
|
+
| `axios` | Default. Recommended for React, Vue, and most Node.js projects |
|
|
158
|
+
| `fetch` | Native browser/Node 18+ Fetch API — zero runtime dependencies |
|
|
159
|
+
| `xior` | Lightweight Axios-compatible alternative ([xior](https://github.com/suhaotian/xior#intro)) |
|
|
160
|
+
| `ng1` | Angular 1 client |
|
|
161
|
+
| `ng2` | Angular 2+ client (uses `HttpClient` and `InjectionToken`) |
|
|
162
|
+
|
|
163
|
+
### Reactive query layer templates (L2)
|
|
164
|
+
|
|
165
|
+
These add a reactive data-fetching layer (SWR or TanStack Query hooks) on top of any compatible L1 client. They cannot be used alone — pair them with an L1 template using a 2-element array:
|
|
166
|
+
|
|
167
|
+
| Template | Description |
|
|
168
|
+
| -------- | ----------- |
|
|
169
|
+
| `swr` | [SWR](https://swr.vercel.app) hooks for queries and mutations |
|
|
170
|
+
| `tsq` | [TanStack Query](https://tanstack.com/query) hooks for queries and mutations |
|
|
171
|
+
|
|
172
|
+
Compatible L1 templates for L2: `axios`, `fetch`, `xior`. Angular clients are not compatible with reactive layers.
|
|
173
|
+
|
|
174
|
+
### Usage examples
|
|
175
|
+
|
|
176
|
+
**Single L1 template (existing behaviour):**
|
|
177
|
+
|
|
178
|
+
```json
|
|
179
|
+
{ "template": "axios" }
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
swaggie -s ./openapi.json -o ./client.ts -t axios
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**L2 + L1 combination — in config:**
|
|
187
|
+
|
|
188
|
+
```json
|
|
189
|
+
{ "template": ["swr", "axios"] }
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# CLI: comma-separated pair
|
|
194
|
+
swaggie -s ./openapi.json -o ./client.ts -t swr,axios
|
|
195
|
+
swaggie -s ./openapi.json -o ./client.ts -t tsq,xior
|
|
196
|
+
swaggie -s ./openapi.json -o ./client.ts -t swr,fetch
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**L2 alone — defaults to `fetch` as the L1:**
|
|
200
|
+
|
|
201
|
+
```json
|
|
202
|
+
{ "template": "swr" }
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Custom templates
|
|
206
|
+
|
|
207
|
+
Pass the path to your own template directory as the template value:
|
|
158
208
|
|
|
159
209
|
```bash
|
|
160
210
|
swaggie -s https://petstore3.swagger.io/api/v3/openapi.json -o ./client/petstore.ts --template ./my-swaggie-template/
|
|
161
211
|
```
|
|
162
212
|
|
|
213
|
+
Custom paths also work as part of a composite pair: `["./my-l2", "axios"]`.
|
|
214
|
+
|
|
163
215
|
---
|
|
164
216
|
|
|
165
217
|
## Example
|
|
@@ -179,7 +231,7 @@ import Axios, { AxiosPromise } from 'axios';
|
|
|
179
231
|
|
|
180
232
|
const axios = Axios.create({
|
|
181
233
|
baseURL: '/api',
|
|
182
|
-
paramsSerializer: (params) =>
|
|
234
|
+
paramsSerializer: (params: any) =>
|
|
183
235
|
encodeParams(params, null, {
|
|
184
236
|
allowDots: true,
|
|
185
237
|
arrayFormat: 'repeat',
|
|
@@ -359,6 +411,39 @@ Either tool needs to be installed separately and configured for your project.
|
|
|
359
411
|
|
|
360
412
|
---
|
|
361
413
|
|
|
414
|
+
## Generating Mocks
|
|
415
|
+
|
|
416
|
+
Swaggie can generate a companion mock file alongside your client — a set of typed spy stubs for every method and hook, ready to drop into your tests.
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
swaggie -s ./spec.json -o ./src/api/client.ts -t swr,axios \
|
|
420
|
+
--mocks ./src/__mocks__/api.ts --testingFramework vitest
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Or in a config file:
|
|
424
|
+
|
|
425
|
+
```json
|
|
426
|
+
{
|
|
427
|
+
"src": "./openapi.json",
|
|
428
|
+
"out": "./src/api/client.ts",
|
|
429
|
+
"template": ["swr", "axios"],
|
|
430
|
+
"mocks": "./src/__mocks__/api.ts",
|
|
431
|
+
"testingFramework": "vitest"
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
The generated mock file exports the same names as the real client, so `vi.mock('./api', () => import('./__mocks__/api'))` is all you need in tests. For L2 (SWR/TSQ) templates, hook stubs come with shorthand helpers:
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
pet.queries.usePetById.mockSWR({ data: { id: 1, name: 'Rex' } });
|
|
439
|
+
pet.mutations.useAddPet.mockSWRMutation({ isMutating: false });
|
|
440
|
+
// TanStack Query equivalents: mockQuery() / mockMutation()
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
See the [Mocking guide](https://yhnavein.github.io/swaggie/guide/mocking) for full details.
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
362
447
|
## Using Swaggie Programmatically
|
|
363
448
|
|
|
364
449
|
You can also call Swaggie directly from Node.js/bun/deno/etc:
|
package/dist/browser.js
CHANGED
|
@@ -9,6 +9,8 @@ var _bundledTemplates = require('./generated/bundledTemplates');
|
|
|
9
9
|
var _utils = require('./utils/utils');
|
|
10
10
|
var _documentLoaderbrowser = require('./utils/documentLoader.browser');
|
|
11
11
|
var _templateEngine = require('./utils/templateEngine');
|
|
12
|
+
var _templateValidator = require('./utils/templateValidator');
|
|
13
|
+
var _templateManager = require('./utils/templateManager');
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Browser-friendly code generation entrypoint.
|
|
@@ -52,12 +54,27 @@ async function generateCode(spec, options) {
|
|
|
52
54
|
return _header.FILE_HEADER + _genTypes2.default.call(void 0, spec, options, false);
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
_templateValidator.validateTemplate.call(void 0, options.template);
|
|
58
|
+
|
|
59
|
+
if (Array.isArray(options.template)) {
|
|
60
|
+
const [l2, l1] = options.template;
|
|
61
|
+
const l1Files = _bundledTemplates.BUNDLED_TEMPLATES[l1 ];
|
|
62
|
+
const l2Files = _bundledTemplates.BUNDLED_TEMPLATES[l2 ];
|
|
63
|
+
if (!l1Files) {
|
|
64
|
+
throw new Error(`Bundled templates for L1 '${l1}' are not available`);
|
|
65
|
+
}
|
|
66
|
+
if (!l2Files) {
|
|
67
|
+
throw new Error(`Bundled templates for L2 '${l2}' are not available`);
|
|
68
|
+
}
|
|
69
|
+
_templateEngine.initTemplateEngineFromBundled.call(void 0, _templateManager.mergeTemplateFiles.call(void 0, l1Files, l2Files));
|
|
70
|
+
} else {
|
|
71
|
+
const templateFiles = _bundledTemplates.BUNDLED_TEMPLATES[options.template ];
|
|
72
|
+
if (!templateFiles) {
|
|
73
|
+
throw new Error(`Bundled templates for '${options.template}' are not available`);
|
|
74
|
+
}
|
|
75
|
+
_templateEngine.initTemplateEngineFromBundled.call(void 0, templateFiles);
|
|
58
76
|
}
|
|
59
77
|
|
|
60
|
-
_templateEngine.initTemplateEngineFromBundled.call(void 0, templateFiles);
|
|
61
78
|
const operationsCode = await _genOperations2.default.call(void 0, spec, options);
|
|
62
79
|
|
|
63
80
|
return operationsCode + _genTypes2.default.call(void 0, spec, options);
|
|
@@ -70,6 +87,7 @@ async function generateCode(spec, options) {
|
|
|
70
87
|
const {
|
|
71
88
|
allowDots,
|
|
72
89
|
arrayFormat,
|
|
90
|
+
queryParamsAsObject,
|
|
73
91
|
mode,
|
|
74
92
|
schemaStyle,
|
|
75
93
|
enumStyle,
|
|
@@ -86,11 +104,12 @@ async function generateCode(spec, options) {
|
|
|
86
104
|
),
|
|
87
105
|
...(allowDots !== undefined ? { allowDots } : {}),
|
|
88
106
|
...(arrayFormat !== undefined ? { arrayFormat } : {}),
|
|
107
|
+
...(queryParamsAsObject !== undefined ? { queryParamsAsObject } : {}),
|
|
89
108
|
};
|
|
90
109
|
|
|
91
110
|
return {
|
|
92
111
|
...rest,
|
|
93
|
-
template: _nullishCoalesce(template, () => ( _swagger.APP_DEFAULTS.template)),
|
|
112
|
+
template: _templateValidator.normalizeTemplate.call(void 0, _nullishCoalesce(template, () => ( _swagger.APP_DEFAULTS.template))),
|
|
94
113
|
servicePrefix: _nullishCoalesce(rest.servicePrefix, () => ( _swagger.APP_DEFAULTS.servicePrefix)),
|
|
95
114
|
nullableStrategy: _nullishCoalesce(_nullishCoalesce(nullables, () => ( rest.nullableStrategy)), () => ( _swagger.APP_DEFAULTS.nullableStrategy)),
|
|
96
115
|
generationMode: _nullishCoalesce(_nullishCoalesce(mode, () => ( rest.generationMode)), () => ( _swagger.APP_DEFAULTS.generationMode)),
|
package/dist/cli.js
CHANGED
|
@@ -13,6 +13,11 @@ const arrayFormatOption = new (0, _commander.Option)(
|
|
|
13
13
|
'Determines how arrays should be serialized'
|
|
14
14
|
).choices(['indices', 'repeat', 'brackets']);
|
|
15
15
|
|
|
16
|
+
const testingFrameworkOption = new (0, _commander.Option)(
|
|
17
|
+
'-T, --testingFramework <framework>',
|
|
18
|
+
'Test framework for generated mock stubs (requires --mocks and --out)'
|
|
19
|
+
).choices(['vitest', 'jest']);
|
|
20
|
+
|
|
16
21
|
const packageJson = readPackageJson();
|
|
17
22
|
|
|
18
23
|
const modeOption = new (0, _commander.Option)('-m, --mode <mode>', 'Generation mode').choices([
|
|
@@ -39,6 +44,10 @@ const nullableStrategyOption = new (0, _commander.Option)(
|
|
|
39
44
|
'--nullables <strategy>',
|
|
40
45
|
"Controls how OpenAPI 'nullable' is translated into TypeScript types"
|
|
41
46
|
).choices(['include', 'nullableAsOptional', 'ignore']);
|
|
47
|
+
const queryParamsAsObjectOption = new (0, _commander.Option)(
|
|
48
|
+
'--queryParamsAsObject [threshold]',
|
|
49
|
+
'Group query params into a single object; pass a number to group only when query params count is greater than threshold'
|
|
50
|
+
).argParser(parseQueryParamsAsObjectArg);
|
|
42
51
|
|
|
43
52
|
const program = new (0, _commander.Command)();
|
|
44
53
|
program
|
|
@@ -63,9 +72,18 @@ program
|
|
|
63
72
|
.option('-b, --baseUrl <string>', 'Base URL that will be used as a default value in the clients')
|
|
64
73
|
.option(
|
|
65
74
|
'-t, --template <string>',
|
|
66
|
-
'Template used for generating API client.
|
|
75
|
+
'Template used for generating API client. ' +
|
|
76
|
+
'L1 (HTTP client) templates: "axios" (default), "fetch", "xior", "ng1", "ng2". ' +
|
|
77
|
+
'L2 (reactive layer) templates must be paired with an L1 using a comma-separated value: ' +
|
|
78
|
+
'"swr,axios", "swr,fetch", "tsq,xior", "tsq,fetch", etc. ' +
|
|
79
|
+
'Providing only an L2 name (e.g. "swr") defaults to "fetch" as the L1.',
|
|
80
|
+
parseTemplateArg
|
|
67
81
|
)
|
|
68
82
|
.option('--preferAny', 'Use "any" type instead of "unknown"')
|
|
83
|
+
.option(
|
|
84
|
+
'-C, --useClient',
|
|
85
|
+
"Prepend 'use client'; to the generated file. Required for Next.js App Router with SWR or TanStack Query hooks"
|
|
86
|
+
)
|
|
69
87
|
.option(
|
|
70
88
|
'--skipDeprecated',
|
|
71
89
|
'Skip deprecated operations. When enabled, deprecated operations will be skipped from the generated code'
|
|
@@ -84,13 +102,19 @@ program
|
|
|
84
102
|
.addOption(enumStyleOption)
|
|
85
103
|
.addOption(enumNamesStyleOption)
|
|
86
104
|
.addOption(dateFormatOption)
|
|
87
|
-
.addOption(nullableStrategyOption)
|
|
105
|
+
.addOption(nullableStrategyOption)
|
|
106
|
+
.addOption(queryParamsAsObjectOption)
|
|
107
|
+
.option(
|
|
108
|
+
'--mocks <path>',
|
|
109
|
+
'Output path for the generated mock/stub file (requires --testingFramework and --out)'
|
|
110
|
+
)
|
|
111
|
+
.addOption(testingFrameworkOption);
|
|
88
112
|
|
|
89
113
|
program.parse(process.argv);
|
|
90
114
|
|
|
91
115
|
const options = program.opts();
|
|
92
116
|
|
|
93
|
-
_index.runCodeGenerator.call(void 0, options).then(complete, error);
|
|
117
|
+
_index.runCodeGenerator.call(void 0, options ).then(complete, error);
|
|
94
118
|
|
|
95
119
|
function complete([code, opts]) {
|
|
96
120
|
if (opts.out) {
|
|
@@ -116,3 +140,29 @@ function readPackageJson() {
|
|
|
116
140
|
throw new Error('Could not read package.json file');
|
|
117
141
|
}
|
|
118
142
|
}
|
|
143
|
+
|
|
144
|
+
function parseTemplateArg(value) {
|
|
145
|
+
const parts = value.split(',').map((s) => s.trim());
|
|
146
|
+
if (parts.length === 1) {
|
|
147
|
+
return parts[0];
|
|
148
|
+
}
|
|
149
|
+
if (parts.length === 2) {
|
|
150
|
+
return [parts[0], parts[1]];
|
|
151
|
+
}
|
|
152
|
+
throw new Error(
|
|
153
|
+
`--template accepts at most 2 comma-separated values (e.g. "swr,axios"). Got ${parts.length}.`
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function parseQueryParamsAsObjectArg(value) {
|
|
158
|
+
if (value === undefined) {
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const threshold = Number(value);
|
|
163
|
+
if (!Number.isInteger(threshold) || threshold < 0) {
|
|
164
|
+
throw new Error('--queryParamsAsObject threshold must be a non-negative integer');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return threshold;
|
|
168
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});var _case = require('case');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
var _swagger = require('../swagger');
|
|
5
|
+
var _utils = require('../utils/utils');
|
|
6
|
+
var _templateValidator = require('../utils/templateValidator');
|
|
7
|
+
var _genOperations = require('./genOperations');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const MOCK_FILE_HEADER = `\
|
|
12
|
+
/* istanbul ignore file -- auto-generated test helper */
|
|
13
|
+
/* tslint:disable */
|
|
14
|
+
/* eslint-disable */
|
|
15
|
+
//----------------------
|
|
16
|
+
// <auto-generated>
|
|
17
|
+
// Generated using Swaggie (https://github.com/yhnavein/swaggie)
|
|
18
|
+
// Please avoid doing any manual changes in this file
|
|
19
|
+
// </auto-generated>
|
|
20
|
+
//----------------------
|
|
21
|
+
// biome-ignore-all lint: auto-generated code
|
|
22
|
+
// deno-lint-ignore-file
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Generates a companion mock/stub file for a generated API client.
|
|
27
|
+
*
|
|
28
|
+
* The mock file exports:
|
|
29
|
+
* - Typed spy stubs for every `*Client` object method
|
|
30
|
+
* - When an L2 template (swr/tsq) is active: hook stubs wrapped with
|
|
31
|
+
* `withMockSWR` / `withMockQuery` / `withMockMutation` helpers
|
|
32
|
+
* - `queryKeys` passthrough (re-exported from the real client — not mocked)
|
|
33
|
+
*
|
|
34
|
+
* @param spec The OpenAPI document
|
|
35
|
+
* @param options Generator options (must have `testingFramework` set)
|
|
36
|
+
* @param relativeApiImport Relative import path from the mock file to the real client file
|
|
37
|
+
*/
|
|
38
|
+
function generateMocks(
|
|
39
|
+
spec,
|
|
40
|
+
options,
|
|
41
|
+
relativeApiImport
|
|
42
|
+
) {
|
|
43
|
+
const fw = options.testingFramework;
|
|
44
|
+
const template = options.template;
|
|
45
|
+
const l2 = getL2Name(template);
|
|
46
|
+
|
|
47
|
+
const operations = _swagger.getOperations.call(void 0, spec);
|
|
48
|
+
const groups = _utils.groupOperationsByGroupName.call(void 0, operations);
|
|
49
|
+
|
|
50
|
+
const groupNames = Object.keys(groups);
|
|
51
|
+
const servicePrefix = options.servicePrefix;
|
|
52
|
+
|
|
53
|
+
let result = MOCK_FILE_HEADER;
|
|
54
|
+
|
|
55
|
+
// Framework import
|
|
56
|
+
result += buildFrameworkImport(fw);
|
|
57
|
+
result += '\n';
|
|
58
|
+
|
|
59
|
+
// Real client import — needed for queryKeys passthrough (value import).
|
|
60
|
+
// Only emitted when there is an L2 (hooks layer).
|
|
61
|
+
if (l2) {
|
|
62
|
+
result += `import * as realApi from '${relativeApiImport}';\n`;
|
|
63
|
+
result += '\n';
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Helper functions (only when L2 is present)
|
|
67
|
+
if (l2 === 'swr') {
|
|
68
|
+
result += buildSwrHelpers(fw);
|
|
69
|
+
result += '\n';
|
|
70
|
+
} else if (l2 === 'tsq') {
|
|
71
|
+
result += buildTsqHelpers(fw);
|
|
72
|
+
result += '\n';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Per-group stubs
|
|
76
|
+
for (const name of groupNames) {
|
|
77
|
+
const group = groups[name];
|
|
78
|
+
const fullName = servicePrefix + name;
|
|
79
|
+
const camelName = _case.camel.call(void 0, fullName);
|
|
80
|
+
const preparedOps = _genOperations.prepareOperations.call(void 0, group, options, spec.components);
|
|
81
|
+
|
|
82
|
+
if (preparedOps.length === 0) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// *Client stub object
|
|
87
|
+
result += buildClientStub(camelName, preparedOps, fw);
|
|
88
|
+
result += '\n';
|
|
89
|
+
|
|
90
|
+
// Hooks stub object (L2 only)
|
|
91
|
+
if (l2 === 'swr') {
|
|
92
|
+
result += buildSwrHooksStub(camelName, preparedOps, fw);
|
|
93
|
+
result += '\n';
|
|
94
|
+
} else if (l2 === 'tsq') {
|
|
95
|
+
result += buildTsqHooksStub(camelName, preparedOps, fw);
|
|
96
|
+
result += '\n';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return result;
|
|
101
|
+
} exports.default = generateMocks;
|
|
102
|
+
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
// Helpers: L2 detection
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
function getL2Name(template) {
|
|
108
|
+
if (!Array.isArray(template)) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const [l2] = template;
|
|
112
|
+
if (_templateValidator.isL2Template.call(void 0, l2)) {
|
|
113
|
+
return l2 ;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Framework import line
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
function buildFrameworkImport(fw) {
|
|
123
|
+
if (fw === 'vitest') {
|
|
124
|
+
return "import { vi } from 'vitest';\n";
|
|
125
|
+
}
|
|
126
|
+
return "import { jest } from '@jest/globals';\n";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function fn(fw) {
|
|
130
|
+
return fw === 'vitest' ? 'vi.fn()' : 'jest.fn()';
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
// SWR helper functions
|
|
135
|
+
// ---------------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
function buildSwrHelpers(fw) {
|
|
138
|
+
const fnRef = fw === 'vitest' ? 'vi' : 'jest';
|
|
139
|
+
return `\
|
|
140
|
+
// ─── SWR mock helpers ────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
/** Augments a spy with a \`mockSWR\` shorthand for useSWR query hooks. */
|
|
143
|
+
function withMockSWR<T extends ReturnType<typeof ${fnRef}.fn>>(spy: T) {
|
|
144
|
+
return Object.assign(spy, {
|
|
145
|
+
mockSWR({ data, isLoading, error }: { data?: unknown; isLoading?: boolean; error?: Error }) {
|
|
146
|
+
spy.mockReturnValue({ data, isLoading: isLoading ?? false, error: error ?? null, mutate: ${fn(fw)} });
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Augments a spy with a \`mockSWRMutation\` shorthand for useSWRMutation hooks. */
|
|
152
|
+
function withMockSWRMutation<T extends ReturnType<typeof ${fnRef}.fn>>(spy: T) {
|
|
153
|
+
return Object.assign(spy, {
|
|
154
|
+
mockSWRMutation({ data, isMutating, error }: { data?: unknown; isMutating?: boolean; error?: Error }) {
|
|
155
|
+
spy.mockReturnValue({ trigger: ${fn(fw)}, isMutating: isMutating ?? false, error: error ?? null, data });
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ---------------------------------------------------------------------------
|
|
163
|
+
// TanStack Query helper functions
|
|
164
|
+
// ---------------------------------------------------------------------------
|
|
165
|
+
|
|
166
|
+
function buildTsqHelpers(fw) {
|
|
167
|
+
const fnRef = fw === 'vitest' ? 'vi' : 'jest';
|
|
168
|
+
return `\
|
|
169
|
+
// ─── TanStack Query mock helpers ─────────────────────────────────────────────
|
|
170
|
+
|
|
171
|
+
/** Augments a spy with a \`mockQuery\` shorthand for useQuery hooks. */
|
|
172
|
+
function withMockQuery<T extends ReturnType<typeof ${fnRef}.fn>>(spy: T) {
|
|
173
|
+
return Object.assign(spy, {
|
|
174
|
+
mockQuery({ data, isLoading, error }: { data?: unknown; isLoading?: boolean; error?: Error }) {
|
|
175
|
+
const pending = isLoading ?? false;
|
|
176
|
+
spy.mockReturnValue({
|
|
177
|
+
data,
|
|
178
|
+
isLoading: pending,
|
|
179
|
+
isPending: pending,
|
|
180
|
+
isFetching: false,
|
|
181
|
+
isSuccess: data !== undefined && !pending,
|
|
182
|
+
error: error ?? null,
|
|
183
|
+
refetch: ${fn(fw)},
|
|
184
|
+
status: pending ? 'pending' : 'success',
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Augments a spy with a \`mockMutation\` shorthand for useMutation hooks. */
|
|
191
|
+
function withMockMutation<T extends ReturnType<typeof ${fnRef}.fn>>(spy: T) {
|
|
192
|
+
return Object.assign(spy, {
|
|
193
|
+
mockMutation({ data, isPending, error }: { data?: unknown; isPending?: boolean; error?: Error }) {
|
|
194
|
+
spy.mockReturnValue({
|
|
195
|
+
mutate: ${fn(fw)},
|
|
196
|
+
mutateAsync: ${fn(fw)},
|
|
197
|
+
isPending: isPending ?? false,
|
|
198
|
+
isSuccess: false,
|
|
199
|
+
error: error ?? null,
|
|
200
|
+
data,
|
|
201
|
+
reset: ${fn(fw)},
|
|
202
|
+
});
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
`;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// *Client stub object
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
function buildClientStub(
|
|
214
|
+
camelName,
|
|
215
|
+
operations,
|
|
216
|
+
fw
|
|
217
|
+
) {
|
|
218
|
+
const lines = operations.map((op) => ` ${op.name}: ${fn(fw)},`);
|
|
219
|
+
return `export const ${camelName}Client = {\n${lines.join('\n')}\n};\n`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
// SWR hooks stub object
|
|
224
|
+
// ---------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
function buildSwrHooksStub(
|
|
227
|
+
camelName,
|
|
228
|
+
operations,
|
|
229
|
+
fw
|
|
230
|
+
) {
|
|
231
|
+
const getOps = operations.filter((o) => o.method === 'GET');
|
|
232
|
+
const mutOps = operations.filter((o) => o.method !== 'GET');
|
|
233
|
+
|
|
234
|
+
const queryLines = getOps.map((op) => {
|
|
235
|
+
const hookName = toHookName(op.name, 'use');
|
|
236
|
+
return ` ${hookName}: withMockSWR(${fn(fw)}),`;
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
const mutationLines = mutOps.map((op) => {
|
|
240
|
+
const hookName = 'use' + _case.pascal.call(void 0, op.name);
|
|
241
|
+
return ` ${hookName}: withMockSWRMutation(${fn(fw)}),`;
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
return [
|
|
245
|
+
`export const ${camelName} = {`,
|
|
246
|
+
` queries: {`,
|
|
247
|
+
...queryLines,
|
|
248
|
+
` },`,
|
|
249
|
+
``,
|
|
250
|
+
` mutations: {`,
|
|
251
|
+
...mutationLines,
|
|
252
|
+
` },`,
|
|
253
|
+
``,
|
|
254
|
+
` // queryKeys are pure functions — no mocking needed`,
|
|
255
|
+
` queryKeys: realApi.${camelName}.queryKeys,`,
|
|
256
|
+
`};\n`,
|
|
257
|
+
].join('\n');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ---------------------------------------------------------------------------
|
|
261
|
+
// TanStack Query hooks stub object
|
|
262
|
+
// ---------------------------------------------------------------------------
|
|
263
|
+
|
|
264
|
+
function buildTsqHooksStub(
|
|
265
|
+
camelName,
|
|
266
|
+
operations,
|
|
267
|
+
fw
|
|
268
|
+
) {
|
|
269
|
+
const getOps = operations.filter((o) => o.method === 'GET');
|
|
270
|
+
const mutOps = operations.filter((o) => o.method !== 'GET');
|
|
271
|
+
|
|
272
|
+
const queryLines = getOps.map((op) => {
|
|
273
|
+
const hookName = toHookName(op.name, 'use');
|
|
274
|
+
return ` ${hookName}: withMockQuery(${fn(fw)}),`;
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
const mutationLines = mutOps.map((op) => {
|
|
278
|
+
const hookName = 'use' + _case.pascal.call(void 0, op.name);
|
|
279
|
+
return ` ${hookName}: withMockMutation(${fn(fw)}),`;
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
return [
|
|
283
|
+
`export const ${camelName} = {`,
|
|
284
|
+
` queries: {`,
|
|
285
|
+
...queryLines,
|
|
286
|
+
` },`,
|
|
287
|
+
``,
|
|
288
|
+
` mutations: {`,
|
|
289
|
+
...mutationLines,
|
|
290
|
+
` },`,
|
|
291
|
+
``,
|
|
292
|
+
` // queryKeys are pure functions — no mocking needed`,
|
|
293
|
+
` queryKeys: realApi.${camelName}.queryKeys,`,
|
|
294
|
+
`};\n`,
|
|
295
|
+
].join('\n');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ---------------------------------------------------------------------------
|
|
299
|
+
// Shared name helpers
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Converts an operation name to a React hook name.
|
|
304
|
+
* Strips a leading "get" prefix (case-insensitive) and capitalises, then
|
|
305
|
+
* prepends the given prefix (e.g. "use").
|
|
306
|
+
*
|
|
307
|
+
* @example toHookName('findPetsByStatus', 'use') → 'useFindPetsByStatus'
|
|
308
|
+
* @example toHookName('getPetById', 'use') → 'usePetById'
|
|
309
|
+
*/
|
|
310
|
+
function toHookName(operationName, prefix) {
|
|
311
|
+
const stripped = operationName.toLowerCase().startsWith('get')
|
|
312
|
+
? operationName.slice(3)
|
|
313
|
+
: operationName;
|
|
314
|
+
const capitalised = stripped.charAt(0).toUpperCase() + stripped.slice(1);
|
|
315
|
+
return prefix + capitalised;
|
|
316
|
+
}
|