create-swagger-client 0.1.2 → 0.1.4
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/LICENSE +21 -0
- package/README.md +9 -16
- package/index.mjs +54 -50
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Cuong Nguyen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -11,6 +11,13 @@ A TypeScript tool that generates a fully type-safe REST API client from OpenAPI/
|
|
|
11
11
|
- 🌐 **URL or File Input**: Generate from remote URLs or local files
|
|
12
12
|
- 🎯 **Type Inference**: Automatic extraction of path params, query params, headers, and request/response types
|
|
13
13
|
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g create-swagger-client
|
|
18
|
+
# or
|
|
19
|
+
npx create-swagger-client
|
|
20
|
+
```
|
|
14
21
|
|
|
15
22
|
## Usage
|
|
16
23
|
|
|
@@ -180,24 +187,10 @@ try {
|
|
|
180
187
|
}
|
|
181
188
|
```
|
|
182
189
|
|
|
183
|
-
## Development
|
|
184
|
-
|
|
185
|
-
### Build
|
|
186
|
-
|
|
187
|
-
```bash
|
|
188
|
-
bun run build
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
### Type Check
|
|
192
|
-
|
|
193
|
-
```bash
|
|
194
|
-
bun run typecheck
|
|
195
|
-
```
|
|
196
|
-
|
|
197
190
|
## Requirements
|
|
198
191
|
|
|
199
|
-
-
|
|
200
|
-
-
|
|
192
|
+
- Node.js 16+ (for running the CLI)
|
|
193
|
+
- TypeScript 5.x (peer dependency for generated types)
|
|
201
194
|
|
|
202
195
|
## License
|
|
203
196
|
|
package/index.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import * as tsMorph from "ts-morph";
|
|
|
6
6
|
|
|
7
7
|
var args = process.argv.slice(2);
|
|
8
8
|
var source = args[0];
|
|
9
|
-
var outPut = args[1] || "client
|
|
9
|
+
var outPut = args[1] || "swagger-client.ts";
|
|
10
10
|
if (!source) {
|
|
11
11
|
console.error("Please provide a source URL or file path.");
|
|
12
12
|
process.exit(1);
|
|
@@ -20,45 +20,48 @@ var isUrl = (str) => {
|
|
|
20
20
|
}
|
|
21
21
|
};
|
|
22
22
|
async function generate() {
|
|
23
|
-
if (!source)
|
|
24
|
-
return;
|
|
23
|
+
if (!source) return;
|
|
25
24
|
if (isUrl(source) === false) {
|
|
26
25
|
source = resolve(process.cwd(), source);
|
|
27
26
|
}
|
|
28
27
|
const spinner = ora(`Generating API client from ${source}...`).start();
|
|
29
28
|
const ast = await openapiTS(source);
|
|
30
29
|
const contents = astToString(ast);
|
|
31
|
-
const project = new tsMorph.Project;
|
|
32
|
-
const sourceFile = project.createSourceFile(
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
const project = new tsMorph.Project();
|
|
31
|
+
const sourceFile = project.createSourceFile(
|
|
32
|
+
resolve(process.cwd(), outPut),
|
|
33
|
+
contents,
|
|
34
|
+
{
|
|
35
|
+
overwrite: true,
|
|
36
|
+
},
|
|
37
|
+
);
|
|
35
38
|
sourceFile.addTypeAlias({
|
|
36
39
|
name: "RestMethod",
|
|
37
40
|
isExported: true,
|
|
38
|
-
type: '"get" | "post" | "put" | "delete" | "patch"'
|
|
41
|
+
type: '"get" | "post" | "put" | "delete" | "patch"',
|
|
39
42
|
});
|
|
40
43
|
sourceFile.addTypeAlias({
|
|
41
44
|
name: "KeyPaths",
|
|
42
45
|
isExported: true,
|
|
43
|
-
type: "keyof paths"
|
|
46
|
+
type: "keyof paths",
|
|
44
47
|
});
|
|
45
48
|
sourceFile.addTypeAlias({
|
|
46
49
|
name: "ExtractPathParams",
|
|
47
50
|
isExported: true,
|
|
48
51
|
typeParameters: ["T extends KeyPaths", "K extends RestMethod"],
|
|
49
|
-
type: "paths[T][K] extends { parameters: { path?: infer P } } ? P : never"
|
|
52
|
+
type: "paths[T][K] extends { parameters: { path?: infer P } } ? P : never",
|
|
50
53
|
});
|
|
51
54
|
sourceFile.addTypeAlias({
|
|
52
55
|
name: "ExtractQueryParams",
|
|
53
56
|
isExported: true,
|
|
54
57
|
typeParameters: ["T extends KeyPaths", "K extends RestMethod"],
|
|
55
|
-
type: "paths[T][K] extends { parameters: { query?: infer Q } } ? Q : never"
|
|
58
|
+
type: "paths[T][K] extends { parameters: { query?: infer Q } } ? Q : never",
|
|
56
59
|
});
|
|
57
60
|
sourceFile.addTypeAlias({
|
|
58
61
|
name: "ExtractHeaderParams",
|
|
59
62
|
isExported: true,
|
|
60
63
|
typeParameters: ["T extends KeyPaths", "K extends RestMethod"],
|
|
61
|
-
type: "paths[T][K] extends { parameters: { header?: infer H } } ? H : never"
|
|
64
|
+
type: "paths[T][K] extends { parameters: { header?: infer H } } ? H : never",
|
|
62
65
|
});
|
|
63
66
|
sourceFile.addTypeAlias({
|
|
64
67
|
name: "ExtractBody",
|
|
@@ -68,7 +71,7 @@ async function generate() {
|
|
|
68
71
|
requestBody: { content: { "application/json": infer B } };
|
|
69
72
|
}
|
|
70
73
|
? B
|
|
71
|
-
: never
|
|
74
|
+
: never`,
|
|
72
75
|
});
|
|
73
76
|
sourceFile.addTypeAlias({
|
|
74
77
|
name: "APIResponse",
|
|
@@ -80,7 +83,7 @@ async function generate() {
|
|
|
80
83
|
| { [code: number]: { content: { "application/json": infer R } } };
|
|
81
84
|
}
|
|
82
85
|
? R
|
|
83
|
-
: unknown
|
|
86
|
+
: unknown`,
|
|
84
87
|
});
|
|
85
88
|
sourceFile.addTypeAlias({
|
|
86
89
|
name: "ApiPayload",
|
|
@@ -91,7 +94,7 @@ async function generate() {
|
|
|
91
94
|
query?: ExtractQueryParams<T, K>;
|
|
92
95
|
body?: K extends "post" | "put" | "patch" ? ExtractBody<T, K> : never;
|
|
93
96
|
headers?: ExtractHeaderParams<T, K>;
|
|
94
|
-
}
|
|
97
|
+
}`,
|
|
95
98
|
});
|
|
96
99
|
sourceFile.addTypeAlias({
|
|
97
100
|
name: "ApiClientType",
|
|
@@ -101,14 +104,14 @@ async function generate() {
|
|
|
101
104
|
path: T,
|
|
102
105
|
payload?: ApiPayload<T, K>,
|
|
103
106
|
) => Promise<APIResponse<T, K>>;
|
|
104
|
-
}
|
|
107
|
+
}`,
|
|
105
108
|
});
|
|
106
109
|
sourceFile.addTypeAlias({
|
|
107
110
|
name: "TypePaths",
|
|
108
111
|
typeParameters: ["T extends RestMethod"],
|
|
109
112
|
type: `{
|
|
110
113
|
[K in KeyPaths]: paths[K] extends { [M in T]: unknown } ? K : never;
|
|
111
|
-
}[KeyPaths]
|
|
114
|
+
}[KeyPaths]`,
|
|
112
115
|
});
|
|
113
116
|
sourceFile.addClass({
|
|
114
117
|
name: "RestApiClient",
|
|
@@ -121,10 +124,10 @@ async function generate() {
|
|
|
121
124
|
name: "option",
|
|
122
125
|
type: "RequestInit",
|
|
123
126
|
hasQuestionToken: true,
|
|
124
|
-
scope: tsMorph.Scope.Private
|
|
125
|
-
}
|
|
126
|
-
]
|
|
127
|
-
}
|
|
127
|
+
scope: tsMorph.Scope.Private,
|
|
128
|
+
},
|
|
129
|
+
],
|
|
130
|
+
},
|
|
128
131
|
],
|
|
129
132
|
methods: [
|
|
130
133
|
{
|
|
@@ -133,7 +136,7 @@ async function generate() {
|
|
|
133
136
|
isAsync: true,
|
|
134
137
|
parameters: [
|
|
135
138
|
{ name: "input", type: "RequestInfo" },
|
|
136
|
-
{ name: "init", type: "RequestInit", hasQuestionToken: true }
|
|
139
|
+
{ name: "init", type: "RequestInit", hasQuestionToken: true },
|
|
137
140
|
],
|
|
138
141
|
statements: `const headers = {
|
|
139
142
|
"Content-Type": "application/json",
|
|
@@ -147,7 +150,7 @@ async function generate() {
|
|
|
147
150
|
\`API request failed: \${response.status} \${response.statusText} - \${errorBody}\`,
|
|
148
151
|
);
|
|
149
152
|
}
|
|
150
|
-
return response.json()
|
|
153
|
+
return response.json();`,
|
|
151
154
|
},
|
|
152
155
|
{
|
|
153
156
|
name: "request",
|
|
@@ -158,8 +161,8 @@ async function generate() {
|
|
|
158
161
|
{
|
|
159
162
|
name: "init",
|
|
160
163
|
type: "ApiPayload<P, M>",
|
|
161
|
-
initializer: "{} as ApiPayload<P, M>"
|
|
162
|
-
}
|
|
164
|
+
initializer: "{} as ApiPayload<P, M>",
|
|
165
|
+
},
|
|
163
166
|
],
|
|
164
167
|
returnType: "Promise<APIResponse<P, M>>",
|
|
165
168
|
statements: `const url = new URL(this.basePath + String(path));
|
|
@@ -179,7 +182,7 @@ async function generate() {
|
|
|
179
182
|
|
|
180
183
|
return this.fetcher(url.toString(), requestInit) as Promise<
|
|
181
184
|
APIResponse<P, M>
|
|
182
|
-
|
|
185
|
+
>;`,
|
|
183
186
|
},
|
|
184
187
|
{
|
|
185
188
|
name: "get",
|
|
@@ -190,11 +193,11 @@ async function generate() {
|
|
|
190
193
|
{
|
|
191
194
|
name: "payload",
|
|
192
195
|
type: 'ApiPayload<T, "get">',
|
|
193
|
-
hasQuestionToken: true
|
|
194
|
-
}
|
|
196
|
+
hasQuestionToken: true,
|
|
197
|
+
},
|
|
195
198
|
],
|
|
196
199
|
returnType: 'Promise<APIResponse<T, "get">>',
|
|
197
|
-
statements: 'return this.request("get", path, payload);'
|
|
200
|
+
statements: 'return this.request("get", path, payload);',
|
|
198
201
|
},
|
|
199
202
|
{
|
|
200
203
|
name: "post",
|
|
@@ -205,11 +208,11 @@ async function generate() {
|
|
|
205
208
|
{
|
|
206
209
|
name: "payload",
|
|
207
210
|
type: 'ApiPayload<T, "post">',
|
|
208
|
-
hasQuestionToken: true
|
|
209
|
-
}
|
|
211
|
+
hasQuestionToken: true,
|
|
212
|
+
},
|
|
210
213
|
],
|
|
211
214
|
returnType: 'Promise<APIResponse<T, "post">>',
|
|
212
|
-
statements: 'return this.request("post", path, payload);'
|
|
215
|
+
statements: 'return this.request("post", path, payload);',
|
|
213
216
|
},
|
|
214
217
|
{
|
|
215
218
|
name: "put",
|
|
@@ -220,11 +223,11 @@ async function generate() {
|
|
|
220
223
|
{
|
|
221
224
|
name: "payload",
|
|
222
225
|
type: 'ApiPayload<T, "put">',
|
|
223
|
-
hasQuestionToken: true
|
|
224
|
-
}
|
|
226
|
+
hasQuestionToken: true,
|
|
227
|
+
},
|
|
225
228
|
],
|
|
226
229
|
returnType: 'Promise<APIResponse<T, "put">>',
|
|
227
|
-
statements: 'return this.request("put", path, payload);'
|
|
230
|
+
statements: 'return this.request("put", path, payload);',
|
|
228
231
|
},
|
|
229
232
|
{
|
|
230
233
|
name: "delete",
|
|
@@ -235,11 +238,11 @@ async function generate() {
|
|
|
235
238
|
{
|
|
236
239
|
name: "payload",
|
|
237
240
|
type: 'ApiPayload<T, "delete">',
|
|
238
|
-
hasQuestionToken: true
|
|
239
|
-
}
|
|
241
|
+
hasQuestionToken: true,
|
|
242
|
+
},
|
|
240
243
|
],
|
|
241
244
|
returnType: 'Promise<APIResponse<T, "delete">>',
|
|
242
|
-
statements: 'return this.request("delete", path, payload);'
|
|
245
|
+
statements: 'return this.request("delete", path, payload);',
|
|
243
246
|
},
|
|
244
247
|
{
|
|
245
248
|
name: "patch",
|
|
@@ -250,18 +253,18 @@ async function generate() {
|
|
|
250
253
|
{
|
|
251
254
|
name: "payload",
|
|
252
255
|
type: 'ApiPayload<T, "patch">',
|
|
253
|
-
hasQuestionToken: true
|
|
254
|
-
}
|
|
256
|
+
hasQuestionToken: true,
|
|
257
|
+
},
|
|
255
258
|
],
|
|
256
259
|
returnType: 'Promise<APIResponse<T, "patch">>',
|
|
257
|
-
statements: 'return this.request("patch", path, payload);'
|
|
260
|
+
statements: 'return this.request("patch", path, payload);',
|
|
258
261
|
},
|
|
259
262
|
{
|
|
260
263
|
name: "buildPathUrl",
|
|
261
264
|
scope: tsMorph.Scope.Private,
|
|
262
265
|
parameters: [
|
|
263
266
|
{ name: "basePath", type: "string" },
|
|
264
|
-
{ name: "pathParams", type: "unknown", hasQuestionToken: true }
|
|
267
|
+
{ name: "pathParams", type: "unknown", hasQuestionToken: true },
|
|
265
268
|
],
|
|
266
269
|
returnType: "string",
|
|
267
270
|
statements: `let pathname = basePath;
|
|
@@ -271,27 +274,27 @@ async function generate() {
|
|
|
271
274
|
encodeURIComponent(String(params[key])),
|
|
272
275
|
);
|
|
273
276
|
}
|
|
274
|
-
return pathname
|
|
277
|
+
return pathname;`,
|
|
275
278
|
},
|
|
276
279
|
{
|
|
277
280
|
name: "prepareBody",
|
|
278
281
|
scope: tsMorph.Scope.Private,
|
|
279
282
|
parameters: [
|
|
280
283
|
{ name: "method", type: "RestMethod" },
|
|
281
|
-
{ name: "body", type: "unknown", hasQuestionToken: true }
|
|
284
|
+
{ name: "body", type: "unknown", hasQuestionToken: true },
|
|
282
285
|
],
|
|
283
286
|
returnType: "string | undefined",
|
|
284
287
|
statements: `if (body && ["post", "put", "patch"].includes(method)) {
|
|
285
288
|
return JSON.stringify(body);
|
|
286
289
|
}
|
|
287
|
-
return undefined
|
|
290
|
+
return undefined;`,
|
|
288
291
|
},
|
|
289
292
|
{
|
|
290
293
|
name: "appendQueryParams",
|
|
291
294
|
scope: tsMorph.Scope.Private,
|
|
292
295
|
parameters: [
|
|
293
296
|
{ name: "url", type: "URL" },
|
|
294
|
-
{ name: "queryParams", type: "unknown", hasQuestionToken: true }
|
|
297
|
+
{ name: "queryParams", type: "unknown", hasQuestionToken: true },
|
|
295
298
|
],
|
|
296
299
|
returnType: "void",
|
|
297
300
|
statements: `if (queryParams != null) {
|
|
@@ -301,15 +304,16 @@ async function generate() {
|
|
|
301
304
|
url.searchParams.append(key, String(value));
|
|
302
305
|
}
|
|
303
306
|
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
]
|
|
307
|
+
}`,
|
|
308
|
+
},
|
|
309
|
+
],
|
|
307
310
|
});
|
|
308
311
|
await sourceFile.formatText();
|
|
309
312
|
await project.save();
|
|
310
313
|
spinner.stopAndPersist({
|
|
311
314
|
symbol: "✔",
|
|
312
|
-
text: `API client generated at ${resolve(process.cwd(), outPut)}
|
|
315
|
+
text: `API client generated at ${resolve(process.cwd(), outPut)}`,
|
|
313
316
|
});
|
|
314
317
|
}
|
|
318
|
+
|
|
315
319
|
generate();
|