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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -16
  3. package/index.mjs +54 -50
  4. 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
- - TypeScript 5.x
200
- - Node.js 16+ or Bun
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-api.ts";
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(resolve(process.cwd(), outPut), contents, {
33
- overwrite: true
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-swagger-client",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Generate fully type-safe REST API clients from OpenAPI/Swagger specifications",
5
5
  "main": "./index.mjs",
6
6
  "module": "./index.mjs",