apigen-ts 0.0.3 → 0.1.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/dist/_template.ts +17 -12
- package/dist/cli.cjs +18 -0
- package/dist/cli.js +12 -527
- package/dist/cli.mjs +16 -0
- package/dist/main-0ae61014.cjs +582 -0
- package/dist/main-0c2fa229.js +559 -0
- package/dist/main-0c2fa229.mjs +559 -0
- package/dist/main.cjs +17 -0
- package/dist/main.d.cts +24 -0
- package/dist/main.d.mts +24 -0
- package/dist/main.js +11 -0
- package/dist/main.mjs +11 -0
- package/package.json +18 -8
- package/readme.md +45 -9
package/dist/_template.ts
CHANGED
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
// Use uppercase for names in ApiClient to avoid conflict with the generated code
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
// Note: Use uppercase for names in ApiClient to avoid conflict with the generated code
|
|
2
|
+
|
|
3
|
+
interface ApigenConfig {
|
|
4
|
+
baseUrl: string
|
|
5
|
+
headers: Record<string, string>
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface ApigenRequest extends Omit<RequestInit, "body"> {
|
|
9
|
+
search?: Record<string, unknown>
|
|
10
|
+
body?: unknown
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
export class ApiClient {
|
|
13
14
|
ISO_FORMAT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d*)?(?:[-+]\d{2}:?\d{2}|Z)?$/
|
|
14
|
-
Config:
|
|
15
|
+
Config: ApigenConfig
|
|
15
16
|
|
|
16
|
-
constructor(config?: Partial<
|
|
17
|
+
constructor(config?: Partial<ApigenConfig>) {
|
|
17
18
|
this.Config = { baseUrl: "/", headers: {}, ...config }
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -39,7 +40,7 @@ export class ApiClient {
|
|
|
39
40
|
}
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
async Fetch<T>(method: string, path: string, opts:
|
|
43
|
+
async Fetch<T>(method: string, path: string, opts: ApigenRequest = {}): Promise<T> {
|
|
43
44
|
let base = this.Config.baseUrl
|
|
44
45
|
if (globalThis.location && (base === "" || base.startsWith("/"))) {
|
|
45
46
|
base = `${globalThis.location.origin}${base.endsWith("/") ? base : `/${base}`}`
|
|
@@ -79,4 +80,8 @@ export class ApiClient {
|
|
|
79
80
|
return rs as unknown as T
|
|
80
81
|
}
|
|
81
82
|
}
|
|
83
|
+
|
|
84
|
+
// apigen:modules
|
|
82
85
|
}
|
|
86
|
+
|
|
87
|
+
// apigen:types
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var main$1 = require('./main-0ae61014.cjs');
|
|
4
|
+
require('fs/promises');
|
|
5
|
+
require('path');
|
|
6
|
+
require('url');
|
|
7
|
+
require('cleye');
|
|
8
|
+
require('@redocly/openapi-core');
|
|
9
|
+
require('array-utils-ts');
|
|
10
|
+
require('lodash-es');
|
|
11
|
+
require('swagger2openapi');
|
|
12
|
+
require('typescript');
|
|
13
|
+
require('prettier');
|
|
14
|
+
|
|
15
|
+
const main = async () => {
|
|
16
|
+
await main$1.apigen(main$1.getCliConfig());
|
|
17
|
+
};
|
|
18
|
+
main();
|
package/dist/cli.js
CHANGED
|
@@ -1,532 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
const f = ts.factory;
|
|
14
|
-
const HttpMethods = ["get", "post", "put", "patch", "delete", "head", "options", "trace"];
|
|
15
|
-
const Keywords = /* @__PURE__ */ new Set([
|
|
16
|
-
"break",
|
|
17
|
-
"case",
|
|
18
|
-
"catch",
|
|
19
|
-
"class",
|
|
20
|
-
"const",
|
|
21
|
-
"continue",
|
|
22
|
-
"debugger",
|
|
23
|
-
"default",
|
|
24
|
-
"delete",
|
|
25
|
-
"do",
|
|
26
|
-
"else",
|
|
27
|
-
"enum",
|
|
28
|
-
"export",
|
|
29
|
-
"extends",
|
|
30
|
-
"false",
|
|
31
|
-
"finally",
|
|
32
|
-
"for",
|
|
33
|
-
"function",
|
|
34
|
-
"if",
|
|
35
|
-
"import",
|
|
36
|
-
"in",
|
|
37
|
-
"instanceof",
|
|
38
|
-
"new",
|
|
39
|
-
"null",
|
|
40
|
-
"return",
|
|
41
|
-
"super",
|
|
42
|
-
"switch",
|
|
43
|
-
"this",
|
|
44
|
-
"throw",
|
|
45
|
-
"true",
|
|
46
|
-
"try",
|
|
47
|
-
"typeof",
|
|
48
|
-
"var",
|
|
49
|
-
"void",
|
|
50
|
-
"while",
|
|
51
|
-
"with",
|
|
52
|
-
"implements",
|
|
53
|
-
"interface",
|
|
54
|
-
"let",
|
|
55
|
-
"package",
|
|
56
|
-
"private",
|
|
57
|
-
"protected",
|
|
58
|
-
"public",
|
|
59
|
-
"static",
|
|
60
|
-
"yield",
|
|
61
|
-
"any",
|
|
62
|
-
"boolean",
|
|
63
|
-
"number",
|
|
64
|
-
"string",
|
|
65
|
-
"symbol",
|
|
66
|
-
// "abstract", "as", "async", "await", "constructor", "declare", "from", "get", "is", "module",
|
|
67
|
-
// "namespace", "of", "require", "set", "type",
|
|
68
|
-
"Record",
|
|
69
|
-
"Partial",
|
|
70
|
-
"Pick",
|
|
71
|
-
"Omit",
|
|
72
|
-
"Exclude",
|
|
73
|
-
"Extract",
|
|
74
|
-
// ts keywords
|
|
75
|
-
"Date",
|
|
76
|
-
"object"
|
|
77
|
-
// ts type names
|
|
78
|
-
]);
|
|
79
|
-
const initCtx = (doc, cfg) => {
|
|
80
|
-
return { name: "ApiClient", ...cfg, doc, tag: "", opNames: /* @__PURE__ */ new Set() };
|
|
81
|
-
};
|
|
82
|
-
const normalizeIdentifier = (val, asVar = false) => {
|
|
83
|
-
let name = val.replace("#/components/schemas/", "").replaceAll("'", "").replace(/[^a-zA-Z0-9]/g, "_");
|
|
84
|
-
if (name.match(/^\d/))
|
|
85
|
-
name = `$${name}`;
|
|
86
|
-
if (asVar && Keywords.has(name))
|
|
87
|
-
name = `$${name}`;
|
|
88
|
-
return name;
|
|
89
|
-
};
|
|
90
|
-
const normalizeOpName = (val) => {
|
|
91
|
-
const articles = /* @__PURE__ */ new Set(["a", "an", "the"]);
|
|
92
|
-
const tmp = val.replace(/'/, "").replace(/[^a-zA-Z0-9]/g, "_").split("_").filter((x) => x !== "" && !articles.has(x)).map((x) => upperFirst(x));
|
|
93
|
-
tmp[0] = tmp[0].toUpperCase() === tmp[0] ? tmp[0].toLowerCase() : lowerFirst(tmp[0]);
|
|
94
|
-
return tmp.join("");
|
|
95
|
-
};
|
|
96
|
-
const getOpName = (ctx, cfg) => {
|
|
97
|
-
let ns = normalizeOpName(filterEmpty(cfg.tags ?? [])[0] ?? "general");
|
|
98
|
-
let op = cfg.operationId ?? null;
|
|
99
|
-
if (!op) {
|
|
100
|
-
op = cfg.path.replace(/^(\/api)?(\/v?\d\.?\d?)?\/(.+)$/, "$3");
|
|
101
|
-
op = `${cfg.method}/${op}`.replace(/\/+/, "/");
|
|
102
|
-
}
|
|
103
|
-
op = normalizeOpName(op);
|
|
104
|
-
let nsr = ns.split("").map((x) => `[${x.toUpperCase()}${x.toLowerCase()}]`).join("");
|
|
105
|
-
if (nsr.endsWith("[Ss]"))
|
|
106
|
-
nsr += "?";
|
|
107
|
-
op = op.replace(new RegExp(`^${nsr}([Cc]ontroller|[Ss]ervice)?([A-Z].*)$`), "$2");
|
|
108
|
-
op = lowerFirst(op);
|
|
109
|
-
return [ns, op];
|
|
110
|
-
};
|
|
111
|
-
const makeInlineEnum = (s) => {
|
|
112
|
-
if (!s.enum)
|
|
113
|
-
return void 0;
|
|
114
|
-
const values = filterEmpty(s.enum);
|
|
115
|
-
if (!values.length)
|
|
116
|
-
return void 0;
|
|
117
|
-
if (!s.type) {
|
|
118
|
-
if (values.every((x) => typeof x === "string"))
|
|
119
|
-
s.type = "string";
|
|
120
|
-
if (values.every((x) => typeof x === "number"))
|
|
121
|
-
s.type = "number";
|
|
122
|
-
if (values.every((x) => typeof x === "boolean"))
|
|
123
|
-
s.type = "boolean";
|
|
124
|
-
}
|
|
125
|
-
if (s.type === "string") {
|
|
126
|
-
const tokens = uniq(values).map((x) => f.createStringLiteral(x.toString()));
|
|
127
|
-
return f.createUnionTypeNode(tokens.map((x) => f.createLiteralTypeNode(x)));
|
|
128
|
-
}
|
|
129
|
-
if (s.type === "number") {
|
|
130
|
-
const tokens = uniq(values).map((x) => f.createNumericLiteral(x));
|
|
131
|
-
return f.createUnionTypeNode(tokens.map((x) => f.createLiteralTypeNode(x)));
|
|
132
|
-
}
|
|
133
|
-
if (s.type === "boolean") {
|
|
134
|
-
const tokens = [];
|
|
135
|
-
if (values.includes(true))
|
|
136
|
-
tokens.push(f.createToken(ts.SyntaxKind.TrueKeyword));
|
|
137
|
-
if (values.includes(false))
|
|
138
|
-
tokens.push(f.createToken(ts.SyntaxKind.FalseKeyword));
|
|
139
|
-
return f.createUnionTypeNode(tokens.map((x) => f.createLiteralTypeNode(x)));
|
|
140
|
-
}
|
|
141
|
-
console.warn(`enum with unknown type ${s.type}`, s);
|
|
142
|
-
return void 0;
|
|
143
|
-
};
|
|
144
|
-
const makeType = (ctx, s) => {
|
|
145
|
-
const mk = makeType.bind(null, ctx);
|
|
146
|
-
if (s === void 0)
|
|
147
|
-
return f.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
148
|
-
if (s === null)
|
|
149
|
-
return f.createLiteralTypeNode(f.createNull());
|
|
150
|
-
if ("$ref" in s && s.$ref) {
|
|
151
|
-
const parts = s.$ref.replace("#/", "").split("/");
|
|
152
|
-
if (parts.length === 3 && parts[0] === "components" && parts[1] === "schemas") {
|
|
153
|
-
return f.createTypeReferenceNode(normalizeIdentifier(parts[2], true));
|
|
154
|
-
}
|
|
155
|
-
const t = unref(ctx, s);
|
|
156
|
-
if (!t)
|
|
157
|
-
throw new Error(`makeTypeRef: ref not found ${JSON.stringify(s)}`);
|
|
158
|
-
return makeType(ctx, t);
|
|
159
|
-
}
|
|
160
|
-
if ("oneOf" in s && s.oneOf)
|
|
161
|
-
return f.createUnionTypeNode(s.oneOf.map(mk));
|
|
162
|
-
if ("anyOf" in s && s.anyOf)
|
|
163
|
-
return f.createUnionTypeNode(s.anyOf.map(mk));
|
|
164
|
-
if ("allOf" in s && s.allOf)
|
|
165
|
-
return f.createIntersectionTypeNode(s.allOf.map(mk));
|
|
166
|
-
if ("type" in s && s.type === "integer")
|
|
167
|
-
s.type = "number";
|
|
168
|
-
if ("enum" in s && s.enum && !Array.isArray(s.type)) {
|
|
169
|
-
const isArray2 = s.type === "array";
|
|
170
|
-
const t = makeInlineEnum(isArray2 ? { ...s, type: s.items?.type } : s);
|
|
171
|
-
if (t)
|
|
172
|
-
return isArray2 ? f.createArrayTypeNode(t) : t;
|
|
173
|
-
}
|
|
174
|
-
if ("properties" in s && s.properties) {
|
|
175
|
-
return f.createTypeLiteralNode(
|
|
176
|
-
Object.entries(s.properties).map(([k, v]) => {
|
|
177
|
-
const r = s.required ?? [];
|
|
178
|
-
const q = r.includes(k) ? void 0 : f.createToken(ts.SyntaxKind.QuestionToken);
|
|
179
|
-
return f.createPropertySignature(void 0, f.createStringLiteral(k), q, mk(v));
|
|
180
|
-
})
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
if ("type" in s) {
|
|
184
|
-
if (Array.isArray(s.type)) {
|
|
185
|
-
const types = [];
|
|
186
|
-
for (const type of s.type) {
|
|
187
|
-
if (type === "null")
|
|
188
|
-
types.push({ type: "null" });
|
|
189
|
-
else
|
|
190
|
-
types.push({ ...s, type });
|
|
191
|
-
}
|
|
192
|
-
return mk({ oneOf: types });
|
|
193
|
-
}
|
|
194
|
-
let t;
|
|
195
|
-
if (s.type === "object")
|
|
196
|
-
t = f.createKeywordTypeNode(ts.SyntaxKind.ObjectKeyword);
|
|
197
|
-
else if (s.type === "boolean")
|
|
198
|
-
t = f.createKeywordTypeNode(ts.SyntaxKind.BooleanKeyword);
|
|
199
|
-
else if (s.type === "number")
|
|
200
|
-
t = f.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword);
|
|
201
|
-
else if (s.type === "string")
|
|
202
|
-
t = f.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
|
|
203
|
-
else if (s.type === "array")
|
|
204
|
-
t = f.createArrayTypeNode(mk(s.items));
|
|
205
|
-
else if (s.type === "null")
|
|
206
|
-
t = f.createLiteralTypeNode(f.createNull());
|
|
207
|
-
else if (isArray(s.type))
|
|
208
|
-
t = f.createUnionTypeNode(s.type.map((x) => mk({ type: x })));
|
|
209
|
-
else
|
|
210
|
-
throw new Error(`makeType: unknown type ${s.type}`);
|
|
211
|
-
if (s.type === "string") {
|
|
212
|
-
if (s.format === "binary")
|
|
213
|
-
t = f.createTypeReferenceNode("File");
|
|
214
|
-
if (s.format === "date-time")
|
|
215
|
-
t = f.createTypeReferenceNode("Date");
|
|
216
|
-
}
|
|
217
|
-
return s.nullable ? f.createUnionTypeNode([t, f.createLiteralTypeNode(f.createNull())]) : t;
|
|
218
|
-
}
|
|
219
|
-
return f.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword);
|
|
220
|
-
};
|
|
221
|
-
const isStringEnum = (s) => {
|
|
222
|
-
if ("enum" in s && s.enum) {
|
|
223
|
-
return s.enum.every((x) => typeof x === "string");
|
|
224
|
-
}
|
|
225
|
-
return false;
|
|
226
|
-
};
|
|
227
|
-
const makeTypeAlias = (ctx, name, s) => {
|
|
228
|
-
if (isStringEnum(s)) {
|
|
229
|
-
const tokens1 = s.enum;
|
|
230
|
-
const tokens2 = filterEmpty(s.enum);
|
|
231
|
-
if (tokens1.length !== tokens2.length) {
|
|
232
|
-
console.warn(`enum ${name} has empty values`, s);
|
|
233
|
-
}
|
|
234
|
-
return f.createEnumDeclaration(
|
|
235
|
-
[f.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
236
|
-
normalizeIdentifier(name, true),
|
|
237
|
-
tokens2.map(
|
|
238
|
-
(x) => f.createEnumMember(upperFirst(normalizeIdentifier(x)), f.createStringLiteral(x))
|
|
239
|
-
)
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
return f.createTypeAliasDeclaration(
|
|
243
|
-
[f.createToken(ts.SyntaxKind.ExportKeyword)],
|
|
244
|
-
f.createIdentifier(normalizeIdentifier(name, true)),
|
|
245
|
-
void 0,
|
|
246
|
-
makeType(ctx, s)
|
|
247
|
-
);
|
|
248
|
-
};
|
|
249
|
-
const unref = (ctx, s) => {
|
|
250
|
-
if (!s)
|
|
251
|
-
return void 0;
|
|
252
|
-
if ("$ref" in s && s.$ref) {
|
|
253
|
-
const parts = s.$ref.replace("#/", "").split("/");
|
|
254
|
-
const obj = parts.reduce(
|
|
255
|
-
// openapi encodes "/" in key as "~1"
|
|
256
|
-
(acc, x) => get(acc, x, get(acc, decodeURIComponent(x).replaceAll("~1", "/"))),
|
|
257
|
-
ctx.doc
|
|
258
|
-
);
|
|
259
|
-
if (obj)
|
|
260
|
-
return obj;
|
|
261
|
-
console.warn(`${ctx.tag} ref ${s.$ref} not found`);
|
|
262
|
-
return void 0;
|
|
263
|
-
}
|
|
264
|
-
return s;
|
|
265
|
-
};
|
|
266
|
-
const getReqSchema = (ctx, config) => {
|
|
267
|
-
const req = unref(ctx, config.requestBody);
|
|
268
|
-
if (!req)
|
|
269
|
-
return void 0;
|
|
270
|
-
const cts = Object.entries(req.content ?? {}).map((x) => [x[0].split(";")[0], x[1].schema]).filter((x) => x[1]);
|
|
271
|
-
if (cts.length === 0)
|
|
272
|
-
return void 0;
|
|
273
|
-
const pretenders = [
|
|
274
|
-
"application/json",
|
|
275
|
-
"text/",
|
|
276
|
-
"multipart/form-data",
|
|
277
|
-
"application/x-www-form-urlencoded"
|
|
278
|
-
];
|
|
279
|
-
for (const p of pretenders) {
|
|
280
|
-
const ct = cts.find((x) => x[0].startsWith(p));
|
|
281
|
-
if (ct)
|
|
282
|
-
return ct;
|
|
283
|
-
}
|
|
284
|
-
cts.map((x) => x[0]);
|
|
285
|
-
return void 0;
|
|
286
|
-
};
|
|
287
|
-
const getRepSchema = (ctx, config) => {
|
|
288
|
-
const successCodes = Object.keys(config.responses ?? {}).filter((x) => x.startsWith("2")).filter((x) => get(config, ["responses", x, "content"]));
|
|
289
|
-
const cts = Object.entries(get(config, ["responses", successCodes[0], "content"], {})).filter((x) => x[1].schema);
|
|
290
|
-
if (cts.length === 0)
|
|
291
|
-
return void 0;
|
|
292
|
-
const ctJson = cts.find((x) => x[0].startsWith("application/json"));
|
|
293
|
-
if (ctJson)
|
|
294
|
-
return ctJson[1].schema;
|
|
295
|
-
const ctText = cts.find((x) => x[0].startsWith("text/"));
|
|
296
|
-
if (ctText)
|
|
297
|
-
return { type: "string" };
|
|
298
|
-
cts.map((x) => x[0]).join(", ");
|
|
299
|
-
return void 0;
|
|
300
|
-
};
|
|
301
|
-
const prepareUrl = (url, rename) => {
|
|
302
|
-
for (const [k, v] of Object.entries(rename))
|
|
303
|
-
url = url.replaceAll(`{${k}}`, "${" + v + "}");
|
|
304
|
-
const parts = url.split("${");
|
|
305
|
-
if (parts.length === 1)
|
|
306
|
-
return f.createStringLiteral(url);
|
|
307
|
-
return f.createTemplateExpression(
|
|
308
|
-
f.createTemplateHead(parts[0]),
|
|
309
|
-
parts.slice(1).map((x, i) => {
|
|
310
|
-
const [name, ...rest] = x.split("}");
|
|
311
|
-
const right = rest.join("}");
|
|
312
|
-
return f.createTemplateSpan(
|
|
313
|
-
f.createIdentifier(name),
|
|
314
|
-
// no normalization required
|
|
315
|
-
i === parts.length - 2 ? f.createTemplateTail(right) : f.createTemplateMiddle(right)
|
|
316
|
-
);
|
|
317
|
-
})
|
|
318
|
-
);
|
|
319
|
-
};
|
|
320
|
-
const prepareOp = (ctx, cfg, opName) => {
|
|
321
|
-
cfg.parameters = cfg.parameters ?? [];
|
|
322
|
-
const reqSchema = getReqSchema(ctx, cfg);
|
|
323
|
-
const repSchema = getRepSchema(ctx, cfg);
|
|
324
|
-
const allParams = filterNullable(cfg.parameters.map((x) => unref(ctx, x)));
|
|
325
|
-
const params = uniqBy(allParams.filter((x) => x.in === "path"), "name");
|
|
326
|
-
if (reqSchema)
|
|
327
|
-
params.push({ name: "body", schema: reqSchema[1] });
|
|
328
|
-
const search = allParams.filter((x) => x.in === "query");
|
|
329
|
-
allParams.filter((x) => x.in === "header");
|
|
330
|
-
for (const [name, v] of Object.entries({ search })) {
|
|
331
|
-
if (!v.length)
|
|
332
|
-
continue;
|
|
333
|
-
const properties = v.reduce((acc, x) => ({ ...acc, [x.name]: x.schema }), {});
|
|
334
|
-
params.push({ name, schema: { type: "object", properties } });
|
|
335
|
-
}
|
|
336
|
-
const urlReplacements = {};
|
|
337
|
-
const fnArgs = params.map((x) => {
|
|
338
|
-
const name = normalizeIdentifier(x.name, true);
|
|
339
|
-
const type = makeType(ctx, x.schema);
|
|
340
|
-
urlReplacements[x.name] = name;
|
|
341
|
-
return f.createParameterDeclaration(void 0, void 0, name, void 0, type);
|
|
342
|
-
});
|
|
343
|
-
const cbArgs = filterNullable([
|
|
344
|
-
search.length ? f.createShorthandPropertyAssignment("search") : void 0,
|
|
345
|
-
reqSchema && f.createShorthandPropertyAssignment("body"),
|
|
346
|
-
reqSchema && reqSchema[0] !== "application/json" ? f.createPropertyAssignment(
|
|
347
|
-
"headers",
|
|
348
|
-
f.createObjectLiteralExpression([
|
|
349
|
-
f.createPropertyAssignment(
|
|
350
|
-
f.createStringLiteral("content-type"),
|
|
351
|
-
f.createStringLiteral(reqSchema[0])
|
|
352
|
-
)
|
|
353
|
-
])
|
|
354
|
-
) : void 0
|
|
355
|
-
]);
|
|
356
|
-
return f.createPropertyAssignment(
|
|
357
|
-
f.createIdentifier(normalizeIdentifier(opName)),
|
|
358
|
-
f.createArrowFunction(
|
|
359
|
-
void 0,
|
|
360
|
-
void 0,
|
|
361
|
-
fnArgs,
|
|
362
|
-
void 0,
|
|
363
|
-
void 0,
|
|
364
|
-
f.createBlock([
|
|
365
|
-
f.createReturnStatement(
|
|
366
|
-
f.createCallExpression(
|
|
367
|
-
f.createIdentifier("this.Fetch"),
|
|
368
|
-
[makeType(ctx, repSchema)],
|
|
369
|
-
[
|
|
370
|
-
f.createStringLiteral(cfg.method),
|
|
371
|
-
// method
|
|
372
|
-
prepareUrl(cfg.path, urlReplacements),
|
|
373
|
-
// path
|
|
374
|
-
f.createObjectLiteralExpression(cbArgs)
|
|
375
|
-
// { query, body, headers }
|
|
376
|
-
]
|
|
377
|
-
)
|
|
378
|
-
)
|
|
379
|
-
])
|
|
380
|
-
)
|
|
381
|
-
);
|
|
382
|
-
};
|
|
383
|
-
const prepareNs = (ctx, name, handlers) => {
|
|
384
|
-
return f.createPropertyDeclaration(
|
|
385
|
-
void 0,
|
|
386
|
-
normalizeIdentifier(name),
|
|
387
|
-
void 0,
|
|
388
|
-
void 0,
|
|
389
|
-
f.createObjectLiteralExpression(handlers)
|
|
390
|
-
);
|
|
391
|
-
};
|
|
392
|
-
const prepareRoutes = async (ctx) => {
|
|
393
|
-
const routes = {};
|
|
394
|
-
for (const [path, pathConfig] of Object.entries(ctx.doc.paths ?? {})) {
|
|
395
|
-
ctx.tag = `${"[ALL]".toUpperCase().padEnd(6, " ")} ${path}`;
|
|
396
|
-
if (!isObject(pathConfig))
|
|
397
|
-
continue;
|
|
398
|
-
if ("$ref" in pathConfig) {
|
|
399
|
-
console.warn(`${ctx.tag} $ref should be resolved before (skipping)`);
|
|
400
|
-
continue;
|
|
401
|
-
}
|
|
402
|
-
for (const method of HttpMethods) {
|
|
403
|
-
ctx.tag = `${method.toUpperCase().padEnd(6, " ")} ${path}`;
|
|
404
|
-
const config = pathConfig[method];
|
|
405
|
-
if (!config)
|
|
406
|
-
continue;
|
|
407
|
-
if (pathConfig.parameters) {
|
|
408
|
-
config.parameters = [...config.parameters ?? [], ...pathConfig.parameters];
|
|
409
|
-
}
|
|
410
|
-
const [ns, op] = getOpName(ctx, { ...config, method, path });
|
|
411
|
-
if (!routes[ns])
|
|
412
|
-
routes[ns] = [];
|
|
413
|
-
const joined = [ns, op].join(".");
|
|
414
|
-
if (ctx.opNames.has(joined)) {
|
|
415
|
-
continue;
|
|
416
|
-
} else {
|
|
417
|
-
ctx.opNames.add(joined);
|
|
418
|
-
}
|
|
419
|
-
try {
|
|
420
|
-
routes[ns].push(prepareOp(ctx, { ...config, method, path }, op));
|
|
421
|
-
} catch (e) {
|
|
422
|
-
console.error(`${ctx.tag} - ${e}`, config);
|
|
423
|
-
throw e;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
return routes;
|
|
428
|
-
};
|
|
429
|
-
const prepareTypes = async (ctx) => {
|
|
430
|
-
const types = [];
|
|
431
|
-
const typesConfig = sortBy(Object.entries(ctx.doc.components?.schemas ?? {}), ([k]) => k);
|
|
432
|
-
for (const [name, config] of typesConfig) {
|
|
433
|
-
try {
|
|
434
|
-
types.push(makeTypeAlias(ctx, name, config));
|
|
435
|
-
} catch (e) {
|
|
436
|
-
console.error(`${ctx.tag} - ${e}`, name, config);
|
|
437
|
-
throw e;
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
return types;
|
|
441
|
-
};
|
|
442
|
-
const patchTemplate = async (ctx, modules) => {
|
|
443
|
-
const filepath = join(dirname(fileURLToPath(import.meta.url)), "_template.ts");
|
|
444
|
-
const file = await fs.readFile(filepath, "utf-8");
|
|
445
|
-
const root = ts.createSourceFile("tmpl.ts", file, ts.ScriptTarget.Latest);
|
|
446
|
-
return Array.from(root.statements).map((x) => {
|
|
447
|
-
if (x.kind === ts.SyntaxKind.ClassDeclaration) {
|
|
448
|
-
const name = get(x, "name.text");
|
|
449
|
-
if (name === "ApiClient") {
|
|
450
|
-
const t = x;
|
|
451
|
-
return f.updateClassDeclaration(
|
|
452
|
-
t,
|
|
453
|
-
t.modifiers,
|
|
454
|
-
f.createIdentifier(ctx.name),
|
|
455
|
-
t.typeParameters,
|
|
456
|
-
t.heritageClauses,
|
|
457
|
-
addNewLines([...t.members, ...modules])
|
|
458
|
-
);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
return x;
|
|
462
|
-
});
|
|
463
|
-
};
|
|
464
|
-
const prepareAst = async (ctx) => {
|
|
465
|
-
const types = await prepareTypes(ctx);
|
|
466
|
-
const routes = await prepareRoutes(ctx);
|
|
467
|
-
const modules = [];
|
|
468
|
-
for (const [k, v] of Object.entries(routes)) {
|
|
469
|
-
modules.push(prepareNs(ctx, k, v));
|
|
470
|
-
}
|
|
471
|
-
return filterNullable([...await patchTemplate(ctx, modules), ...types]);
|
|
472
|
-
};
|
|
473
|
-
const addNewLines = (nodes) => {
|
|
474
|
-
const result = [];
|
|
475
|
-
for (const node of nodes) {
|
|
476
|
-
result.push(node);
|
|
477
|
-
result.push(f.createIdentifier("\n"));
|
|
478
|
-
}
|
|
479
|
-
return result;
|
|
480
|
-
};
|
|
481
|
-
const loadSchema = async (url) => {
|
|
482
|
-
const { bundle } = await redocly.bundle({
|
|
483
|
-
ref: url,
|
|
484
|
-
config: await redocly.createConfig({}),
|
|
485
|
-
removeUnusedComponents: false
|
|
486
|
-
});
|
|
487
|
-
if (bundle.parsed.swagger) {
|
|
488
|
-
const { openapi } = await convertObj(bundle.parsed, { patch: true });
|
|
489
|
-
return openapi;
|
|
490
|
-
}
|
|
491
|
-
return bundle.parsed;
|
|
492
|
-
};
|
|
493
|
-
const printCode = async (nodes) => {
|
|
494
|
-
const code = ts.createPrinter().printFile(
|
|
495
|
-
f.createSourceFile(
|
|
496
|
-
addNewLines(nodes),
|
|
497
|
-
f.createToken(ts.SyntaxKind.EndOfFileToken),
|
|
498
|
-
ts.NodeFlags.None
|
|
499
|
-
)
|
|
500
|
-
).replaceAll("}, ", "},\n\n");
|
|
501
|
-
const options = await prettier.resolveConfig(process.cwd());
|
|
502
|
-
return prettier.format(code, { ...options, parser: "typescript" });
|
|
503
|
-
};
|
|
504
|
-
const apigen = async (source, output, cfg) => {
|
|
505
|
-
const doc = await loadSchema(source);
|
|
506
|
-
const ast = await prepareAst(initCtx(doc, cfg));
|
|
507
|
-
const txt = [
|
|
508
|
-
`// Auto-generated by https://github.com/vladkens/apigen-ts`,
|
|
509
|
-
`// Source: ${source}
|
|
510
|
-
`,
|
|
511
|
-
await printCode(ast)
|
|
512
|
-
].join("\n");
|
|
513
|
-
await fs.mkdir(dirname(output), { recursive: true });
|
|
514
|
-
await fs.writeFile(output, txt);
|
|
515
|
-
};
|
|
2
|
+
import { a as apigen, g as getCliConfig } from './main-0c2fa229.js';
|
|
3
|
+
import 'fs/promises';
|
|
4
|
+
import 'path';
|
|
5
|
+
import 'url';
|
|
6
|
+
import 'cleye';
|
|
7
|
+
import '@redocly/openapi-core';
|
|
8
|
+
import 'array-utils-ts';
|
|
9
|
+
import 'lodash-es';
|
|
10
|
+
import 'swagger2openapi';
|
|
11
|
+
import 'typescript';
|
|
12
|
+
import 'prettier';
|
|
516
13
|
|
|
517
14
|
const main = async () => {
|
|
518
|
-
|
|
519
|
-
name: "apigen",
|
|
520
|
-
// version: "0.0.1",
|
|
521
|
-
parameters: ["<source>", "<output>"],
|
|
522
|
-
flags: {
|
|
523
|
-
name: {
|
|
524
|
-
type: String,
|
|
525
|
-
description: "api class name to export",
|
|
526
|
-
default: "ApiClient"
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
});
|
|
530
|
-
await apigen(argv._.source, argv._.output, { name: argv.flags.name });
|
|
15
|
+
await apigen(getCliConfig());
|
|
531
16
|
};
|
|
532
17
|
main();
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { a as apigen, g as getCliConfig } from './main-0c2fa229.mjs';
|
|
2
|
+
import 'fs/promises';
|
|
3
|
+
import 'path';
|
|
4
|
+
import 'url';
|
|
5
|
+
import 'cleye';
|
|
6
|
+
import '@redocly/openapi-core';
|
|
7
|
+
import 'array-utils-ts';
|
|
8
|
+
import 'lodash-es';
|
|
9
|
+
import 'swagger2openapi';
|
|
10
|
+
import 'typescript';
|
|
11
|
+
import 'prettier';
|
|
12
|
+
|
|
13
|
+
const main = async () => {
|
|
14
|
+
await apigen(getCliConfig());
|
|
15
|
+
};
|
|
16
|
+
main();
|